diff --git a/CHANGELOG.md b/CHANGELOG.md index 725562a55..b6f3e0fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ ## [Unreleased] +## [1.2.2] - 2026-07-29 + +**Which packages this version covers.** Only `@beyondnet/evolith-cli` and +`@beyondnet/evolith-mcp`, the two that declared the stale dependency range. No +other package changed, and republishing them would only add noise to the +registry. + +### Bug Fixes + +* **deps:** the CLI and the MCP server now resolve `@beyondnet/evolith-sdk@^2.0.0` + instead of `^1.1.0` (GT-634). The SDK publishes `1.0.0`, `1.1.0` and `2.0.0` + with nothing in between, so a caret range under 1.x pinned **exactly 1.1.0** — + published 2026-07-18, five days before the security wave that shipped in 2.0.0 + on 2026-07-27. Both consumers were published on 2026-07-28, after 2.0.0 existed, + still pointing at the 1.x line, so `npm install @beyondnet/evolith-cli@latest` + installed a pre-wave SDK. **The exposure described in the 1.2.0 section below is + therefore not closed by 1.2.0 alone for this dependency; it is closed here.** + + The lockfile made it concrete rather than cosmetic: it pinned the registry + tarball of `sdk@1.1.0`, with integrity hashes, at + `src/sdk/cli/node_modules/@beyondnet/evolith-sdk` and + `src/packages/mcp-server/node_modules/@beyondnet/evolith-sdk`. A clean `npm ci` + fetched the pre-wave SDK and nested it inside each consumer, where it shadows + the workspace link — the top-level symlink to the local 2.0.0 is what a + developer reads, and the nested 1.1.0 is what a fresh install resolves. Both + nested entries disappear once the range is `^2.0.0`. + + Neither package re-exports the SDK, and both use only `EvolithRestClient`, so + 2.0.0's breaking change (payload types re-exported from `core-domain`, + `.passed` → `.verdict`, `'info'` severity retired) reaches no public API of + either. That is why this is a patch and not a minor. + ## [1.2.0] - 2026-07-27 Changes accumulated since the 1.0.0 release of 2026-06-28. Automated changelog diff --git a/package-lock.json b/package-lock.json index a443ffedf..8ef2af7fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16576,14 +16576,14 @@ }, "src/packages/mcp-server": { "name": "@beyondnet/evolith-mcp", - "version": "1.2.1", + "version": "1.2.2", "license": "MIT", "dependencies": { "@beyondnet/evolith-agent-runtime": "^1.1.0", "@beyondnet/evolith-core": "^1.2.0", "@beyondnet/evolith-core-domain": "^1.2.0", "@beyondnet/evolith-infra-providers": "^1.2.0", - "@beyondnet/evolith-sdk": "^1.1.0", + "@beyondnet/evolith-sdk": "^2.0.0", "@modelcontextprotocol/sdk": "1.29.0", "@nestjs/cache-manager": "3.1.3", "@nestjs/common": "11.1.27", @@ -16633,12 +16633,6 @@ "node": ">=20.0.0" } }, - "src/packages/mcp-server/node_modules/@beyondnet/evolith-sdk": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@beyondnet/evolith-sdk/-/evolith-sdk-1.1.0.tgz", - "integrity": "sha512-x53iNqKqiNOh13KcxqGCfI52T7X6BO7qXR8PMpUUFrAofwK2cZeIMFQ16Ku2P+GhLsQygQwofx4poB+3qM0F2w==", - "license": "MIT" - }, "src/packages/mcp-server/node_modules/@nestjs/cache-manager": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-3.1.3.tgz", @@ -16892,13 +16886,13 @@ }, "src/sdk/cli": { "name": "@beyondnet/evolith-cli", - "version": "1.2.1", + "version": "1.2.2", "license": "MIT", "dependencies": { "@beyondnet/evolith-agent-runtime": "^1.1.0", "@beyondnet/evolith-core-domain": "^1.2.0", "@beyondnet/evolith-infra-providers": "^1.2.0", - "@beyondnet/evolith-sdk": "^1.1.0", + "@beyondnet/evolith-sdk": "^2.0.0", "@clack/prompts": "1.5.1", "@hono/node-server": "1.19.14", "@modelcontextprotocol/sdk": "1.29.0", @@ -16949,12 +16943,6 @@ "node": ">=18.0.0" } }, - "src/sdk/cli/node_modules/@beyondnet/evolith-sdk": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@beyondnet/evolith-sdk/-/evolith-sdk-1.1.0.tgz", - "integrity": "sha512-x53iNqKqiNOh13KcxqGCfI52T7X6BO7qXR8PMpUUFrAofwK2cZeIMFQ16Ku2P+GhLsQygQwofx4poB+3qM0F2w==", - "license": "MIT" - }, "src/sdk/cli/node_modules/@typescript-eslint/parser": { "version": "8.64.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", diff --git a/product/products/smart-cli/product-inventory.es.md b/product/products/smart-cli/product-inventory.es.md index 60347d866..058440ca2 100644 --- a/product/products/smart-cli/product-inventory.es.md +++ b/product/products/smart-cli/product-inventory.es.md @@ -7,7 +7,7 @@ Inventario generado de la superficie instalable de Evolith CLI y MCP. No editar | Campo | Valor | |---|---| -| Package | `@beyondnet/evolith-cli@1.2.1` | +| Package | `@beyondnet/evolith-cli@1.2.2` | | Binary | `evolith`, `evolith-cli` | | CLI commands | 35 | | MCP tools | 50 | @@ -15,4 +15,4 @@ Inventario generado de la superficie instalable de Evolith CLI y MCP. No editar | MCP prompts | 8 | | MCP transports | `stdio (JSON-RPC 2.0)`, `Streamable HTTP (official MCP SDK)` | | Phase-gate schemas | 45 | -| Cobertura de statements | 88.09% statements · 88.38% lines | +| Cobertura de statements | pendiente de un run de cobertura | diff --git a/product/products/smart-cli/product-inventory.md b/product/products/smart-cli/product-inventory.md index 0f7e134ad..4dc3ab026 100644 --- a/product/products/smart-cli/product-inventory.md +++ b/product/products/smart-cli/product-inventory.md @@ -7,7 +7,7 @@ Generated inventory of the installable Evolith CLI and MCP surface. Do not edit | Field | Value | |---|---| -| Package | `@beyondnet/evolith-cli@1.2.1` | +| Package | `@beyondnet/evolith-cli@1.2.2` | | Binary | `evolith`, `evolith-cli` | | CLI commands | 35 | | MCP tools | 50 | @@ -15,4 +15,4 @@ Generated inventory of the installable Evolith CLI and MCP surface. Do not edit | MCP prompts | 8 | | MCP transports | `stdio (JSON-RPC 2.0)`, `Streamable HTTP (official MCP SDK)` | | Phase-gate schemas | 45 | -| Statement coverage | 88.09% statements · 88.38% lines | +| Statement coverage | pending a coverage run | diff --git a/reference/core/control-center/maturity-reports/maturity-reconciliation.json b/reference/core/control-center/maturity-reports/maturity-reconciliation.json index 2f7efefbc..384689743 100644 --- a/reference/core/control-center/maturity-reports/maturity-reconciliation.json +++ b/reference/core/control-center/maturity-reports/maturity-reconciliation.json @@ -11,7 +11,7 @@ }, "evidence": { "closureRecords": 574, - "cliPackage": "@beyondnet/evolith-cli@1.2.1", + "cliPackage": "@beyondnet/evolith-cli@1.2.2", "adrCount": 139, "rulesetCount": 175, "schemaCount": 45 diff --git a/src/packages/mcp-server/package.json b/src/packages/mcp-server/package.json index 9d6ab6044..100f89a2f 100644 --- a/src/packages/mcp-server/package.json +++ b/src/packages/mcp-server/package.json @@ -1,6 +1,6 @@ { "name": "@beyondnet/evolith-mcp", - "version": "1.2.1", + "version": "1.2.2", "description": "Evolith MCP Gateway — NestJS-based Model Context Protocol server for AI agents. Supports stdio and SSE transports.", "main": "dist/main.js", "types": "dist/main.d.ts", @@ -41,7 +41,7 @@ "@beyondnet/evolith-core": "^1.2.0", "@beyondnet/evolith-core-domain": "^1.2.0", "@beyondnet/evolith-infra-providers": "^1.2.0", - "@beyondnet/evolith-sdk": "^1.1.0", + "@beyondnet/evolith-sdk": "^2.0.0", "@modelcontextprotocol/sdk": "1.29.0", "@nestjs/cache-manager": "3.1.3", "@nestjs/common": "11.1.27", diff --git a/src/rulesets/contracts/evolith-machine-contracts.json b/src/rulesets/contracts/evolith-machine-contracts.json index 22c1c705e..013babc9d 100644 --- a/src/rulesets/contracts/evolith-machine-contracts.json +++ b/src/rulesets/contracts/evolith-machine-contracts.json @@ -3,7 +3,7 @@ "compatibilityPolicy": "semver-major", "producer": { "package": "@beyondnet/evolith-cli", - "version": "1.2.1" + "version": "1.2.2" }, "schemas": [ { diff --git a/src/sdk/cli/CHANGELOG.md b/src/sdk/cli/CHANGELOG.md index 851d721c7..b6e01a66f 100644 --- a/src/sdk/cli/CHANGELOG.md +++ b/src/sdk/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [1.2.2] + +### Bug Fixes + +* **deps:** resolve `@beyondnet/evolith-sdk@^2.0.0` instead of `^1.1.0` (GT-634). The SDK publishes `1.0.0`, `1.1.0` and `2.0.0` with nothing in between, so the caret range pinned **exactly 1.1.0** — the build of 2026-07-18, five days before the security wave that shipped in 2.0.0. `1.2.1` was published on 2026-07-28, after 2.0.0 existed, so `npm i -g @beyondnet/evolith-cli` installed a pre-wave SDK. The lockfile also pinned that tarball at `src/sdk/cli/node_modules/@beyondnet/evolith-sdk`, where a nested install shadows the workspace link — which is why no local run showed it. Only `EvolithRestClient` is used and the SDK is not re-exported, so 2.0.0's breaking change reaches no part of this package's surface. + ## [1.0.2] ### Bug Fixes diff --git a/src/sdk/cli/package.json b/src/sdk/cli/package.json index f2b67f419..6bc901df2 100644 --- a/src/sdk/cli/package.json +++ b/src/sdk/cli/package.json @@ -1,6 +1,6 @@ { "name": "@beyondnet/evolith-cli", - "version": "1.2.1", + "version": "1.2.2", "description": "Evolith CLI - Governance, standards validation, and AI agent integration for satellite repositories", "main": "dist/main.js", "bin": { @@ -73,7 +73,7 @@ "@beyondnet/evolith-agent-runtime": "^1.1.0", "@beyondnet/evolith-core-domain": "^1.2.0", "@beyondnet/evolith-infra-providers": "^1.2.0", - "@beyondnet/evolith-sdk": "^1.1.0", + "@beyondnet/evolith-sdk": "^2.0.0", "@hono/node-server": "1.19.14", "@modelcontextprotocol/sdk": "1.29.0", "@nestjs/common": "11.1.27", diff --git a/src/sdk/cli/scripts/check-install-smoke.mjs b/src/sdk/cli/scripts/check-install-smoke.mjs index cc4e87bb0..c9ac388ac 100644 --- a/src/sdk/cli/scripts/check-install-smoke.mjs +++ b/src/sdk/cli/scripts/check-install-smoke.mjs @@ -44,7 +44,7 @@ * 1 - an unresolvable specifier, a boot failure, a pack/install failure, or a vacuous scan */ -import { readdirSync, readFileSync, existsSync, mkdtempSync, mkdirSync, rmSync } from 'node:fs'; +import { readdirSync, readFileSync, existsSync, mkdtempSync, mkdirSync, rmSync, renameSync } from 'node:fs'; import { join, resolve, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { createRequire } from 'node:module'; @@ -54,6 +54,22 @@ import { fileURLToPath } from 'node:url'; const GUARD = 'check-install-smoke'; const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +/** + * The workspace root — a third place `npm pack` may write the tarball. Found by + * walking up to the lockfile rather than counting directory levels, because + * `--pkg` points this script at packages at different depths. + */ +const REPO_ROOT = (() => { + let dir = PKG_ROOT; + for (let i = 0; i < 8; i += 1) { + if (existsSync(join(dir, 'package-lock.json'))) return dir; + const up = dirname(dir); + if (up === dir) break; + dir = up; + } + return resolve(PKG_ROOT, '..', '..', '..'); +})(); + /** Both module systems, because a future build may emit ESM. */ const SPECIFIER_RE = /(?:require\(|from\s*)["'](@beyondnet\/[^"']+)["']/g; @@ -114,8 +130,21 @@ function fail(lines) { process.exit(1); } +/** + * Every child here must be immune to an ambient dry-run. + * + * This guard runs inside `prepublishOnly`, and `npm-release.yml`'s rehearsal mode + * invokes `npm publish --dry-run`, which exports `npm_config_dry_run=true` to that + * script. Inherited, it makes `npm pack` write no tarball and `npm install` install + * nothing — while both still exit 0 — so the guard reported a good artifact as NOT + * installable. Packing and installing into a throwaway directory is this guard's + * measurement, not part of the publish being rehearsed, so the variable is stripped + * for every child rather than at one call site. + */ function run(cmd, args, opts = {}) { - return spawnSync(cmd, args, { encoding: 'utf8', ...opts }); + const env = { ...process.env, ...(opts.env || {}) }; + delete env.npm_config_dry_run; + return spawnSync(cmd, args, { encoding: 'utf8', ...opts, env }); } /** @@ -220,13 +249,69 @@ function main(argv) { if (!spec) { // Pack THIS package. --ignore-scripts so a prepublishOnly that calls this // guard cannot recurse into itself. - const packed = run('npm', ['pack', '--ignore-scripts', '--silent', '--pack-destination', temp], { cwd: pkgRoot }); + // THIS GUARD COULD NOT RUN UNDER THE RELEASE REHEARSAL, AND SAID SO WRONGLY. + // + // `npm-release.yml`'s dry_run mode runs `npm publish --dry-run`, which puts + // `npm_config_dry_run=true` in the environment of `prepublishOnly` — and + // this guard runs there. The nested `npm pack` below INHERITS it, so npm + // printed the tarball name and wrote no file, and the guard reported "this + // artifact is NOT installable" about a perfectly good artifact. Measured, not + // guessed: `npm_config_dry_run=true npm pack --pack-destination ` prints + // the filename and leaves empty, on both npm 10 and npm 11. + // + // So the pack is made immune to an ambient dry-run: the variable is stripped + // from the child environment AND `--dry-run=false` is passed. Packing into a + // temp directory is not the operation the rehearsal is rehearsing, and it + // must happen for the rehearsal to mean anything. + // + // Two earlier explanations were wrong and are recorded so they are not + // re-attempted: it is not that `--pack-destination` is ignored on the runner, + // and not that npm majors disagree about the destination. npm 10 honours it. + // + // The location is still DISCOVERED rather than predicted — snapshot the + // candidate directories, pack, take whatever appeared — and a failure prints + // what each directory actually holds, because a slow CI loop must not be + // spent discovering one more location. + const candidates = [temp, pkgRoot, REPO_ROOT]; + const tgzIn = (dir) => { + try { + return new Set(readdirSync(dir).filter((f) => f.endsWith('.tgz'))); + } catch { + return new Set(); + } + }; + const before = candidates.map(tgzIn); + + const packed = run( + 'npm', + ['pack', '--ignore-scripts', '--silent', '--dry-run=false', '--pack-destination', temp], + { cwd: pkgRoot }, + ); if (packed.status !== 0) { fail([`npm pack failed (${packed.status}):`, String(packed.stderr).trim()]); } - const tarball = String(packed.stdout).trim().split('\n').pop(); - spec = join(temp, tarball); - if (!existsSync(spec)) fail([`npm pack reported "${tarball}" but no such tarball exists in ${temp}.`]); + const reported = String(packed.stdout).trim().split('\n').pop(); + + let found = null; + candidates.forEach((dir, i) => { + if (found) return; + for (const f of tgzIn(dir)) { + if (!before[i].has(f)) { found = { dir, file: f }; return; } + } + }); + + if (!found) { + fail([ + `npm pack reported "${reported}" and no new tarball appeared in any candidate directory:`, + ...candidates.map((d, i) => ` ${d}\n holds: ${[...tgzIn(d)].join(', ') || '(no .tgz)'} · before: ${[...before[i]].join(', ') || '(no .tgz)'}`), + 'npm prints the filename regardless of where it writes, so the name alone proves nothing.', + ]); + } + + spec = join(temp, found.file); + // Move it out of the repository if that is where npm put it, rather than + // leaving it for the root-cleanliness guard to trip over. + if (found.dir !== temp) renameSync(join(found.dir, found.file), spec); } const installDir = join(temp, 'consumer');