From 4d961872cf4bb5830174aa2b2e3a79f777514755 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Wed, 14 Oct 2026 23:44:21 +0300 Subject: [PATCH] chore: make OpenSpec toolchain explicit and diagnosable (WealthLedger/specs#4) --- README.md | 10 ++++++---- openspec/.cli-version | 1 + package.json | 1 + scripts/check-openspec.cjs | 39 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 openspec/.cli-version create mode 100644 scripts/check-openspec.cjs diff --git a/README.md b/README.md index 17e921c..e344d9b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ OpenSpec применяется после явного решения Change Ow Для native validation из этого репозитория: ```bash +npm run check:openspec openspec schema validate wealth-ledger-sdd --verbose openspec schema validate wealth-ledger-sdd-v2 --verbose openspec status --change --store wealth-ledger-specs @@ -25,10 +26,11 @@ openspec validate --type change --strict --store wealth-ledger-specs openspec validate --specs --strict --store wealth-ledger-specs ``` -Версия OpenSpec CLI не закреплена отдельным файлом: при обновлении проверяйте обе -schema, затронутые changes и canonical specs, фиксируя `openspec --version` вместе -с результатами проверок. Structural validation не заменяет semantic verification -и independent read-only review перед archive. +OpenSpec CLI ставится глобально и не закрепляется в `package.json`: его транзитивная +зависимость `braces` имеет advisory без исправления в 3.x. Ожидаемая версия хранится +в `openspec/.cli-version`; `npm run check:openspec` проверяет Node из `.nvmrc`, версию +CLI и доступность store, и при расхождении выводит команду исправления. Structural +validation не заменяет semantic verification и independent read-only review перед archive. Node задаётся собственной `.nvmrc`; после `nvm use --silent` выполните `npm ci`. `npm run verify` проверяет diff. OpenSpec validation остаётся отдельной проверкой diff --git a/openspec/.cli-version b/openspec/.cli-version new file mode 100644 index 0000000..b50dd27 --- /dev/null +++ b/openspec/.cli-version @@ -0,0 +1 @@ +1.13.1 diff --git a/package.json b/package.json index 7ad2ecd..fe680be 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "scripts": { "verify": "git diff --check", + "check:openspec": "node scripts/check-openspec.cjs", "release:preview": "release-it --dry-run --ci", "release:prepare": "release-it --ci", "commitlint": "commitlint", diff --git a/scripts/check-openspec.cjs b/scripts/check-openspec.cjs new file mode 100644 index 0000000..0a92e8a --- /dev/null +++ b/scripts/check-openspec.cjs @@ -0,0 +1,39 @@ +const assert = require("node:assert/strict"); +const { execFileSync } = require("node:child_process"); +const { readFileSync } = require("node:fs"); +const path = require("node:path"); + +// OpenSpec CLI is installed globally (no npm pin: its transitive braces advisory has no 3.x fix). +// This check fails fast with an actionable message instead of silently using another version. +const root = path.resolve(__dirname, ".."); +const expectedCli = readFileSync(path.join(root, "openspec/.cli-version"), "utf8").trim(); +const expectedNode = readFileSync(path.join(root, ".nvmrc"), "utf8").trim(); +const store = "wealth-ledger-specs"; + +const run = (command, args) => + execFileSync(command, args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim(); + +assert.equal( + process.version.replace(/^v/, ""), expectedNode, + `Node ${process.version} differs from .nvmrc ${expectedNode}. Run: nvm use --silent`, +); + +let cliVersion; +try { + cliVersion = run("openspec", ["--version"]); +} catch { + assert.fail(`openspec CLI not found. Install it globally: npm install -g @fission-ai/openspec@${expectedCli}`); +} +assert.equal( + cliVersion, expectedCli, + `openspec ${cliVersion} differs from openspec/.cli-version ${expectedCli}. Reinstall: npm install -g @fission-ai/openspec@${expectedCli}`, +); + +// Store presence is checked by the CLI itself: an unknown store must fail, not fall back silently. +try { + run("openspec", ["validate", "--specs", "--store", store]); +} catch (error) { + assert.fail(`openspec store '${store}' is not usable: ${error.stderr || error.stdout || error.message}`); +} + +console.log(`openspec ${cliVersion} on Node ${expectedNode}: toolchain matches`);