diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index f392f48..2e40c38 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "spec-superflow", "description": "8-state workflow engine with 9 collaborative skills. Bridges planning and execution via execution-contract.md. Embedded schema validation, TDD, SDD, code review, systematic debugging, and delta spec sync.", - "version": "0.9.0", + "version": "0.9.1", "source": "./", "author": { "name": "MageByte", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 0018854..408674e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spec-superflow", - "version": "0.9.0", + "version": "0.9.1", "description": "Spec-first workflow: integrates OpenSpec planning engine + Superpowers execution discipline. 9 collaborative skills with embedded TDD, SDD, code review, debugging, and delta spec sync.", "source": "./", "author": { diff --git a/.claude/always/phase-guard.md b/.claude/always/phase-guard.md index a5e2cb9..80160ed 100644 --- a/.claude/always/phase-guard.md +++ b/.claude/always/phase-guard.md @@ -1,3 +1,3 @@ -# spec-superflow v0.9.0 | 阶段: {{state}} | 工作流: {{workflow}} +# spec-superflow v0.9.1 | 阶段: {{state}} | 工作流: {{workflow}} 当前阶段允许的操作由 workflow-start 路由规则定义。 禁止跨越 DP gate 进入下一阶段。变更范围以 execution-contract.md 的 Intent Lock 为准。 diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 35c29b3..1e6b0d6 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "spec-superflow", - "version": "0.9.0", + "version": "0.9.1", "description": "Spec-first workflow that bridges OpenSpec-style planning and Superpowers-style execution discipline.", "author": { "name": "MageByte", diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index acf41e7..747157c 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -5,7 +5,7 @@ }, "metadata": { "description": "Spec-first workflow plugin marketplace for Cursor", - "version": "0.9.0" + "version": "0.9.1" }, "plugins": [ { diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index e93e4d3..cae9cf3 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "spec-superflow", "displayName": "spec-superflow", "description": "Spec-first workflow: integrates OpenSpec planning engine + Superpowers execution discipline. 9 collaborative skills with embedded TDD, SDD, code review, debugging, and delta spec sync.", - "version": "0.9.0", + "version": "0.9.1", "author": { "name": "MageByte", "url": "https://github.com/MageByte-Zero" diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 3d1966b..285983d 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -6,13 +6,13 @@ }, "metadata": { "description": "Spec-first workflow plugins and skills for AI coding agents.", - "version": "0.9.0" + "version": "0.9.1" }, "plugins": [ { "name": "spec-superflow", "description": "Spec-first workflow with planning artifacts, execution contracts, TDD, review gates, systematic debugging, and delta spec sync.", - "version": "0.9.0", + "version": "0.9.1", "source": ".", "author": { "name": "MageByte", diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c57ca..715e7d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,21 @@ The format loosely follows Keep a Changelog. ## [Unreleased] +## [0.9.1] - 2026-07-15 + ### Changed +- **#52 — isolate optional name**: `ssf isolate ` no longer forwards an absent optional name as the literal string `undefined`; explicit names and `--force` retain their existing behavior. +- **#43 — portable skill runtime**: all nine canonical skills no longer require `${CLAUDE_PLUGIN_ROOT}`. Raw marketplace, cache, copy, and symlink loading use a semver-pinned package CLI; local installers rewrite that prefix to their bundled runtime tree. +- **Cross-platform runtime diagnostics**: `ssf doctor` now detects stale root placeholders and missing runtime trees, while version sync and CI consistency checks include all nine canonical skill prefixes. +- **Linked-worktree version hook**: `npm run setup-hooks` now locates Git's shared hooks directory, upgrades stale spec-superflow hooks, and validates the worktree being committed rather than a sibling checkout. - **#45 — Execution-mode recommendation**: DP-4 now runs `ssf execution recommend` to list Inline, Batch Inline, and SDD from task and wave evidence, with a recommendation. Every persisted plan at `/.superpowers/sdd/execution-plan.json` requires explicit `--confirm`; a non-recommended selection records `--acknowledge-recommendation` instead of treating Inline as an override. Named waves, dependencies, and review receipts remain mandatory. +### Added + +- Runtime distribution inventory for all 17 documented platforms plus the ZCODE compatibility installer path. +- `npm run test:raw-mode` packages the current source and proves the canonical runtime works without plugin-root variables or a global `ssf` command. + ## [0.9.0] - 2026-07-11 ### Added diff --git a/GEMINI.md b/GEMINI.md index 8559df1..d770956 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -8,7 +8,7 @@ The workflow is self-contained and does not require OpenSpec or Superpowers at r -# spec-superflow v0.9.0 | 阶段: {{state}} | 工作流: {{workflow}} +# spec-superflow v0.9.1 | 阶段: {{state}} | 工作流: {{workflow}} 当前阶段允许的操作由 workflow-start 路由规则定义。 禁止跨越 DP gate 进入下一阶段。变更范围以 execution-contract.md 的 Intent Lock 为准。 diff --git a/INSTALL.md b/INSTALL.md index dab13dc..7f6f32c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,7 +7,7 @@ - [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) — 规划引擎(Schema 验证、Delta Spec、工件解析) - [obra/superpowers](https://github.com/obra/superpowers) — 执行纪律(TDD 铁律、SDD、系统化调试、代码审查) -当前发布版本:**v0.9.0**。 +当前发布版本:**v0.9.1**。 --- diff --git a/README.md b/README.md index 127a19f..1727fab 100644 --- a/README.md +++ b/README.md @@ -161,8 +161,9 @@ npx spec-superflow list # 或通过 npx 使用 ### 版本 -- 当前版本:`v0.9.0` -- v0.9.0 highlights:支持 Node 20/22、model profiles 只读解析,以及 code-reviewer 的最小性审查;详见 [CHANGELOG.md](CHANGELOG.md) +- 当前版本:`v0.9.1` +- v0.9.1 highlights:DP-4 执行模式推荐、跨 17 个平台的 portable runtime,以及无插件根路径的 raw-package smoke;详见 [CHANGELOG.md](CHANGELOG.md) +- v0.9.0 highlights:支持 Node 20/22、model profiles 只读解析,以及 code-reviewer 的最小性审查 - 自包含插件,不需要运行时安装 OpenSpec 或 Superpowers - 上游来源:[Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec) 和 [obra/superpowers](https://github.com/obra/superpowers) - 版本历史见 [CHANGELOG.md](CHANGELOG.md) diff --git a/docs/.!25091!.DS_Store b/docs/.!25091!.DS_Store deleted file mode 100644 index e69de29..0000000 diff --git a/docs/README_en.md b/docs/README_en.md index 4771e91..e619013 100644 --- a/docs/README_en.md +++ b/docs/README_en.md @@ -126,7 +126,8 @@ npm install -g spec-superflow ### Version -- Current: `v0.9.0` +- Current: `v0.9.1` +- v0.9.1 highlights: DP-4 execution-mode recommendations, a portable runtime across 17 platforms, and a raw-package smoke with no plugin-root variable. - Self-contained — no OpenSpec or Superpowers runtime required - Upstream: [Fission-AI/OpenSpec](https://github.com/Fission-AI/OpenSpec), [obra/superpowers](https://github.com/obra/superpowers) - Changelog: [CHANGELOG.md](../CHANGELOG.md) diff --git a/docs/release-checklist.md b/docs/release-checklist.md index 4d2c090..66edfae 100644 --- a/docs/release-checklist.md +++ b/docs/release-checklist.md @@ -11,6 +11,7 @@ Use this checklist before publishing a new version of `spec-superflow`. - `LICENSE` is present - `ssf version ` covers all manifests (JSON) + documentation (Markdown/shell) - `node scripts/check-version-consistency.mjs` passes (also runs in CI) +- Verify all nine runtime-dependent canonical skills use the exact release version, and local installer output rewrites them to its bundled `scripts/spec-superflow.mjs`. - Verify `.github/plugin/marketplace.json` and `.claude-plugin/marketplace.json` versions match ## Workflow Integrity @@ -46,6 +47,8 @@ For each example in `docs/examples/`: - `node scripts/check-version-consistency.mjs` — exits 0 - `node scripts/spec-superflow.mjs --help` — all subcommands listed - `node scripts/spec-superflow.mjs install-workbuddy --dry-run` — finds all 9 skills and target paths +- `npm run test:raw-mode` — packs the current source and runs a canonical runtime in an empty directory with no plugin-root variables or global `ssf`. +- Run a representative local-installer smoke test. - `spec-superflow.config.json` absence still works (backward compatible defaults) - `package.json` `bin` field points to correct entry script @@ -74,6 +77,7 @@ For each example in `docs/examples/`: ## Publishing Checks +- Release preparation does not authorize `git tag`, `npm publish`, GitHub Release creation, marketplace publication, issue closure, or external issue comments. Perform those only after the maintainer explicitly authorizes publication. - there are no stray `TODO` or `TBD` markers - links and referenced paths are still valid - no local-only junk files are included diff --git a/gemini-extension.json b/gemini-extension.json index d275a54..7c3a048 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "spec-superflow", "description": "Spec-first workflow plugin: clarified requirements → execution contract → disciplined implementation", - "version": "0.9.0", + "version": "0.9.1", "contextFileName": "GEMINI.md" } diff --git a/hooks/session-start b/hooks/session-start index 3f96b98..6a5d55a 100755 --- a/hooks/session-start +++ b/hooks/session-start @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# v0.9.0: conditional injection — detects artifacts, injects workflow-start pointer +# v0.9.1: conditional injection — detects artifacts, injects workflow-start pointer msg="\nYou have spec-superflow installed. Use /spec-superflow:workflow-start ONLY when you detect an active spec-superflow change in the workspace (look for \`.spec-superflow.yaml\`, \`proposal.md\`, \`execution-contract.md\`, or \`specs/\` directories), OR when the user explicitly invokes it by name. For ordinary coding tasks without spec-superflow artifacts, do NOT invoke workflow-start — just handle the request directly.\n" # Three platforms share the same message, only output format differs. diff --git a/llms.txt b/llms.txt index abeb554..6f80139 100644 --- a/llms.txt +++ b/llms.txt @@ -3,7 +3,7 @@ ## Overview spec-superflow is a self-contained workflow integration plugin for Claude Code, Cursor, OpenAI Codex CLI/App, GitHub Copilot CLI, Gemini CLI, OpenCode, WorkBuddy, and Trae. It merges spec-driven planning artifacts (proposal, specs, design, tasks) with disciplined execution guardrails (TDD, review gates, controlled handoff) into one unified workflow. -Current version: v0.9.0. +Current version: v0.9.1. ## Key Documents - README.md: Chinese homepage with full usage guide and FAQ diff --git a/package.json b/package.json index 6d911d6..3b5aaea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "spec-superflow", - "version": "0.9.0", + "version": "0.9.1", "description": "Spec-first workflow: integrates OpenSpec planning engine + Superpowers execution discipline via bridge contract. 9 collaborative skills for multi-agent coding tools.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -12,6 +12,7 @@ "scripts": { "build": "tsc", "test": "node --test tests/e2e.test.mjs tests/lib/*.test.mjs", + "test:raw-mode": "node --test tests/lib/raw-mode-smoke.test.mjs", "validate": "node scripts/validate-artifacts", "version": "node scripts/spec-superflow.mjs version $npm_new_version && node scripts/check-version-consistency.mjs && git add -A", "check-versions": "node scripts/check-version-consistency.mjs", diff --git a/plugin.json b/plugin.json index 2fee18c..53e1fee 100644 --- a/plugin.json +++ b/plugin.json @@ -1,7 +1,7 @@ { "name": "spec-superflow", "description": "Spec-first workflow that bridges OpenSpec-style planning and Superpowers-style execution discipline.", - "version": "0.9.0", + "version": "0.9.1", "author": { "name": "MageByte", "url": "https://github.com/MageByte-Zero" diff --git a/scripts/check-version-consistency.mjs b/scripts/check-version-consistency.mjs index f2e3864..33335f9 100755 --- a/scripts/check-version-consistency.mjs +++ b/scripts/check-version-consistency.mjs @@ -83,6 +83,29 @@ for (const check of TEXT_CHECKS) { } } +const RUNTIME_FILES = [ + 'workflow-start', 'need-explorer', 'spec-writer', 'contract-builder', + 'build-executor', 'code-reviewer', 'bug-investigator', 'release-archivist', 'spec-merger', +].map(skill => `skills/${skill}/SKILL.md`).concat([ + 'skills/build-executor/implementer-prompt.md', + 'skills/build-executor/task-reviewer-prompt.md', + 'skills/code-reviewer/code-reviewer-prompt.md', +]); +for (const file of RUNTIME_FILES) { + const fp = join(ROOT, file); + if (!existsSync(fp)) { + errors.push({ file, found: 'FILE_NOT_FOUND', expected: CANONICAL }); + continue; + } + const versions = [...readFileSync(fp, 'utf8').matchAll(/npx --yes --package spec-superflow@(\d+\.\d+\.\d+) ssf/g)] + .map(match => match[1]); + if (versions.length === 0) { + errors.push({ file, found: 'RUNTIME_PREFIX_NOT_FOUND', expected: CANONICAL }); + } else if (versions.some(version => version !== CANONICAL)) { + errors.push({ file, found: [...new Set(versions)].join(', '), expected: CANONICAL }); + } +} + // ── Report ── if (errors.length === 0) { console.log(`✅ Version consistency check passed — all files at ${CANONICAL}`); diff --git a/scripts/install-cursor.mjs b/scripts/install-cursor.mjs index 0bb6736..3b71cc2 100644 --- a/scripts/install-cursor.mjs +++ b/scripts/install-cursor.mjs @@ -13,6 +13,7 @@ import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { parseArgs } from 'node:util'; +import { shellQuote } from './lib/shell-quote.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const defaultPluginRoot = dirname(__dirname); // repository root when running from clone @@ -21,7 +22,7 @@ const targetRoot = process.cwd(); const GITHUB_REPO = 'MageByte-Zero/spec-superflow'; const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`; -// Directories needed at runtime by skills (referenced via ${CLAUDE_PLUGIN_ROOT}) +// Directories needed by local installer-rewritten runtime commands. const RUNTIME_DIRS = ['scripts', 'docs', 'templates', 'dist', 'hooks']; function ensureDir(dir) { @@ -102,8 +103,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(skillMd, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(skillMd, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(skillMd, content, 'utf-8'); } // Also fix sub-prompt files (implementer-prompt.md, etc.) const subFiles = readdirSync(dst).filter(f => f.endsWith('.md') && f !== 'SKILL.md'); @@ -112,8 +117,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(subPath, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(subPath, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(subPath, content, 'utf-8'); } } diff --git a/scripts/install-git-hooks.mjs b/scripts/install-git-hooks.mjs index 9e3c9bc..82d6334 100644 --- a/scripts/install-git-hooks.mjs +++ b/scripts/install-git-hooks.mjs @@ -6,12 +6,18 @@ // Automatically run post-install so every contributor gets the guard. import { writeFileSync, existsSync, chmodSync, readFileSync } from 'node:fs'; -import { join, dirname } from 'node:path'; +import { join, dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { execFileSync } from 'node:child_process'; const __dirname = dirname(fileURLToPath(import.meta.url)); const ROOT = join(__dirname, '..'); -const HOOKS_DIR = join(ROOT, '.git', 'hooks'); +// Ask Git for the common hooks directory so linked worktrees install correctly. +// `git rev-parse --git-path hooks` may be relative in a normal checkout. +const HOOKS_DIR = resolve(ROOT, execFileSync('git', ['rev-parse', '--git-path', 'hooks'], { + cwd: ROOT, + encoding: 'utf8', +}).trim()); const HOOK_PATH = join(HOOKS_DIR, 'pre-commit'); const HOOK_SCRIPT = `#!/usr/bin/env bash @@ -21,7 +27,7 @@ const HOOK_SCRIPT = `#!/usr/bin/env bash set -euo pipefail -REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" echo "🔍 spec-superflow: checking version consistency..." @@ -41,16 +47,20 @@ if (!existsSync(HOOKS_DIR)) { process.exit(1); } -// Preserve existing hook if it's not ours +// Preserve an unrelated hook, but upgrade an older spec-superflow hook. const MARKER = '# spec-superflow pre-commit hook'; if (existsSync(HOOK_PATH)) { const existing = readFileSync(HOOK_PATH, 'utf-8'); if (existing.includes(MARKER)) { - console.log('✅ Pre-commit hook already installed (spec-superflow).'); - process.exit(0); + if (existing === HOOK_SCRIPT) { + console.log('✅ Pre-commit hook already installed (spec-superflow).'); + process.exit(0); + } + console.log('♻️ Updating existing pre-commit hook (spec-superflow).'); + } else { + console.log('⚠️ Existing pre-commit hook found. Backing up to pre-commit.backup ...'); + writeFileSync(`${HOOK_PATH}.backup`, existing); } - console.log('⚠️ Existing pre-commit hook found. Backing up to pre-commit.backup ...'); - writeFileSync(`${HOOK_PATH}.backup`, existing); } writeFileSync(HOOK_PATH, HOOK_SCRIPT); diff --git a/scripts/install-zcode.mjs b/scripts/install-zcode.mjs index 68167a0..d015840 100644 --- a/scripts/install-zcode.mjs +++ b/scripts/install-zcode.mjs @@ -13,6 +13,7 @@ import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { parseArgs } from 'node:util'; +import { shellQuote } from './lib/shell-quote.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const defaultPluginRoot = dirname(__dirname); // repository root when running from clone @@ -21,7 +22,7 @@ const targetRoot = process.cwd(); const GITHUB_REPO = 'MageByte-Zero/spec-superflow'; const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`; -// Directories needed at runtime by skills (referenced via ${CLAUDE_PLUGIN_ROOT}) +// Directories needed by local installer-rewritten runtime commands. const RUNTIME_DIRS = ['scripts', 'docs', 'templates', 'dist', 'hooks']; function ensureDir(dir) { @@ -102,8 +103,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(skillMd, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(skillMd, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(skillMd, content, 'utf-8'); } // Also fix sub-prompt files (implementer-prompt.md, etc.) const subFiles = readdirSync(dst).filter(f => f.endsWith('.md') && f !== 'SKILL.md'); @@ -112,8 +117,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(subPath, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(subPath, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(subPath, content, 'utf-8'); } } diff --git a/scripts/lib/cmd-doctor.mjs b/scripts/lib/cmd-doctor.mjs index cf02f9a..6bfcb5a 100644 --- a/scripts/lib/cmd-doctor.mjs +++ b/scripts/lib/cmd-doctor.mjs @@ -2,6 +2,12 @@ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'; import { join } from 'node:path'; import { loadConfig } from './config-loader.mjs'; +import { PLATFORM_RUNTIME_INVENTORY } from './platform-runtime-inventory.mjs'; + +const RUNTIME_SKILLS = new Set([ + 'workflow-start', 'need-explorer', 'spec-writer', 'contract-builder', + 'build-executor', 'code-reviewer', 'bug-investigator', 'release-archivist', 'spec-merger', +]); function readJsonIfExists(filePath) { if (!existsSync(filePath)) return null; @@ -95,6 +101,48 @@ function checkSkills(root) { return { pass: false, message: `${withSkillMd.length}/${dirs.length} present, missing SKILL.md: ${missing.join(', ')}` }; } +function localRuntimePaths(content) { + const doubleQuoted = [...content.matchAll(/node\s+"([^"]+\/scripts\/spec-superflow\.mjs)"/g)] + .map(match => match[1]); + const singleQuoted = [...content.matchAll(/node\s+'(.+\/scripts\/spec-superflow\.mjs)'/g)] + .map(match => match[1].split("'\\''").join("'")); + return [...doubleQuoted, ...singleQuoted]; +} + +function checkRuntimeDistribution(root) { + const pkg = readJsonIfExists(join(root, 'package.json')); + const skillsDir = join(root, 'skills'); + if (!pkg?.version || !existsSync(skillsDir)) { + return { pass: false, message: 'runtime distribution cannot be checked: package.json or skills/ is missing; reinstall spec-superflow' }; + } + + const canonicalPrefix = `npx --yes --package spec-superflow@${pkg.version} ssf`; + const issues = []; + for (const name of readdirSync(skillsDir)) { + if (!RUNTIME_SKILLS.has(name)) continue; + const skillPath = join(skillsDir, name, 'SKILL.md'); + if (!existsSync(skillPath)) continue; + const content = readFileSync(skillPath, 'utf8'); + if (/\$\{CLAUDE_PLUGIN_ROOT\}|\$\{PLUGIN_ROOT\}/.test(content)) { + issues.push(`${name}: plugin-root placeholder remains`); + continue; + } + if (content.includes(canonicalPrefix)) continue; + const localPaths = localRuntimePaths(content); + if (localPaths.length > 0) { + if (localPaths.every(existsSync)) continue; + issues.push(`${name}: local runtime tree is missing`); + continue; + } + issues.push(`${name}: runtime prefix or local runtime tree is missing`); + } + + if (issues.length > 0) { + return { pass: false, message: `${issues.join('; ')}; reinstall or upgrade spec-superflow instead of editing skill files manually` }; + } + return { pass: true, message: `${PLATFORM_RUNTIME_INVENTORY.length} platforms inventoried; canonical skills and local runtime are resolvable` }; +} + function checkDist(root) { const distDir = join(root, 'dist'); if (!existsSync(distDir)) { @@ -184,6 +232,7 @@ export async function run(args) { ['Hooks', checkHooks(root)], ['Codex manifest', checkCodexManifest(root)], ['Skills', checkSkills(root)], + ['Runtime distribution', checkRuntimeDistribution(root)], ['dist/', checkDist(root)], ['Node.js', checkNodeVersion()], ['Docs', checkDocs(root)], @@ -215,4 +264,4 @@ export async function run(args) { } } -export { checkVersionConsistency, checkHooks, checkCodexManifest, checkSkills, checkDist, checkRootPluginAuthor, checkNodeVersion, checkDocs }; +export { checkVersionConsistency, checkHooks, checkCodexManifest, checkSkills, checkRuntimeDistribution, checkDist, checkRootPluginAuthor, checkNodeVersion, checkDocs }; diff --git a/scripts/lib/cmd-install-workbuddy.mjs b/scripts/lib/cmd-install-workbuddy.mjs index 918aeac..a3a0b74 100644 --- a/scripts/lib/cmd-install-workbuddy.mjs +++ b/scripts/lib/cmd-install-workbuddy.mjs @@ -23,6 +23,7 @@ import { dirname, join, resolve } from 'node:path'; import { parseArgs } from 'node:util'; import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; +import { shellQuote } from './shell-quote.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const defaultPluginRoot = resolve(__dirname, '..', '..'); // repo root when run from clone @@ -95,8 +96,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(filePath, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(filePath, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(filePath, content, 'utf-8'); } for (const name of entries) { diff --git a/scripts/lib/cmd-isolate.mjs b/scripts/lib/cmd-isolate.mjs index 9eb6993..3a99645 100644 --- a/scripts/lib/cmd-isolate.mjs +++ b/scripts/lib/cmd-isolate.mjs @@ -22,7 +22,8 @@ export async function run(args) { const extra = values.force ? ['--force'] : []; // Literal command ('node') + literal argument array, no shell — same safe form // as cmd-install-*.mjs. execFileSync throws on non-zero exit; propagate its status. - const nodeArgs = [ENSURE, changeDir, changeName]; + const nodeArgs = [ENSURE, changeDir]; + if (changeName !== undefined) nodeArgs.push(changeName); for (const a of extra) nodeArgs.push(a); try { execFileSync('node', nodeArgs, { diff --git a/scripts/lib/cmd-runtime.mjs b/scripts/lib/cmd-runtime.mjs new file mode 100644 index 0000000..d3877d0 --- /dev/null +++ b/scripts/lib/cmd-runtime.mjs @@ -0,0 +1,85 @@ +// ssf runtime — portable, root-independent operations used by canonical skills. +import { readFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const ROOT = join(__dirname, '..', '..'); + +const ASSETS = new Set([ + 'docs/artifact-contract.md', + 'docs/decision-points.md', + 'docs/state-machine.md', + 'skills/build-executor/implementer-prompt.md', + 'skills/build-executor/task-reviewer-prompt.md', + 'templates/design.md', + 'templates/execution-contract.md', + 'templates/proposal.md', + 'templates/spec.md', + 'templates/tasks.md', +]); + +export async function run(args) { + const [operation, ...rest] = args; + + switch (operation) { + case 'check-update': + return runScript('scripts/check-update.mjs', rest); + case 'infer': + return runScript('scripts/infer-workflow.mjs', rest); + case 'guard': + return runScript('scripts/guard/guard.mjs', rest); + case 'config': + return runReadOnlyConfig(rest); + case 'asset': + return readAsset(rest); + default: + return usage(`Unknown runtime operation: ${operation || '(missing)'}`); + } +} + +function runScript(relativeScript, args) { + const result = spawnSync(process.execPath, [join(ROOT, relativeScript), ...args], { + cwd: process.cwd(), + env: process.env, + stdio: 'inherit', + }); + + if (result.error) throw result.error; + if (result.status !== 0) process.exitCode = result.status ?? 1; +} + +async function runReadOnlyConfig(args) { + if (args.includes('--set')) { + return usage('runtime config is read-only; use the main ssf config command to change configuration'); + } + const { run: runConfig } = await import('./cmd-config.mjs'); + await runConfig(args); +} + +function readAsset(args) { + if (args[0] !== 'read' || args.length !== 2) { + return usage('Usage: ssf runtime asset read '); + } + + const assetPath = args[1]; + if (!ASSETS.has(assetPath)) { + return usage(`Asset is not in the runtime allowlist: ${assetPath}`); + } + + process.stdout.write(readFileSync(join(ROOT, assetPath), 'utf8')); +} + +function usage(message) { + console.error(message); + console.error(`Usage: + ssf runtime check-update + ssf runtime infer + ssf runtime guard check [--json] + ssf runtime config [--get |--resolve-model ] + ssf runtime asset read `); + process.exitCode = 2; +} + +export { ASSETS }; diff --git a/scripts/lib/cmd-version.mjs b/scripts/lib/cmd-version.mjs index 07da3f0..2dcc98a 100644 --- a/scripts/lib/cmd-version.mjs +++ b/scripts/lib/cmd-version.mjs @@ -26,6 +26,18 @@ const TEXT_FILES = [ { file: 'llms.txt', pattern: /(Current version: v)0\.\d+\.\d+(\.)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, { file: '.claude/always/phase-guard.md', pattern: /(# spec-superflow v)0\.\d+\.\d+( \|)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, { file: 'GEMINI.md', pattern: /(# spec-superflow v)0\.\d+\.\d+( \|)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/workflow-start/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/need-explorer/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/spec-writer/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/contract-builder/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/build-executor/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/build-executor/implementer-prompt.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/build-executor/task-reviewer-prompt.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/code-reviewer/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/code-reviewer/code-reviewer-prompt.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/bug-investigator/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/release-archivist/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, + { file: 'skills/spec-merger/SKILL.md', pattern: /(npx --yes --package spec-superflow@)0\.\d+\.\d+( ssf)/g, replacement: '$10.%MINOR%.%PATCH%$2' }, ]; function getNestedValue(obj, pathParts) { diff --git a/scripts/lib/install.mjs b/scripts/lib/install.mjs index 8683f5f..caa7667 100644 --- a/scripts/lib/install.mjs +++ b/scripts/lib/install.mjs @@ -5,9 +5,9 @@ // PlatformConfig from ./platforms.mjs. For each platform it: // 1. Resolves the plugin source (latest GitHub release or --local ). // 2. Copies RUNTIME_DIRS (scripts/docs/templates/dist/hooks) to -// /spec-superflow/ so ${CLAUDE_PLUGIN_ROOT}/... resolves. -// 3. Copies skills/ to /skills/ with ${CLAUDE_PLUGIN_ROOT} -// rewritten to the absolute plugin root. +// /spec-superflow/ so local runtime commands resolve. +// 3. Copies skills/ to /skills/ with portable package commands +// rewritten to the bundled runtime. // 4. Writes a phase-guard rule file to the platform's rules directory // (when the platform has one) so the workflow entry rule is auto-included. // @@ -21,6 +21,7 @@ import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; import { parseArgs } from 'node:util'; import { getPlatform, rulesTargetDir, phaseGuardFileName } from './platforms.mjs'; +import { shellQuote } from './shell-quote.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const defaultPluginRoot = resolve(__dirname, '..', '..'); // repo root when run from clone @@ -28,7 +29,7 @@ const defaultPluginRoot = resolve(__dirname, '..', '..'); // repo root when run const GITHUB_REPO = 'MageByte-Zero/spec-superflow'; const GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_REPO}/releases/latest`; -// Directories needed at runtime by skills (referenced via ${CLAUDE_PLUGIN_ROOT}). +// Directories needed by local installer-rewritten runtime commands. const RUNTIME_DIRS = ['scripts', 'docs', 'templates', 'dist', 'hooks']; function ensureDir(dir) { @@ -76,8 +77,8 @@ async function copyDir(src, dst) { } /** - * Copy skills to target, replacing ${CLAUDE_PLUGIN_ROOT} with the actual - * plugin root path so skill instructions resolve in the target platform. + * Copy skills to target, retaining legacy root replacement and rewriting the + * portable package command to the target's bundled runtime. */ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { // Clean old skill directories before copying. @@ -101,8 +102,12 @@ async function copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs) { let content = readFileSync(filePath, 'utf-8'); if (content.includes('${CLAUDE_PLUGIN_ROOT}')) { content = content.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginRootAbs); - writeFileSync(filePath, content, 'utf-8'); } + content = content.replace( + /npx --yes --package spec-superflow@\d+\.\d+\.\d+ ssf/g, + `node ${shellQuote(join(pluginRootAbs, 'scripts', 'spec-superflow.mjs'))}`, + ); + writeFileSync(filePath, content, 'utf-8'); } for (const name of entries) { @@ -225,7 +230,7 @@ export async function installPlatform(platformId, opts = {}) { } } - // 2. Copy skills to /skills/ with CLAUDE_PLUGIN_ROOT replaced + // 2. Copy skills to /skills/ with portable commands rewritten const count = await copySkillsWithRoot(sourceSkills, targetSkills, pluginRootAbs); console.log(` skills/ → ${targetSkills} (${count} skills, paths rewritten)`); diff --git a/scripts/lib/platform-runtime-inventory.mjs b/scripts/lib/platform-runtime-inventory.mjs new file mode 100644 index 0000000..072bd1c --- /dev/null +++ b/scripts/lib/platform-runtime-inventory.mjs @@ -0,0 +1,27 @@ +// Runtime distribution contract for every platform claimed in the public docs. +// Modes are intentionally host-agnostic: tests validate the final artifact, +// rather than assuming that every loader injects a plugin-root environment. +export const PLATFORM_RUNTIME_INVENTORY = [ + { id: 'claude-code', modes: ['native-root', 'canonical-fallback'] }, + { id: 'cursor', modes: ['native-root', 'installer-rewrite'] }, + { id: 'codex-cli', modes: ['native-root', 'canonical-fallback'] }, + { id: 'codex-app', modes: ['native-root', 'canonical-fallback'] }, + { id: 'copilot-cli', modes: ['native-root', 'canonical-fallback'] }, + { id: 'gemini-cli', modes: ['native-root', 'canonical-fallback'] }, + { id: 'opencode', modes: ['canonical-fallback'] }, + { id: 'workbuddy', modes: ['native-root', 'installer-rewrite'] }, + { id: 'trae', modes: ['native-root', 'canonical-fallback'] }, + { id: 'cline', modes: ['installer-rewrite'] }, + { id: 'kiro', modes: ['installer-rewrite'] }, + { id: 'windsurf', modes: ['installer-rewrite'] }, + { id: 'qwen', modes: ['installer-rewrite'] }, + { id: 'amazon-q', modes: ['installer-rewrite'] }, + { id: 'roocode', modes: ['installer-rewrite'] }, + { id: 'continue', modes: ['installer-rewrite'] }, + { id: 'pi', modes: ['installer-rewrite'] }, +]; + +export const ZCODE_COMPATIBILITY_PATH = { + id: 'zcode', + modes: ['installer-rewrite'], +}; diff --git a/scripts/lib/shell-quote.mjs b/scripts/lib/shell-quote.mjs new file mode 100644 index 0000000..5b004a8 --- /dev/null +++ b/scripts/lib/shell-quote.mjs @@ -0,0 +1,4 @@ +// Quote one value for a POSIX shell command embedded in a skill instruction. +export function shellQuote(value) { + return `'${value.replace(/'/g, "'\\''")}'`; +} diff --git a/scripts/spec-superflow.mjs b/scripts/spec-superflow.mjs index 5e02f41..b753e33 100755 --- a/scripts/spec-superflow.mjs +++ b/scripts/spec-superflow.mjs @@ -18,6 +18,7 @@ const COMMANDS = { handoff: () => import('./lib/cmd-handoff.mjs'), isolate: () => import('./lib/cmd-isolate.mjs'), execution: () => import('./lib/cmd-execution.mjs'), + runtime: () => import('./lib/cmd-runtime.mjs'), 'install-cursor': () => import('./lib/cmd-install-cursor.mjs'), 'install-workbuddy': () => import('./lib/cmd-install-workbuddy.mjs'), 'install-cline': () => import('./lib/cmd-install-cline.mjs'), @@ -70,6 +71,12 @@ Commands: Upgrade inline/batch to SDD, or replan existing SDD waves, as a new revision execution review --wave --base --head --report --verdict pass|fail Record one review receipt for a planned wave + runtime check-update Run a portable update check for canonical skills + runtime infer Infer workflow mode without a plugin-root path + runtime guard ... Run a portable phase-transition guard + runtime config ... Read effective configuration (writes are rejected) + runtime asset read + Read one allowlisted package asset for a skill install-cursor Deploy skills/scripts/docs to .cursor/ (local Cursor setup) install-workbuddy Deploy skills to WorkBuddy marketplace and enable them install-cline Deploy to .cline/ + .clinerules/ (Cline) diff --git a/skills/bug-investigator/SKILL.md b/skills/bug-investigator/SKILL.md index 03474e8..1cdd064 100644 --- a/skills/bug-investigator/SKILL.md +++ b/skills/bug-investigator/SKILL.md @@ -49,7 +49,7 @@ Scientific method: form a single hypothesis ("I think X is the root cause becaus ### DP-5: Debug Escalation (3+ Failures) -3+ failed fixes = architectural problem. Each fix revealing new problems elsewhere = wrong architecture. Record: `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_5_result `. Discuss with user before attempting more fixes. +3+ failed fixes = architectural problem. Each fix revealing new problems elsewhere = wrong architecture. Record: `npx --yes --package spec-superflow@0.9.1 ssf state set dp_5_result `. Discuss with user before attempting more fixes. ## Red Flags — Return to Phase 1 diff --git a/skills/build-executor/SKILL.md b/skills/build-executor/SKILL.md index c55808e..b66c17c 100644 --- a/skills/build-executor/SKILL.md +++ b/skills/build-executor/SKILL.md @@ -11,18 +11,18 @@ Controls the implementation phase. Uses `execution-contract.md` as the workflow Read: `execution-contract.md`, `tasks.md`, relevant `specs/`, relevant `design.md`. (Skip contract/spec requirements when workflow is `tweak`.) -Check workflow mode first: `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state get workflow`. If `tweak` → direct edit mode. If `hotfix` or `full` → standard contract-first discipline. +Check workflow mode first: `npx --yes --package spec-superflow@0.9.1 ssf state get workflow`. If `tweak` → direct edit mode. If `hotfix` or `full` → standard contract-first discipline. Branch/worktree preflight before ANY implementation edit (mandatory — do not skip): 1. Run the isolation check: ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" isolate + npx --yes --package spec-superflow@0.9.1 ssf isolate ``` This script enforces git isolation: if you are on `main`/`master` it creates a git worktree (preferred) or a new branch, and exits non-zero if it cannot and you have not approved `--force`. -2. If `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" isolate` exits non-zero: STOP. Do not edit `main`/`master` in place. - Ask the user for explicit approval (and re-run with `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" isolate --force` +2. If `npx --yes --package spec-superflow@0.9.1 ssf isolate` exits non-zero: STOP. Do not edit `main`/`master` in place. + Ask the user for explicit approval (and re-run with `npx --yes --package spec-superflow@0.9.1 ssf isolate --force` only after they approve). 3. If it succeeds, report the chosen branch/worktree and make all implementation edits there. @@ -48,15 +48,15 @@ Return to `specifying` or `bridging` if: new behavior appears, interfaces change For `full`/`hotfix`, generate proposed waves from the approved contract, then use the recommendation as a decision aid rather than silently defaulting a mode: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" execution recommend \ +npx --yes --package spec-superflow@0.9.1 ssf execution recommend \ --wave ::[:] --json # Show every available mode, the observed facts, and the recommendation to the user. # The command writes a receipt tied to the artifacts, contract, and waves. After the user chooses, record that explicit confirmation: -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" execution plan \ +npx --yes --package spec-superflow@0.9.1 ssf execution plan \ --mode --confirm --reason "user-selected execution mode" \ --wave ::[:] # Add --acknowledge-recommendation when the selection differs from the recommendation. -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" execution show --json +npx --yes --package spec-superflow@0.9.1 ssf execution show --json ``` The optional fourth `--wave` segment names prerequisite wave IDs. `execution show --json` reports `current`, plus each wave's `depends_on`, `receipt`, `blockers`, `retryable`, and `eligible` status. A wave with `retryable: true` has a current `fail` receipt and is eligible only for its focused repair and re-review; its dependents remain blocked until its replacement `pass` receipt. Report the saved plan revision, selected mode, ordered waves, dependencies, and whether every `parallel` wave can actually be dispatched concurrently on the current platform. If concurrency is unavailable, state the capability and reason plainly; retain the planned `parallel` strategy and do not silently execute it as a serial or Batch Inline plan. @@ -69,7 +69,7 @@ The recommendation uses task count, configured `execution.inlineThreshold`, and | **Inline** | Recommended for a single sequential task; always available for a user-confirmed choice | | **Batch Inline** | Recommended for a bounded sequential batch; it remains serial and is never presented as parallel | -Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `execution recommend` and use `ssf execution revise --confirm`; it creates a new revision and invalidates receipts from the prior revision. +Do not transition to `executing` until `execution show` reports `current: true` and the phase guard passes. A revised plan must repeat `npx --yes --package spec-superflow@0.9.1 ssf execution recommend` and use `npx --yes --package spec-superflow@0.9.1 ssf execution revise --confirm`; it creates a new revision and invalidates receipts from the prior revision. ## Batch Inline Execution @@ -84,21 +84,21 @@ Boundaries: if any task touches >1 module, involves schema/API/config changes, o For full/hotfix by default. Dispatch according to the persisted plan, review each planned wave, and run a final broad review after all waves. ### Planned-Wave Loop -1. Read the current plan with `ssf execution show --json`; only waves shown with `current: true` and `eligible: true` may start. A `retryable: true` wave may only be repaired and re-reviewed; do not dispatch its dependents until its replacement receipt is `pass`. The CLI encodes dependencies in `--wave ::[:]` and rejects a review receipt for a wave whose prerequisites lack current `pass` receipts. +1. Read the current plan with `npx --yes --package spec-superflow@0.9.1 ssf execution show --json`; only waves shown with `current: true` and `eligible: true` may start. A `retryable: true` wave may only be repaired and re-reviewed; do not dispatch its dependents until its replacement receipt is `pass`. The CLI encodes dependencies in `--wave ::[:]` and rejects a review receipt for a wave whose prerequisites lack current `pass` receipts. 2. A `parallel` wave may dispatch independent tasks simultaneously only when the platform supports concurrent dispatch. If it does not, disclose the unavailable capability and execute the same wave one task at a time without changing its stored strategy. 3. A `serial` wave dispatches one task at a time in listed order. 4. After every wave, write a non-empty persisted regular-file review report (separate from the implementer's report), then record exactly one receipt that names that review report: ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" execution review \ + npx --yes --package spec-superflow@0.9.1 ssf execution review \ --wave --base --head --report --verdict ``` Do not begin a dependent wave until its predecessor receipt is `pass`. 5. Critical/Important findings require a `fail` receipt, a focused repair, re-review, then a replacement `pass` receipt. Never advance or close with a missing or failed receipt. ### Per-Task Loop -1. **Dispatch implementer**: Use `${CLAUDE_PLUGIN_ROOT}/skills/build-executor/implementer-prompt.md` template. Extract task brief with `scripts/task-brief PLAN_FILE N`. Include: where task fits, brief path, interfaces from prior tasks, report file path. +1. **Dispatch implementer**: Load the template with `npx --yes --package spec-superflow@0.9.1 ssf runtime asset read skills/build-executor/implementer-prompt.md`. Extract task brief with `scripts/task-brief PLAN_FILE N`. Include: where task fits, brief path, interfaces from prior tasks, report file path. 2. **Handle response**: DONE → generate review package + dispatch reviewer. DONE_WITH_CONCERNS → assess. NEEDS_CONTEXT → provide context. BLOCKED → re-dispatch with better model or escalate. -3. **Review**: Use `skills/build-executor/task-reviewer-prompt.md`. Reviewer returns spec compliance + code quality verdicts with the wave ID, git range, report path, and `pass`/`fail` receipt command. +3. **Review**: Load `npx --yes --package spec-superflow@0.9.1 ssf runtime asset read skills/build-executor/task-reviewer-prompt.md`. Reviewer returns spec compliance + code quality verdicts with the wave ID, git range, report path, and `pass`/`fail` receipt command. 4. **Fix**: If Critical or Important issues, write the `fail` receipt, dispatch fix subagent, re-review, and write the replacement `pass` receipt. 5. **Mark complete**: Append to `.superpowers/sdd/progress.md`: `Task N: complete (commits .., review clean)` @@ -106,7 +106,7 @@ For full/hotfix by default. Dispatch according to the persisted plan, review eac Use the configured profile that matches the task role. Resolve it before dispatch: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" config --resolve-model +npx --yes --package spec-superflow@0.9.1 ssf runtime config --resolve-model ``` | Profile | Role | @@ -119,11 +119,11 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" config --resolve-model < For platforms whose dispatch supports a `model` field, explicitly pass the resolved `model` value. If the result is `configured: false`, automatic selection is unavailable: do not invent a provider model and do not bypass the existing requirement to specify `model` explicitly. Resolution only reads configuration; it does not switch models. ### Progress Ledger -Track in `.superpowers/sdd/progress.md`. Check for existing ledger — completed tasks are done. After each batch: `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set batches_completed `. +Track in `.superpowers/sdd/progress.md`. Check for existing ledger — completed tasks are done. After each batch: `npx --yes --package spec-superflow@0.9.1 ssf state set batches_completed `. ## Inline Execution Mode -Only after a user-confirmed `inline` selection is recorded by `ssf execution plan --confirm`; a non-recommended selection also records `--acknowledge-recommendation`. Executes in the current session and still writes one review receipt per planned wave. +Only after a user-confirmed `inline` selection is recorded by `npx --yes --package spec-superflow@0.9.1 ssf execution plan --confirm`; a non-recommended selection also records `--acknowledge-recommendation`. Executes in the current session and still writes one review receipt per planned wave. Per-task: extract brief → write failing test → confirm failure → implement → confirm green → checkpoint review (done-when criteria, SHALL/MUST verification) → commit → save a task-level recovery checkpoint when another task remains → append to progress ledger. @@ -131,7 +131,7 @@ After a task is committed and reviewed, when another task remains, save the recovery context with real evidence: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" checkpoint save \ +npx --yes --package spec-superflow@0.9.1 ssf checkpoint save \ --task --next "" --completed "" \ --verification "" --review "" \ --risk "" --commit-start --commit-end @@ -139,7 +139,7 @@ node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" checkpoint save dp_5_result ""` + timestamp. +DP-4 is written by `npx --yes --package spec-superflow@0.9.1 ssf execution plan`; do not write it with raw `state set`. +DP-5 (debug escalation): `npx --yes --package spec-superflow@0.9.1 ssf state set dp_5_result ""` + timestamp. ## Completion Standard diff --git a/skills/build-executor/implementer-prompt.md b/skills/build-executor/implementer-prompt.md index 46d0bc5..fee6849 100644 --- a/skills/build-executor/implementer-prompt.md +++ b/skills/build-executor/implementer-prompt.md @@ -22,7 +22,7 @@ Subagent (general-purpose): ## Planned Wave You are assigned to planned wave [WAVE_ID] with strategy [WAVE_STRATEGY]. - Read `ssf execution show --json` before editing. Do not start + Read `npx --yes --package spec-superflow@0.9.1 ssf execution show --json` before editing. Do not start unless all declared dependencies have `pass` review receipts. A `parallel` label permits concurrent dispatch only when the controller confirms the platform supports it; never change the saved wave strategy yourself. diff --git a/skills/build-executor/task-reviewer-prompt.md b/skills/build-executor/task-reviewer-prompt.md index c038339..14d5a94 100644 --- a/skills/build-executor/task-reviewer-prompt.md +++ b/skills/build-executor/task-reviewer-prompt.md @@ -146,7 +146,7 @@ Subagent (general-purpose): command for the controller: ```bash - ssf execution review --wave [WAVE_ID] --base [BASE_SHA] --head [HEAD_SHA] --report [REVIEW_REPORT_FILE] --verdict + npx --yes --package spec-superflow@0.9.1 ssf execution review --wave [WAVE_ID] --base [BASE_SHA] --head [HEAD_SHA] --report [REVIEW_REPORT_FILE] --verdict ``` Use `fail` for any Critical/Important finding. A repair must be re-reviewed diff --git a/skills/code-reviewer/SKILL.md b/skills/code-reviewer/SKILL.md index 79355bf..1fb6634 100644 --- a/skills/code-reviewer/SKILL.md +++ b/skills/code-reviewer/SKILL.md @@ -16,7 +16,7 @@ Two responsibilities: requesting review (dispatching a reviewer subagent) and re 1. Get SHAs: `BASE_SHA=$(git rev-parse HEAD~1)` and `HEAD_SHA=$(git rev-parse HEAD)` 2. Dispatch `general-purpose` subagent using template at `skills/code-reviewer/code-reviewer-prompt.md` 3. Fill placeholders: `[DESCRIPTION]` (what was built), `[PLAN_OR_REQUIREMENTS]` (contract/spec reference), `[BASE_SHA]`, `[HEAD_SHA]`, `[WAVE_ID]`, and a distinct `[REVIEW_REPORT_FILE]`. -4. Require the reviewer to write a non-empty persisted review report at `[REVIEW_REPORT_FILE]`, then record that exact path in the wave receipt: `ssf execution review --wave --base --head --report --verdict `. +4. Require the reviewer to write a non-empty persisted review report at `[REVIEW_REPORT_FILE]`, then record that exact path in the wave receipt: `npx --yes --package spec-superflow@0.9.1 ssf execution review --wave --base --head --report --verdict `. 5. Act on feedback: Critical/Important findings require a `fail` receipt, focused repair, re-review, and replacement `pass` receipt before a dependent wave or closing can proceed. Note Minor for later, push back with reasoning if reviewer is wrong. ### Minimality And Scope @@ -72,7 +72,7 @@ Suggestion breaks existing functionality, reviewer lacks context, violates YAGNI | Performative agreement | State requirement or just act | | Blind implementation | Verify against codebase first | | Batch without testing | One at a time, test each | -| Proceeding without a wave receipt | Record `pass`/`fail` via `ssf execution review` before the next dependent wave | +| Proceeding without a wave receipt | Record `pass`/`fail` via `npx --yes --package spec-superflow@0.9.1 ssf execution review` before the next dependent wave | | Assuming reviewer is right | Check if breaks things | | Avoiding pushback | Technical correctness > comfort | | Partial implementation | Clarify all items first | diff --git a/skills/code-reviewer/code-reviewer-prompt.md b/skills/code-reviewer/code-reviewer-prompt.md index 007289e..11f8886 100644 --- a/skills/code-reviewer/code-reviewer-prompt.md +++ b/skills/code-reviewer/code-reviewer-prompt.md @@ -93,7 +93,7 @@ Subagent (general-purpose): report path. End with the exact receipt command: ```bash - ssf execution review --wave [WAVE_ID] --base [BASE_SHA] --head [HEAD_SHA] --report [REVIEW_REPORT_FILE] --verdict + npx --yes --package spec-superflow@0.9.1 ssf execution review --wave [WAVE_ID] --base [BASE_SHA] --head [HEAD_SHA] --report [REVIEW_REPORT_FILE] --verdict ``` Use `fail` when any Critical or Important finding remains. A repair needs diff --git a/skills/contract-builder/SKILL.md b/skills/contract-builder/SKILL.md index 62597ab..79d7ea6 100644 --- a/skills/contract-builder/SKILL.md +++ b/skills/contract-builder/SKILL.md @@ -5,9 +5,9 @@ description: Convert approved planning artifacts into an execution contract. Inv # Contract Builder -Converts planning artifacts into a single execution handshake: `execution-contract.md`. Use `${CLAUDE_PLUGIN_ROOT}/templates/execution-contract.md` as the baseline structure. +Converts planning artifacts into a single execution handshake: `execution-contract.md`. Load the baseline with `npx --yes --package spec-superflow@0.9.1 ssf runtime asset read templates/execution-contract.md`. -Read before generating: `proposal.md`, `specs/`, `design.md`, `tasks.md`, `docs/artifact-contract.md`. +Read before generating: `proposal.md`, `specs/`, `design.md`, `tasks.md`, then load `docs/artifact-contract.md` with `npx --yes --package spec-superflow@0.9.1 ssf runtime asset read docs/artifact-contract.md`. ## Artifact Mapping @@ -35,8 +35,8 @@ Must make obvious: approved behavior, out-of-scope, constraints, batches, test o After drafting: summarize handoff rules, identify ambiguity, flag unmapped requirements, ask user to approve explicitly. After approval: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_3_result "approved: " -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_3_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_3_result "approved: " +npx --yes --package spec-superflow@0.9.1 ssf state set dp_3_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` DP-3 is a hard gate — no implementation without this record. @@ -57,9 +57,9 @@ Generate minimal contract: Intent Lock (one sentence), Task List (numbered), App ## Post-Generation -Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state init ` to create `.spec-superflow.yaml` with hashes. +Run `npx --yes --package spec-superflow@0.9.1 ssf state init ` to create `.spec-superflow.yaml` with hashes. -For hotfix, after writing the minimal contract, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state init ` or `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state rebuild ` so `contract_hash` is recorded. DP-3 remains mandatory before build. +For hotfix, after writing the minimal contract, run `npx --yes --package spec-superflow@0.9.1 ssf state init ` or `npx --yes --package spec-superflow@0.9.1 ssf state rebuild ` so `contract_hash` is recorded. DP-3 remains mandatory before build. ## Exception Handling diff --git a/skills/need-explorer/SKILL.md b/skills/need-explorer/SKILL.md index 25e957a..ac7fd38 100644 --- a/skills/need-explorer/SKILL.md +++ b/skills/need-explorer/SKILL.md @@ -37,8 +37,8 @@ Restate what you heard: "Here's what I'm hearing: [problem, scope, non-goals, su After user confirms the summary: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_1_result "confirmed: " -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_1_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_1_result "confirmed: " +npx --yes --package spec-superflow@0.9.1 ssf state set dp_1_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` DP-1 confirms scope, non-goals, and success criteria before artifact creation. diff --git a/skills/release-archivist/SKILL.md b/skills/release-archivist/SKILL.md index 54b52a6..582fef0 100644 --- a/skills/release-archivist/SKILL.md +++ b/skills/release-archivist/SKILL.md @@ -60,12 +60,12 @@ Check for files modified outside scope fence, new dependencies not in design. Un - Scope added without artifact updates? - Unresolved blockers or known risks? - Delta specs exist that need merging? -- Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" audit ` — include `decision-point-audit.md` in archive +- Run `npx --yes --package spec-superflow@0.9.1 ssf audit ` — include `decision-point-audit.md` in archive ### DP-6 (Verification Outcome) ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_6_result ": " -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_6_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_6_result ": " +npx --yes --package spec-superflow@0.9.1 ssf state set dp_6_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` If FAIL, do NOT proceed to DP-7. Route back or ask about abandonment. @@ -73,13 +73,13 @@ After recording a PASS outcome, also record it as the verification gate so the `executing → closing` transition is allowed (the guard accepts either `test_result: pass` or a `dp_6_result` starting with `pass`): ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set test_result pass +npx --yes --package spec-superflow@0.9.1 ssf state set test_result pass ``` ### DP-7 (Archive Confirmation) ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_7_result "confirmed: " -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_7_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_7_result "confirmed: " +npx --yes --package spec-superflow@0.9.1 ssf state set dp_7_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` Verify DP-0 through DP-6 are recorded before DP-7. @@ -89,7 +89,7 @@ If implementation diverged from the contract, return to `bridging` before closur ## Post-Verification -Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state transition closing`. If delta specs exist, route to `spec-merger`. +Run `npx --yes --package spec-superflow@0.9.1 ssf state transition closing`. If delta specs exist, route to `spec-merger`. ## Lightweight Closure (hotfix/tweak) @@ -98,6 +98,6 @@ Verify files exist and are non-empty, run `node --check` on code files, skip 5-s ## Exception Handling - **Parse failures**: Report exact file and section -- **Missing files**: If audit can't generate, run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" audit` manually +- **Missing files**: If audit can't generate, run `npx --yes --package spec-superflow@0.9.1 ssf audit` manually - **User interruption**: Re-run verification from the beginning on resume - **DP gaps**: Flag missing DPs during DP-6; ask user whether to proceed or return diff --git a/skills/spec-merger/SKILL.md b/skills/spec-merger/SKILL.md index 6c86461..62f9271 100644 --- a/skills/spec-merger/SKILL.md +++ b/skills/spec-merger/SKILL.md @@ -10,7 +10,7 @@ After a change completes, delta specs (ADDED/MODIFIED/REMOVED/RENAMED) must be m ## Pre-Flight Checks ### Conflict Detection -Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" sync `. If conflicts are detected (same requirement modified by multiple changes), present the conflict list to the user for resolution order. +Run `npx --yes --package spec-superflow@0.9.1 ssf sync `. If conflicts are detected (same requirement modified by multiple changes), present the conflict list to the user for resolution order. ### Abandoned Change Guard Check if the change is `abandoned`. If so → STOP: "Abandoned changes cannot be synced. Delta specs are preserved for reference but must not be merged." @@ -55,7 +55,7 @@ Output sync report table: Capability, ADDED/MODIFIED/REMOVED/RENAMED counts, Sta 2. Change folder (including deltas) remains for traceability. 3. Record that merging is complete so the `executing → closing` guard allows closure: ```bash - node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set spec_merged true + npx --yes --package spec-superflow@0.9.1 ssf state set spec_merged true ``` (If the change had no delta sections, still set `spec_merged true` — there was nothing to merge.) diff --git a/skills/spec-writer/SKILL.md b/skills/spec-writer/SKILL.md index 2e0f73c..2ac72f6 100644 --- a/skills/spec-writer/SKILL.md +++ b/skills/spec-writer/SKILL.md @@ -13,7 +13,7 @@ Read `.spec-superflow.yaml` (especially `dp_0_decisions`, `dp_0_confirmed`) and ## Config Check -Run: `bash "${CLAUDE_PLUGIN_ROOT}/scripts/get-config" artifacts.order` — generate in configured order (default: proposal → specs → design → tasks). Run with `artifacts.skip` — skip any listed artifacts. +Run: `npx --yes --package spec-superflow@0.9.1 ssf runtime config --get artifacts.order` — generate in configured order (default: proposal → specs → design → tasks). Run with `artifacts.skip` — skip any listed artifacts. ## Artifact Roles @@ -73,8 +73,8 @@ Generate one at a time. Confirm each before next. This prevents scope drift — Present summary of all 4 artifacts (2-3 sentences each). Ask user for adjustments. After approval: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_2_result "approved: " -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_2_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_2_result "approved: " +npx --yes --package spec-superflow@0.9.1 ssf state set dp_2_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` ## Handoff Rule diff --git a/skills/workflow-start/SKILL.md b/skills/workflow-start/SKILL.md index edc5911..88a1149 100644 --- a/skills/workflow-start/SKILL.md +++ b/skills/workflow-start/SKILL.md @@ -15,14 +15,14 @@ Do NOT invoke for: general coding tasks outside spec-superflow changes, casual q ## States -`exploring` → `specifying` → `bridging` → `approved-for-build` → `executing` → `closing`, with `debugging` side-path from `executing`, and `abandoned` as terminal. Read `docs/state-machine.md` if transition is ambiguous. +`exploring` → `specifying` → `bridging` → `approved-for-build` → `executing` → `closing`, with `debugging` side-path from `executing`, and `abandoned` as terminal. If a transition is ambiguous, run `npx --yes --package spec-superflow@0.9.1 ssf runtime asset read docs/state-machine.md`. ## Initialization -1. **Update check**: Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/check-update.mjs"`. Exit 0 → continue. Exit 1 → non-blocking upgrade reminder. Exit 2 → skip. +1. **Update check**: Run `npx --yes --package spec-superflow@0.9.1 ssf runtime check-update`. Exit 0 → continue. Exit 1 → non-blocking upgrade reminder. Exit 2 → skip. 2. **Inspect change folder**: Check for `proposal.md`, `specs/`, `design.md`, `tasks.md`, `execution-contract.md`. Answer: Is the change fuzzy? Artifacts missing/unstable? Contract exist? User approved contract? Execution in progress or blocked? In verification/wrap-up? -3. **Overlay recovery scan**: Run `ssf handoff list --json` and `ssf checkpoint list --json`. A `result-ready` handoff requires explicit review and `ssf handoff resolve` before resuming the affected work. An `active` handoff is non-blocking side work. Show a non-stale checkpoint as recovery context; show a stale checkpoint only as historical evidence. -4. **Execution-control recovery scan**: For `approved-for-build`, `executing`, `debugging`, or `closing`, run `ssf execution show --json`. Treat only `current: true` plus `waves[].eligible: true` as permission to start a wave; report plan revision, mode, next eligible wave, and every wave's receipt/blockers. A missing, invalid, or stale plan blocks implementation and routes to `build-executor`; do not infer progress from chat history. +3. **Overlay recovery scan**: Run `npx --yes --package spec-superflow@0.9.1 ssf handoff list --json` and `npx --yes --package spec-superflow@0.9.1 ssf checkpoint list --json`. A `result-ready` handoff requires explicit review and `npx --yes --package spec-superflow@0.9.1 ssf handoff resolve` before resuming the affected work. An `active` handoff is non-blocking side work. Show a non-stale checkpoint as recovery context; show a stale checkpoint only as historical evidence. +4. **Execution-control recovery scan**: For `approved-for-build`, `executing`, `debugging`, or `closing`, run `npx --yes --package spec-superflow@0.9.1 ssf execution show --json`. Treat only `current: true` plus `waves[].eligible: true` as permission to start a wave; report plan revision, mode, next eligible wave, and every wave's receipt/blockers. A missing, invalid, or stale plan blocks implementation and routes to `build-executor`; do not infer progress from chat history. ## DP-0: User Confirmation Gate @@ -32,17 +32,17 @@ Ask: change name + one-sentence intent, known constraints, related optimizations After confirmation: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_0_decisions "" -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_0_result confirmed -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_0_confirmed true -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) +npx --yes --package spec-superflow@0.9.1 ssf state set dp_0_decisions "" +npx --yes --package spec-superflow@0.9.1 ssf state set dp_0_result confirmed +npx --yes --package spec-superflow@0.9.1 ssf state set dp_0_confirmed true +npx --yes --package spec-superflow@0.9.1 ssf state set dp_0_timestamp $(date -u +%Y-%m-%dT%H:%M:%SZ) ``` Config-aware routing: check `artifacts.order` and `artifacts.skip` from project config. ## Mode Detection -If workflow is `auto`/`null`/unset: run `node "${CLAUDE_PLUGIN_ROOT}/scripts/infer-workflow.mjs" `. Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **tweak** (≤4 tasks, config/doc only), **full** (anything larger). Persist with `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state set workflow `. +If workflow is `auto`/`null`/unset: run `npx --yes --package spec-superflow@0.9.1 ssf runtime infer `. Inference: **hotfix** (≤2 tasks, ≤2 files, no schema/API/new modules), **tweak** (≤4 tasks, config/doc only), **full** (anything larger). Persist with `npx --yes --package spec-superflow@0.9.1 ssf state set workflow `. Validate mode against artifact content. If hotfix/tweak criteria not met → upgrade to `full` and output reason. Don't overwrite explicit mode unless user asks. @@ -52,19 +52,19 @@ Validate mode against artifact content. If hotfix/tweak criteria not met → upg Change is fuzzy, scope unclear, comparing options, no stable change name. ### Route to spec-writer -Guard: `node "${CLAUDE_PLUGIN_ROOT}/scripts/guard/guard.mjs" check exploring specifying --json` → fail = BLOCK. User knows what they want, artifacts missing/incomplete. +Guard: `npx --yes --package spec-superflow@0.9.1 ssf runtime guard check exploring specifying --json` → fail = BLOCK. User knows what they want, artifacts missing/incomplete. ### Route to contract-builder Guard: `... check specifying bridging --json` → fail = BLOCK. Artifacts exist, implementation requested, contract missing/stale. Include `DP-3: 契约批准`. ### Route to build-executor -Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `ssf execution recommend [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `ssf execution plan --mode --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `ssf execution show --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check approved-for-build executing --json` → fail = BLOCK. +Contract exists and approved, contract matches artifacts. Include `DP-4: 执行模式选择`: propose waves, run `npx --yes --package spec-superflow@0.9.1 ssf execution recommend [--wave ...]`, show the user every available mode plus evidence and the recommendation, then obtain a clear selection. The command saves a current receipt; before the first implementation edit, `build-executor` must run `npx --yes --package spec-superflow@0.9.1 ssf execution plan --mode --confirm ...` (and `--acknowledge-recommendation` when the selected mode differs from the recommendation) using matching artifacts, contract, and waves, then `npx --yes --package spec-superflow@0.9.1 ssf execution show --json`; report the saved revision, selected mode, recommendation alignment, ordered waves, and actual concurrent-dispatch capability. A revision must repeat recommend and confirmation. Do not transition to `executing` until `show` reports `current: true`; then run `... check approved-for-build executing --json` → fail = BLOCK. ### Route to bug-investigator Execution hit blockage: test failure, unexpected behavior, build error, task cannot proceed. After debugging, route back to build-executor. ### Route to code-reviewer -The current planned wave is implemented and ready for spec-compliance + code-quality verification. A reviewer must write an `ssf execution review --wave --base --head --report --verdict ` receipt before any dependent wave or closing transition. +The current planned wave is implemented and ready for spec-compliance + code-quality verification. A reviewer must write an `npx --yes --package spec-superflow@0.9.1 ssf execution review --wave --base --head --report --verdict ` receipt before any dependent wave or closing transition. ### Route to release-archivist Guard: `... check executing closing --json` → fail = BLOCK. Implementation complete, verification complete/nearly complete. Include `DP-7: 归档确认`. @@ -83,10 +83,10 @@ uncertainty. Do not create a prototype handoff or enter a prototype worktree until the user confirms. After confirmation: ```bash -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" handoff create \ +npx --yes --package spec-superflow@0.9.1 ssf handoff create \ --type prototype --objective "" \ --expected-output "" --acceptance "" -node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" isolate prototype- +npx --yes --package spec-superflow@0.9.1 ssf isolate prototype- ``` Never suggest or enter this route automatically for backend, CLI, configuration, @@ -97,7 +97,7 @@ work. - **Hotfix**: Route to contract-builder (minimal), skip need-explorer + spec-writer, guard check `exploring bridging --workflow hotfix`, then `bridging -> approved-for-build`, after DP-3 → build-executor (recommend, show, and confirm an execution mode), after → release-archivist (lightweight). Hotfix may skip `proposal.md`, `design.md`, `tasks.md`, and `specs/`, but it still requires a fresh minimal `execution-contract.md`, DP-3 approval, and a current execution plan before build - **Tweak**: Route to build-executor (direct edit), skip need-explorer + spec-writer + contract-builder, guard check `exploring approved-for-build --workflow tweak`, after → release-archivist (lightweight) -Post-transition: 💡 `node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" inject ` to update phase-guard artifacts. +Post-transition: 💡 `npx --yes --package spec-superflow@0.9.1 ssf inject ` to update phase-guard artifacts. ## Staleness Detection @@ -112,7 +112,7 @@ Use content inspection, not timestamps. ## Guardrails - No implementation before planning artifacts or contract exist -- No implementation for full/hotfix without a current `ssf execution plan`; no state transition based on an unverified DP-4 string +- No implementation for full/hotfix without a current `npx --yes --package spec-superflow@0.9.1 ssf execution plan`; no state transition based on an unverified DP-4 string - No "continue" without state inspection - No implementation past stale contract - No implementation past bug without investigation diff --git a/tests/lib/cmd-doctor.test.mjs b/tests/lib/cmd-doctor.test.mjs index 828133b..cf3eacf 100644 --- a/tests/lib/cmd-doctor.test.mjs +++ b/tests/lib/cmd-doctor.test.mjs @@ -362,3 +362,89 @@ describe('cmd-doctor: checkDocs()', () => { assert.equal(result.pass, true); }); }); + +describe('cmd-doctor: checkRuntimeDistribution()', () => { + let checkRuntimeDistribution; + + before(async () => { + const modulePath = join(process.cwd(), 'scripts/lib/cmd-doctor.mjs'); + const mod = await import(modulePath); + checkRuntimeDistribution = mod.checkRuntimeDistribution; + }); + + it('passes for the canonical package and reports platform coverage', () => { + const result = checkRuntimeDistribution(process.cwd()); + + assert.equal(result.pass, true, result.message); + assert.match(result.message, /17 platforms/); + }); + + it('reports an unrewritten plugin-root placeholder as an upgrade issue', () => { + const root = mkdtempSync(join(tmpdir(), 'ssf-doctor-runtime-')); + try { + mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true }); + writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })); + writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'), + 'node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" state get demo'); + + const result = checkRuntimeDistribution(root); + assert.equal(result.pass, false); + assert.match(result.message, /plugin-root placeholder|reinstall/i); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('checks code-reviewer as a portable runtime skill', () => { + const root = mkdtempSync(join(tmpdir(), 'ssf-doctor-code-reviewer-')); + try { + mkdirSync(join(root, 'skills', 'code-reviewer'), { recursive: true }); + writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })); + writeFileSync(join(root, 'skills', 'code-reviewer', 'SKILL.md'), + 'node "${CLAUDE_PLUGIN_ROOT}/scripts/spec-superflow.mjs" execution review demo'); + + const result = checkRuntimeDistribution(root); + assert.equal(result.pass, false); + assert.match(result.message, /code-reviewer: plugin-root placeholder remains/); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('rejects a local command whose referenced runtime file is missing', () => { + const root = mkdtempSync(join(tmpdir(), 'ssf-doctor-stale-runtime-')); + try { + mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true }); + mkdirSync(join(root, 'scripts'), { recursive: true }); + writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })); + writeFileSync(join(root, 'scripts', 'spec-superflow.mjs'), '// unrelated bundled CLI'); + writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'), + 'node "/tmp/missing-runtime/scripts/spec-superflow.mjs" state get demo'); + + const result = checkRuntimeDistribution(root); + assert.equal(result.pass, false); + assert.match(result.message, /workflow-start: local runtime tree is missing/); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); + + it('accepts a POSIX-escaped local runtime path containing a single quote', () => { + const root = mkdtempSync(join(tmpdir(), "ssf-doctor-quote-'")); + try { + const localCli = join(root, 'scripts', 'spec-superflow.mjs'); + mkdirSync(join(root, 'skills', 'workflow-start'), { recursive: true }); + mkdirSync(join(root, 'scripts'), { recursive: true }); + writeFileSync(join(root, 'package.json'), JSON.stringify({ version: '1.2.3' })); + writeFileSync(localCli, '// bundled CLI'); + const quotedCli = `'${localCli.replace(/'/g, "'\\''")}'`; + writeFileSync(join(root, 'skills', 'workflow-start', 'SKILL.md'), + `node ${quotedCli} state get demo`); + + const result = checkRuntimeDistribution(root); + assert.equal(result.pass, true, result.message); + } finally { + rmSync(root, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/lib/cmd-install-zcode.test.mjs b/tests/lib/cmd-install-zcode.test.mjs index 840be78..2b857b5 100644 --- a/tests/lib/cmd-install-zcode.test.mjs +++ b/tests/lib/cmd-install-zcode.test.mjs @@ -30,13 +30,13 @@ describe('BUG/#29: install-zcode deploys skills', () => { assert.equal(existsSync(join(cwd, '.zcode', 'rules', 'phase-guard.mdc')), true, 'phase guard should be written'); }); - it('SHALL give contract-builder the deployed execution-contract template path', () => { + it('SHALL give contract-builder the portable execution-contract asset command', () => { execSync(`node ${CLI} install-zcode --local "${ROOT}"`, { cwd, stdio: 'pipe', timeout: 60000 }); const templatePath = join(cwd, '.zcode', 'spec-superflow', 'templates', 'execution-contract.md'); const contractBuilder = readFileSync(join(cwd, '.zcode', 'skills', 'contract-builder', 'SKILL.md'), 'utf-8'); assert.equal(existsSync(templatePath), true, 'deployed template should exist'); - assert.equal(contractBuilder.includes(templatePath), true, 'contract-builder should reference the deployed template'); + assert.match(contractBuilder, /runtime asset read templates\/execution-contract\.md/); }); }); diff --git a/tests/lib/cmd-isolate.test.mjs b/tests/lib/cmd-isolate.test.mjs new file mode 100644 index 0000000..d2bd3d5 --- /dev/null +++ b/tests/lib/cmd-isolate.test.mjs @@ -0,0 +1,68 @@ +// Regression tests for #52: optional isolate names must not become the +// literal string "undefined" when cmd-isolate invokes ensure-branch. +import { afterEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +const ROOT = process.cwd(); +const CLI = join(ROOT, 'scripts', 'spec-superflow.mjs'); +const ENSURE = join(ROOT, 'scripts', 'ensure-branch.mjs'); +const tempDirs = []; + +afterEach(() => { + while (tempDirs.length > 0) { + rmSync(tempDirs.pop(), { recursive: true, force: true }); + } +}); + +function runIsolate(args) { + const tempDir = mkdtempSync(join(tmpdir(), 'ssf-cmd-isolate-')); + tempDirs.push(tempDir); + const binDir = join(tempDir, 'bin'); + const capture = join(tempDir, 'argv.txt'); + const fakeNode = join(binDir, 'node'); + + mkdirSync(binDir); + writeFileSync(fakeNode, '#!/bin/sh\nprintf "%s\\n" "$@" > "$SSF_CAPTURE"\n', 'utf8'); + chmodSync(fakeNode, 0o755); + + const result = spawnSync(process.execPath, [CLI, 'isolate', ...args], { + encoding: 'utf8', + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH}`, + SSF_CAPTURE: capture, + }, + }); + + return { + result, + argv: readFileSync(capture, 'utf8').trimEnd().split('\n'), + }; +} + +describe('cmd-isolate optional change name (#52)', () => { + it('omits the optional name from ensure-branch argv when the caller omits it', () => { + const { result, argv } = runIsolate(['/tmp/changes/demo']); + + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(argv, [ENSURE, '/tmp/changes/demo']); + }); + + it('forwards an explicitly supplied name unchanged', () => { + const { result, argv } = runIsolate(['/tmp/changes/demo', 'runtime-fix']); + + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(argv, [ENSURE, '/tmp/changes/demo', 'runtime-fix']); + }); + + it('forwards force without manufacturing an optional name', () => { + const { result, argv } = runIsolate(['/tmp/changes/demo', '--force']); + + assert.equal(result.status, 0, result.stderr); + assert.deepEqual(argv, [ENSURE, '/tmp/changes/demo', '--force']); + }); +}); diff --git a/tests/lib/cmd-runtime.test.mjs b/tests/lib/cmd-runtime.test.mjs new file mode 100644 index 0000000..03d4d72 --- /dev/null +++ b/tests/lib/cmd-runtime.test.mjs @@ -0,0 +1,83 @@ +// Runtime facade regression tests. These execute the public CLI so the +// canonical-skill protocol is tested without relying on a plugin-root env var. +import { afterEach, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { join, delimiter } from 'node:path'; +import { tmpdir } from 'node:os'; + +const ROOT = process.cwd(); +const CLI = join(ROOT, 'scripts', 'spec-superflow.mjs'); +const tempDirs = []; + +afterEach(() => { + while (tempDirs.length > 0) { + rmSync(tempDirs.pop(), { recursive: true, force: true }); + } +}); + +function runRuntime(args, options = {}) { + return spawnSync(process.execPath, [CLI, 'runtime', ...args], { + cwd: options.cwd || ROOT, + encoding: 'utf8', + env: { ...process.env, ...options.env }, + }); +} + +describe('ssf runtime', () => { + it('reads approved assets from the package root without a plugin-root environment variable', () => { + const result = runRuntime(['asset', 'read', 'templates/execution-contract.md'], { + env: { CLAUDE_PLUGIN_ROOT: '', PLUGIN_ROOT: '' }, + }); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /# 执行合同/); + }); + + it('rejects traversal and assets outside the allowlist', () => { + const result = runRuntime(['asset', 'read', '../package.json']); + + assert.equal(result.status, 2); + assert.match(result.stderr, /allowed|allowlist/i); + }); + + it('dispatches infer and guard while preserving their established output', () => { + const infer = runRuntime(['infer', '/tmp/ssf-runtime-missing-change']); + const guard = runRuntime(['guard', 'check', 'docs/examples/add-dark-mode', 'exploring', 'specifying', '--json']); + + assert.equal(infer.status, 0, infer.stderr); + assert.deepEqual(JSON.parse(infer.stdout).mode, 'full'); + assert.equal(guard.status, 0, guard.stderr); + assert.equal(JSON.parse(guard.stdout).pass, true); + }); + + it('provides read-only config and rejects config writes', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'ssf-runtime-config-')); + tempDirs.push(tempDir); + const read = runRuntime(['config', '--get', 'execution.inlineThreshold'], { cwd: tempDir }); + const write = runRuntime(['config', '--set', 'execution.inlineThreshold=99'], { cwd: tempDir }); + + assert.equal(read.status, 0, read.stderr); + assert.equal(read.stdout.trim(), '3'); + assert.equal(write.status, 2); + assert.match(write.stderr, /read-only|--set/i); + }); + + it('uses the existing update-check result without requiring a global ssf command', () => { + const tempDir = mkdtempSync(join(tmpdir(), 'ssf-runtime-update-')); + tempDirs.push(tempDir); + const binDir = join(tempDir, 'bin'); + const fakeNpm = join(binDir, 'npm'); + mkdirSync(binDir); + writeFileSync(fakeNpm, '#!/bin/sh\nprintf "0.9.0\\n"\n', 'utf8'); + chmodSync(fakeNpm, 0o755); + + const result = runRuntime(['check-update'], { + env: { PATH: `${binDir}${delimiter}${process.env.PATH}` }, + }); + + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /up to date/i); + }); +}); diff --git a/tests/lib/install-git-hooks.test.mjs b/tests/lib/install-git-hooks.test.mjs new file mode 100644 index 0000000..3e312c5 --- /dev/null +++ b/tests/lib/install-git-hooks.test.mjs @@ -0,0 +1,20 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const source = readFileSync(join(process.cwd(), 'scripts', 'install-git-hooks.mjs'), 'utf8'); + +describe('install-git-hooks', () => { + it('resolves the hook directory and checked repository from Git at runtime', () => { + assert.match(source, /git rev-parse --git-path hooks/); + assert.match(source, /git rev-parse --show-toplevel/); + assert.doesNotMatch(source, /REPO_ROOT="\$\(cd "\$\(dirname "\$0"\)\/\.\.\/\.\." && pwd\)"/); + }); + + it('upgrades an existing spec-superflow hook when its generated content is stale', () => { + assert.match(source, /existing === HOOK_SCRIPT/); + assert.match(source, /Updating existing pre-commit hook/); + assert.doesNotMatch(source, /if \(existing\.includes\(MARKER\)\) \{\s*console\.log\([^\n]+\);\s*process\.exit\(0\);/s); + }); +}); diff --git a/tests/lib/platform-runtime-distribution.test.mjs b/tests/lib/platform-runtime-distribution.test.mjs new file mode 100644 index 0000000..01ed2a9 --- /dev/null +++ b/tests/lib/platform-runtime-distribution.test.mjs @@ -0,0 +1,150 @@ +// Canonical skill protocol checks. Later distribution waves extend this file +// with installer and platform inventory fixtures. +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync, realpathSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { PLATFORM_RUNTIME_INVENTORY, ZCODE_COMPATIBILITY_PATH } from '../../scripts/lib/platform-runtime-inventory.mjs'; + +const ROOT = process.cwd(); +const VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version; +const PREFIX = `npx --yes --package spec-superflow@${VERSION} ssf`; +const CLI = join(ROOT, 'scripts', 'spec-superflow.mjs'); +const RUNTIME_SKILLS = [ + 'workflow-start', + 'need-explorer', + 'spec-writer', + 'contract-builder', + 'build-executor', + 'code-reviewer', + 'bug-investigator', + 'release-archivist', + 'spec-merger', +]; + +function skill(name) { + return readFileSync(join(ROOT, 'skills', name, 'SKILL.md'), 'utf8'); +} + +describe('canonical skill runtime protocol', () => { + it('uses the exact package-version prefix for every runtime-dependent skill', () => { + for (const name of RUNTIME_SKILLS) { + const content = skill(name); + assert.match(content, new RegExp(PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), + `${name} should use the canonical portable prefix`); + assert.doesNotMatch(content, /\$\{CLAUDE_PLUGIN_ROOT\}|\$\{PLUGIN_ROOT\}/, + `${name} should not require a host plugin-root variable`); + } + }); + + it('uses allowlisted runtime assets for build-executor prompts', () => { + const content = skill('build-executor'); + + assert.match(content, /runtime asset read skills\/build-executor\/implementer-prompt\.md/); + assert.match(content, /runtime asset read skills\/build-executor\/task-reviewer-prompt\.md/); + }); + + it('does not leave bare ssf commands in runtime instructions or reviewer templates', () => { + const files = [ + ...RUNTIME_SKILLS.map(name => join(ROOT, 'skills', name, 'SKILL.md')), + join(ROOT, 'skills', 'build-executor', 'implementer-prompt.md'), + join(ROOT, 'skills', 'build-executor', 'task-reviewer-prompt.md'), + join(ROOT, 'skills', 'code-reviewer', 'code-reviewer-prompt.md'), + ]; + + for (const file of files) { + const content = readFileSync(file, 'utf8'); + assert.doesNotMatch(content, /`ssf\s+(?:handoff|checkpoint|execution|state|runtime|validate|doctor|config|version)\b/, + `${file} contains a bare ssf command`); + assert.doesNotMatch(content, /^\s*ssf\s+(?:handoff|checkpoint|execution|state|runtime|validate|doctor|config|version)\b/m, + `${file} contains an unprefixed ssf command line`); + } + }); +}); + +describe('local runtime deployment', () => { + it('rewrites the canonical prefix to ZCODE\'s installed runtime tree', () => { + const target = mkdtempSync(join(tmpdir(), 'ssf-zcode-runtime-')); + try { + execFileSync(process.execPath, [CLI, 'install-zcode', '--local', ROOT], { + cwd: target, + stdio: 'pipe', + }); + + const pluginRoot = join(target, '.zcode', 'spec-superflow'); + const content = readFileSync(join(target, '.zcode', 'skills', 'workflow-start', 'SKILL.md'), 'utf8'); + const localPrefix = `node '${join(realpathSync(pluginRoot), 'scripts', 'spec-superflow.mjs')}'`; + + assert.match(content, new RegExp(localPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); + assert.doesNotMatch(content, new RegExp(PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); + } finally { + rmSync(target, { recursive: true, force: true }); + } + }); + + it('emits a shell-literal local runtime command when the target path contains a dollar sign', () => { + const target = mkdtempSync(join(tmpdir(), 'ssf-$runtime-')); + try { + execFileSync(process.execPath, [CLI, 'install-zcode', '--local', ROOT], { + cwd: target, + stdio: 'pipe', + }); + + const content = readFileSync(join(target, '.zcode', 'skills', 'workflow-start', 'SKILL.md'), 'utf8'); + const command = content.match(/node '[^']+' runtime asset read docs\/state-machine\.md/); + assert.ok(command, 'workflow-start should contain a shell-literal local runtime command'); + + const output = execFileSync('sh', ['-c', command[0]], { cwd: target, encoding: 'utf8' }); + assert.match(output, /State Machine/); + } finally { + rmSync(target, { recursive: true, force: true }); + } + }); +}); + +describe('platform runtime inventory', () => { + it('lists every documented platform and the ZCODE compatibility path', () => { + const ids = new Set(PLATFORM_RUNTIME_INVENTORY.map(platform => platform.id)); + + assert.deepEqual(ids, new Set([ + 'claude-code', 'cursor', 'codex-cli', 'codex-app', 'copilot-cli', 'gemini-cli', + 'opencode', 'workbuddy', 'trae', 'cline', 'kiro', 'windsurf', 'qwen', + 'amazon-q', 'roocode', 'continue', 'pi', + ])); + assert.equal(ZCODE_COMPATIBILITY_PATH.id, 'zcode'); + }); + + it('gives each documented distribution an explicit, testable runtime mode', () => { + const validModes = new Set(['native-root', 'installer-rewrite', 'canonical-fallback']); + + for (const platform of PLATFORM_RUNTIME_INVENTORY) { + assert.ok(platform.modes.length > 0, `${platform.id} needs at least one distribution mode`); + for (const mode of platform.modes) assert.ok(validModes.has(mode), `${platform.id}: ${mode}`); + } + assert.deepEqual(ZCODE_COMPATIBILITY_PATH.modes, ['installer-rewrite']); + assert.ok(idsWithMode('codex-cli', 'canonical-fallback')); + assert.ok(idsWithMode('codex-app', 'canonical-fallback')); + assert.ok(idsWithMode('opencode', 'canonical-fallback')); + }); +}); + +describe('runtime version synchronization', () => { + it('includes code-reviewer when a release version is dry-run', () => { + const output = execFileSync(process.execPath, [CLI, 'version', '0.9.2', '--dry-run'], { + cwd: ROOT, + encoding: 'utf8', + }); + + assert.match(output, /skills\/code-reviewer\/SKILL\.md: version string updated/); + assert.match(output, /skills\/build-executor\/implementer-prompt\.md: version string updated/); + assert.match(output, /skills\/build-executor\/task-reviewer-prompt\.md: version string updated/); + assert.match(output, /skills\/code-reviewer\/code-reviewer-prompt\.md: version string updated/); + }); +}); + +function idsWithMode(id, mode) { + return PLATFORM_RUNTIME_INVENTORY.find(platform => platform.id === id)?.modes.includes(mode); +} diff --git a/tests/lib/raw-mode-smoke.test.mjs b/tests/lib/raw-mode-smoke.test.mjs new file mode 100644 index 0000000..6292698 --- /dev/null +++ b/tests/lib/raw-mode-smoke.test.mjs @@ -0,0 +1,42 @@ +// Raw-package smoke: canonical skills must work without host plugin-root variables +// or a globally installed `ssf` binary. +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; +import { spawnSync } from 'node:child_process'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +const ROOT = process.cwd(); + +function run(command, args, options) { + const result = spawnSync(command, args, { encoding: 'utf8', ...options }); + assert.equal(result.status, 0, `${command} ${args.join(' ')}\n${result.stderr}`); + return result.stdout; +} + +describe('raw-package runtime smoke', () => { + it('runs a packed canonical runtime with no plugin-root variables or global ssf', () => { + const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')); + assert.equal(pkg.scripts['test:raw-mode'], 'node --test tests/lib/raw-mode-smoke.test.mjs'); + + const tempRoot = mkdtempSync(join(tmpdir(), 'ssf-raw-mode-')); + try { + const packJson = run('npm', ['pack', '--json', '--pack-destination', tempRoot], { cwd: ROOT }); + const [{ filename }] = JSON.parse(packJson); + const env = { ...process.env }; + delete env.CLAUDE_PLUGIN_ROOT; + delete env.PLUGIN_ROOT; + delete env.CODEX_HOME; + + const output = run('npx', [ + '--yes', '--package', join(tempRoot, filename), 'ssf', + 'runtime', 'asset', 'read', 'docs/state-machine.md', + ], { cwd: tempRoot, env }); + + assert.match(output, /State Machine/); + } finally { + rmSync(tempRoot, { recursive: true, force: true }); + } + }); +});