From 3f7defea2436794ec8986a07dd2c87d6d466686d Mon Sep 17 00:00:00 2001 From: CodeSigils Date: Sun, 6 Sep 2026 10:40:20 +0300 Subject: [PATCH] ci: separate deterministic and external checks # Conflicts: # .github/workflows/ci.yml --- .github/workflows/ci.yml | 57 +++++++++++++++++++++++++++++++--------- README.md | 21 ++++++++++++--- scripts/validators/ci.js | 11 ++++++++ 3 files changed, 73 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29b0424..f268b5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: tags: ['v*'] pull_request: branches: [main] + schedule: + - cron: '17 4 * * 1' + workflow_dispatch: permissions: contents: read @@ -15,13 +18,9 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - test: + lint: runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - fail-fast: false - matrix: - node-version: ['24.x', '25.x'] env: CHECK_BASE_REF: origin/main GH_TOKEN: ${{ github.token }} @@ -31,30 +30,64 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: ${{ matrix.node-version }} + node-version-file: .node-version cache: 'npm' cache-dependency-path: package-lock.json - run: npm ci - - run: npm test - run: npm run format:check - - if: matrix.node-version == '24.x' - run: mkdir -p artifacts && npm pack --pack-destination artifacts - run: bash -n scripts/check-markdown.sh - run: bash scripts/staged-install-verify.sh - run: git diff --exit-code -- skills/markdown-formatter - run: npm audit --audit-level=high - - run: npm run verify:urls - run: npm run check:links + - name: Build tested npm package + run: mkdir -p artifacts && npm pack --pack-destination artifacts - name: Upload tested npm package - if: matrix.node-version == '24.x' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with: name: npm-package path: artifacts/*.tgz if-no-files-found: error + test: + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + node-version: ['24.x', '25.x'] + env: + CHECK_BASE_REF: origin/main + GH_TOKEN: ${{ github.token }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + with: + fetch-depth: 0 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: package-lock.json + - run: npm ci + - run: npm test + + external-contracts: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 + with: + node-version-file: .node-version + cache: 'npm' + cache-dependency-path: package-lock.json + - run: npm ci + - name: Verify evidence URLs + run: npm run verify:urls + publish: - needs: test + needs: [test, lint] if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/README.md b/README.md index 4816d2e..dba017f 100644 --- a/README.md +++ b/README.md @@ -444,12 +444,25 @@ compatible minor/patch npm updates and action updates into focused pull requests; CI remains the merge gate. Review major npm updates separately for runtime or formatting behavior changes. +### CI and pull requests + +Every pull request must pass the deterministic `lint` gate and both runtime +matrix jobs (`test (24.x)` and `test (25.x)`). The lint gate runs formatting, +payload synchronization, dependency audit, offline link checks, and builds the +single npm tarball used by publishing; the runtime jobs focus on the test suite +across supported Node versions. Publishing is allowed only after all three +gates succeed. + +Live HTTP checks are intentionally not required for pull requests. The +`external-contracts` job runs weekly and on demand from the Actions tab, so an +upstream outage does not make an otherwise deterministic change unmergeable. + ### Evidence URLs [`docs/evidence-urls.json`](docs/evidence-urls.json) records the external references used by the README and skill. Unit tests enforce that every -`last_verified` value is no more than 30 days old; CI also performs live HTTP -verification. +`last_verified` value is no more than 30 days old. The scheduled/manual +`external-contracts` job performs live HTTP verification. After checking the links live, refresh the timestamps with: @@ -483,8 +496,8 @@ before removing that token. Runtime changes must be merged before the isolated version-bump commit. `release.sh` requires a clean tree, synchronized skill metadata, a stable `x.y.z` version, an isolated version commit, a pushed `main`, and successful -CI. It creates the tag and GitHub Release; the tag workflow publishes the exact -npm tarball tested by CI. +CI. It creates the tag and GitHub Release; the `publish` job publishes the exact +npm tarball built and tested by CI. ```bash npm version patch --no-git-tag-version # or minor/major diff --git a/scripts/validators/ci.js b/scripts/validators/ci.js index 9a9c04e..85e1761 100644 --- a/scripts/validators/ci.js +++ b/scripts/validators/ci.js @@ -32,6 +32,8 @@ function validateCi(files) { // Required patterns that must appear const required = [ + { pattern: /\n\s+lint:\s*\n/i, label: "runs a deterministic lint/package gate" }, + { pattern: /\n\s+external-contracts:\s*\n/i, label: "isolates live external URL checks" }, { pattern: /npm\s+test\b/i, label: "runs the canonical npm test suite" }, { pattern: /npm\s+run\s+format:check/i, label: "checks maintainer docs formatting" }, { pattern: /staged-install-verify\.sh/i, label: "verifies staged runtime payload" }, @@ -48,6 +50,15 @@ function validateCi(files) { } } + // Keep network-dependent evidence checks out of the required runtime matrix. + const testJob = ci.match(/\n\s+test:\s*\n([\s\S]*?)(?=\n\s+[a-z][\w-]*:\s*\n|\s*$)/i); + if (testJob && /verify:urls/i.test(testJob[1])) { + errors.push("ci.yml: live evidence URL checks must run only in external-contracts"); + } + if (!/needs:\s*\[\s*test\s*,\s*lint\s*\]/i.test(ci)) { + warnings.push("ci.yml: publish should require both test and lint jobs"); + } + // .node-version alignment const nodeVersionContent = read(".node-version"); const ciNodeVersion = nodeVersionContent