docs(agents): status table, test counts and release narrative for v1.… #23
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Release (npm + MCP registry) via trusted publishing (OIDC) | |
| # | |
| # WHAT THIS DOES | |
| # On a version tag (v*) — or a manual dispatch — this publishes, in order: | |
| # 1. the core package @chanmeng666/archlang (repo root) → npm, with provenance | |
| # 2. the MCP shim @chanmeng666/archlang-mcp (packages/mcp) → npm, with provenance | |
| # 3. the MCP server manifest (packages/mcp/server.json) → the official MCP registry | |
| # Each step is idempotent: it reads the version from the package's own package.json and | |
| # skips the publish when that exact version already exists on the registry, so re-running | |
| # a partially-failed release (or dispatching after a fix) is safe. | |
| # | |
| # TRUSTED PUBLISHING — NO NPM TOKEN EXISTS ANYWHERE | |
| # Publishing authenticates via GitHub OIDC (`permissions: id-token: write`), not a stored | |
| # secret. There is deliberately NO npm automation token in repo secrets, org secrets, or | |
| # .npmrc. This works only because each package is registered ONCE, by hand, as a trusted | |
| # publisher on npmjs.com: | |
| # npmjs.com → each package → Settings → Trusted Publisher → GitHub Actions, with | |
| # Owner/Repo = ChanMeng666/archlang and Workflow filename = release.yml | |
| # Both @chanmeng666/archlang AND @chanmeng666/archlang-mcp must have that one-time | |
| # registration pointing at THIS file. Without it, `npm publish` here fails with an auth | |
| # error — that is the expected signal to (re)do the npmjs registration, not to add a token. | |
| # | |
| # The MCP registry publish (step 3) also uses GitHub OIDC (`mcp-publisher login | |
| # github-oidc`) — no secret. The registry validates server.json against the just-published | |
| # npm package (mcpName ↔ name, case-sensitive io.github.ChanMeng666/… owner, ≤100-char | |
| # description). NOTE: the MCP registry is in public preview and may reset data / change | |
| # behaviour; this step is kept strict (fails loudly) so a regression surfaces here. | |
| # | |
| # House style mirrors ci.yml / deploy.yml (actions/checkout@v5, actions/setup-node@v5). | |
| name: Release (npm + MCP registry via OIDC) | |
| on: | |
| push: | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| permissions: | |
| id-token: write # OIDC token exchange for npm trusted publishing + mcp-publisher | |
| contents: write # create the GitHub Release for the pushed v* tag (read is not enough) | |
| jobs: | |
| publish: | |
| name: Publish (npm + MCP registry) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: 22 | |
| registry-url: 'https://registry.npmjs.org' # required for OIDC token exchange | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci # with the bundled npm, like ci.yml | |
| # Trusted publishing needs npm >= 11.5.1. Pin to 11.x on purpose, NOT npm@latest: | |
| # npm 12 defaults allowScripts off, which would block esbuild's postinstall — and we | |
| # only upgrade AFTER `npm ci` above has already run its install scripts. | |
| - name: Pin npm to 11.x (trusted-publishing minimum) | |
| run: | | |
| npm install -g npm@11.18.0 | |
| npm --version | |
| # Idempotency: resolve each package's declared version and check whether it is already | |
| # on the registry. `npm view pkg@ver version` prints the version if it exists, nothing | |
| # if that version is absent, and errors if the package name is unknown (|| true). | |
| - name: Resolve versions and decide what to publish | |
| id: check | |
| run: | | |
| core_version=$(node -p "require('./package.json').version") | |
| mcp_version=$(node -p "require('./packages/mcp/package.json').version") | |
| echo "core_version=$core_version" >> "$GITHUB_OUTPUT" | |
| echo "mcp_version=$mcp_version" >> "$GITHUB_OUTPUT" | |
| core_on_registry=$(npm view "@chanmeng666/archlang@$core_version" version 2>/dev/null || true) | |
| mcp_on_registry=$(npm view "@chanmeng666/archlang-mcp@$mcp_version" version 2>/dev/null || true) | |
| if [ "$core_on_registry" = "$core_version" ]; then | |
| echo "core_exists=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::@chanmeng666/archlang@$core_version already on npm — skipping core publish." | |
| else | |
| echo "core_exists=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Will publish @chanmeng666/archlang@$core_version to npm." | |
| fi | |
| if [ "$mcp_on_registry" = "$mcp_version" ]; then | |
| echo "mcp_exists=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::@chanmeng666/archlang-mcp@$mcp_version already on npm — skipping mcp npm publish." | |
| else | |
| echo "mcp_exists=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Will publish @chanmeng666/archlang-mcp@$mcp_version to npm." | |
| fi | |
| # The MCP-registry state is checked INDEPENDENTLY of npm: if a previous run | |
| # published to npm but died before the registry sync, `mcp_exists` is true on the | |
| # re-run and must not also skip the sync. The search API is best-effort (the | |
| # registry is in preview): if the probe fails or the shape changes, we attempt the | |
| # sync and let `mcp-publisher publish` be the arbiter (a duplicate fails loudly). | |
| reg_json=$(curl -fsSL "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.ChanMeng666/archlang-mcp" || echo "") | |
| if echo "$reg_json" | grep -q "\"version\": *\"$mcp_version\""; then | |
| echo "registry_synced=true" >> "$GITHUB_OUTPUT" | |
| echo "::notice::MCP registry already has $mcp_version — skipping registry sync." | |
| else | |
| echo "registry_synced=false" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Will sync io.github.ChanMeng666/archlang-mcp@$mcp_version to the MCP registry." | |
| fi | |
| # prepublishOnly is `npm run build && npm run test`, and `npm run test` is the WHOLE | |
| # monorepo suite — which includes `editors/vscode/test/stdio.test.ts`. That suite hard-fails | |
| # under CI when the core is built but `editors/vscode/dist/server.js` is not, on the | |
| # (correct) reasoning that a present core plus an absent bundle means nothing about the | |
| # shipped bundle was verified. `npm run build` does not build that bundle, so the publish | |
| # path has to, or the gate fires on an artifact the CORE publish never had a reason to make. | |
| # Building it here keeps the gate at full strength instead of narrowing it: the bundle is | |
| # genuinely verified during the release. (v1.25.0 was the first release after the bundle | |
| # tests landed, and the first to hit this.) | |
| - name: Build the core and the VS Code bundle (prepublishOnly runs the bundle tests) | |
| run: npm run build && npm run vscode:build:only | |
| # prepublishOnly (build + full test suite) is the release gate for the core. | |
| - name: Publish core to npm (@chanmeng666/archlang) | |
| if: ${{ success() && steps.check.outputs.core_exists != 'true' }} | |
| run: npm publish --provenance --access public | |
| # Runs only after the core step. prepack builds the shim (core stays external — the | |
| # shim never re-bundles it, so it does not need the core's dist/ to be freshly built). | |
| - name: Publish MCP shim to npm (@chanmeng666/archlang-mcp) | |
| if: ${{ success() && steps.check.outputs.mcp_exists != 'true' }} | |
| run: npm publish -w packages/mcp --provenance --access public | |
| # MCP registry sync — whenever the registry lags server.json's version (independent of | |
| # whether npm published this run: by this point npm has the version either way, and the | |
| # registry validates against npm). Official install/auth flow from | |
| # https://modelcontextprotocol.io/registry/github-actions (GitHub OIDC variant). | |
| # server.json lives in packages/mcp, so run from there. | |
| - name: Install mcp-publisher CLI | |
| if: ${{ success() && steps.check.outputs.registry_synced != 'true' }} | |
| working-directory: packages/mcp | |
| # Pinned + checksum-verified (the runner is linux/amd64). To bump: update VERSION | |
| # and SHA256 together (sha256sum of the linux_amd64 tarball). | |
| env: | |
| MCP_PUBLISHER_VERSION: v1.7.9 | |
| MCP_PUBLISHER_SHA256: ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac | |
| run: | | |
| curl -fsSLo mcp-publisher.tar.gz "https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz" | |
| echo "${MCP_PUBLISHER_SHA256} mcp-publisher.tar.gz" | sha256sum -c - | |
| tar xz -f mcp-publisher.tar.gz mcp-publisher | |
| test -x ./mcp-publisher && echo "mcp-publisher installed" | |
| - name: Authenticate to MCP Registry (GitHub OIDC) | |
| if: ${{ success() && steps.check.outputs.registry_synced != 'true' }} | |
| working-directory: packages/mcp | |
| run: ./mcp-publisher login github-oidc | |
| # Bounded retry, and the reason is a RACE we have actually seen. The registry validates | |
| # `server.json` against the npm package it names — and on the v1.30.0 release that | |
| # validation 404'd on a version this same job had published moments earlier, because | |
| # the npm registry had not yet made it visible to a third party. `gh run rerun --failed` | |
| # then succeeded with no change, which is the signature of a race and not of a broken | |
| # manifest. Six attempts, 20 s apart, is ~2 minutes of patience against a lookup that | |
| # normally settles in seconds; a genuinely bad manifest still fails, just two minutes | |
| # later, and the last attempt's output is what the log shows. | |
| - name: Publish server to MCP Registry | |
| if: ${{ success() && steps.check.outputs.registry_synced != 'true' }} | |
| working-directory: packages/mcp | |
| run: | | |
| for attempt in 1 2 3 4 5 6; do | |
| if ./mcp-publisher publish; then | |
| echo "::notice::MCP registry publish succeeded on attempt $attempt." | |
| exit 0 | |
| fi | |
| if [ "$attempt" -lt 6 ]; then | |
| echo "::warning::MCP registry publish failed (attempt $attempt/6) — retrying in 20s." | |
| sleep 20 | |
| fi | |
| done | |
| echo "::error::MCP registry publish failed after 6 attempts." | |
| exit 1 | |
| # The GitHub Release. This step used to not exist: releases were created by hand, so | |
| # they drifted — 23 tags shipped with NO Release at all, including the two newest, and | |
| # the Releases page advertised v1.13.0 as "Latest" while npm served 1.15.0. | |
| # | |
| # Notes are EXTRACTED from CHANGELOG.md (the canonical release narrative — AGENTS.md | |
| # keeps it there and nowhere else) rather than hand-written or derived from commit | |
| # subjects, so the Release page cannot say something the changelog doesn't. The | |
| # extractor exits non-zero when a version has no section, so an undocumented release | |
| # fails loudly instead of shipping with an empty body. | |
| # | |
| # Last, and only on success: a Release is the human-visible announcement, so it must | |
| # not appear for a run whose npm/registry publish failed. Idempotent — a re-run over an | |
| # existing Release skips instead of failing (matching how the publish steps behave). | |
| - name: Create the GitHub Release | |
| if: ${{ success() && startsWith(github.ref, 'refs/tags/v') }} | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ github.ref_name }} | |
| run: | | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "::notice::GitHub Release $TAG already exists — skipping." | |
| exit 0 | |
| fi | |
| node scripts/changelog-section.mjs "$TAG" > release-notes.md | |
| gh release create "$TAG" \ | |
| --title "$TAG" \ | |
| --notes-file release-notes.md \ | |
| --verify-tag | |
| echo "::notice::Created GitHub Release $TAG from its CHANGELOG.md section." |