@specship/specship-win32-arm64 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/dist/.claude-plugin/plugin.json +15 -3
- package/lib/dist/activation/cheatsheet.d.ts +10 -7
- package/lib/dist/activation/cheatsheet.d.ts.map +1 -1
- package/lib/dist/activation/cheatsheet.js +21 -5
- package/lib/dist/activation/cheatsheet.js.map +1 -1
- package/lib/dist/activation/hook-output.d.ts +75 -2
- package/lib/dist/activation/hook-output.d.ts.map +1 -1
- package/lib/dist/activation/hook-output.js +114 -3
- package/lib/dist/activation/hook-output.js.map +1 -1
- package/lib/dist/activation/search-intercept.d.ts +20 -7
- package/lib/dist/activation/search-intercept.d.ts.map +1 -1
- package/lib/dist/activation/search-intercept.js +27 -5
- package/lib/dist/activation/search-intercept.js.map +1 -1
- package/lib/dist/activation/steering.d.ts +7 -4
- package/lib/dist/activation/steering.d.ts.map +1 -1
- package/lib/dist/activation/steering.js +10 -8
- package/lib/dist/activation/steering.js.map +1 -1
- package/lib/dist/agents/detect.d.ts +32 -0
- package/lib/dist/agents/detect.d.ts.map +1 -0
- package/lib/dist/agents/detect.js +54 -0
- package/lib/dist/agents/detect.js.map +1 -0
- package/lib/dist/agents/recent-project.d.ts +18 -0
- package/lib/dist/agents/recent-project.d.ts.map +1 -0
- package/lib/dist/agents/recent-project.js +92 -0
- package/lib/dist/agents/recent-project.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.d.ts +23 -0
- package/lib/dist/analytics/canonical-tools.d.ts.map +1 -0
- package/lib/dist/analytics/canonical-tools.js +36 -0
- package/lib/dist/analytics/canonical-tools.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.json +16 -0
- package/lib/dist/analytics/specship-impact.d.ts +3 -2
- package/lib/dist/analytics/specship-impact.d.ts.map +1 -1
- package/lib/dist/analytics/specship-impact.js +6 -4
- package/lib/dist/analytics/specship-impact.js.map +1 -1
- package/lib/dist/bin/specship.js +252 -170
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/bin/uninstall.js +2 -2
- package/lib/dist/bin/uninstall.js.map +1 -1
- package/lib/dist/claudemd/audit.d.ts +12 -3
- package/lib/dist/claudemd/audit.d.ts.map +1 -1
- package/lib/dist/claudemd/audit.js +65 -22
- package/lib/dist/claudemd/audit.js.map +1 -1
- package/lib/dist/commands/specship/spec.md +16 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +18 -4
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/schema.sql +8 -0
- package/lib/dist/db/spec-queries.d.ts +13 -1
- package/lib/dist/db/spec-queries.d.ts.map +1 -1
- package/lib/dist/db/spec-queries.js +21 -0
- package/lib/dist/db/spec-queries.js.map +1 -1
- package/lib/dist/design-md/check.d.ts +51 -0
- package/lib/dist/design-md/check.d.ts.map +1 -0
- package/lib/dist/design-md/check.js +335 -0
- package/lib/dist/design-md/check.js.map +1 -0
- package/lib/dist/design-md/css.d.ts +40 -0
- package/lib/dist/design-md/css.d.ts.map +1 -0
- package/lib/dist/design-md/css.js +224 -0
- package/lib/dist/design-md/css.js.map +1 -0
- package/lib/dist/design-md/extract.d.ts +84 -0
- package/lib/dist/design-md/extract.d.ts.map +1 -0
- package/lib/dist/design-md/extract.js +595 -0
- package/lib/dist/design-md/extract.js.map +1 -0
- package/lib/dist/design-md/maintain.d.ts +45 -0
- package/lib/dist/design-md/maintain.d.ts.map +1 -0
- package/lib/dist/design-md/maintain.js +127 -0
- package/lib/dist/design-md/maintain.js.map +1 -0
- package/lib/dist/design-md/render.d.ts +28 -0
- package/lib/dist/design-md/render.d.ts.map +1 -0
- package/lib/dist/design-md/render.js +178 -0
- package/lib/dist/design-md/render.js.map +1 -0
- package/lib/dist/design-md/upkeep.d.ts +48 -0
- package/lib/dist/design-md/upkeep.d.ts.map +1 -0
- package/lib/dist/design-md/upkeep.js +169 -0
- package/lib/dist/design-md/upkeep.js.map +1 -0
- package/lib/dist/design-md/verify.d.ts +65 -0
- package/lib/dist/design-md/verify.d.ts.map +1 -0
- package/lib/dist/design-md/verify.js +311 -0
- package/lib/dist/design-md/verify.js.map +1 -0
- package/lib/dist/enforce/changed-files.d.ts +7 -0
- package/lib/dist/enforce/changed-files.d.ts.map +1 -1
- package/lib/dist/enforce/changed-files.js +17 -0
- package/lib/dist/enforce/changed-files.js.map +1 -1
- package/lib/dist/enforce/enforce.d.ts +35 -1
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +66 -2
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +50 -4
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +105 -13
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/parse-warnings.d.ts +16 -0
- package/lib/dist/extraction/parse-warnings.d.ts.map +1 -0
- package/lib/dist/extraction/parse-warnings.js +30 -0
- package/lib/dist/extraction/parse-warnings.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +27 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +168 -1
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/health/smoke-check.js +1 -1
- package/lib/dist/health/smoke-check.js.map +1 -1
- package/lib/dist/hooks/hooks.json +16 -1
- package/lib/dist/index.d.ts +63 -3
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +235 -9
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/agent-record.d.ts +80 -0
- package/lib/dist/installer/agent-record.d.ts.map +1 -0
- package/lib/dist/installer/agent-record.js +351 -0
- package/lib/dist/installer/agent-record.js.map +1 -0
- package/lib/dist/installer/auto-migrate.d.ts +56 -0
- package/lib/dist/installer/auto-migrate.d.ts.map +1 -0
- package/lib/dist/installer/auto-migrate.js +328 -0
- package/lib/dist/installer/auto-migrate.js.map +1 -0
- package/lib/dist/installer/index.d.ts +37 -8
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +156 -46
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/install-defaults.d.ts +4 -3
- package/lib/dist/installer/install-defaults.d.ts.map +1 -1
- package/lib/dist/installer/install-defaults.js +6 -5
- package/lib/dist/installer/install-defaults.js.map +1 -1
- package/lib/dist/installer/{config-writer.d.ts → targets/claude-config-writer.d.ts} +1 -1
- package/lib/dist/installer/targets/claude-config-writer.d.ts.map +1 -0
- package/lib/dist/installer/{config-writer.js → targets/claude-config-writer.js} +7 -6
- package/lib/dist/installer/targets/claude-config-writer.js.map +1 -0
- package/lib/dist/installer/targets/claude-projects.d.ts +9 -0
- package/lib/dist/installer/targets/claude-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/claude-projects.js +151 -0
- package/lib/dist/installer/targets/claude-projects.js.map +1 -0
- package/lib/dist/installer/targets/claude.d.ts +29 -2
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +44 -20
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/feature-parity.d.ts +28 -0
- package/lib/dist/installer/targets/feature-parity.d.ts.map +1 -0
- package/lib/dist/installer/targets/feature-parity.js +32 -0
- package/lib/dist/installer/targets/feature-parity.js.map +1 -0
- package/lib/dist/installer/{gemini-commands.d.ts → targets/gemini-commands.d.ts} +7 -3
- package/lib/dist/installer/targets/gemini-commands.d.ts.map +1 -0
- package/lib/dist/installer/{gemini-commands.js → targets/gemini-commands.js} +37 -9
- package/lib/dist/installer/targets/gemini-commands.js.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts +11 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.js +84 -0
- package/lib/dist/installer/targets/gemini-projects.js.map +1 -0
- package/lib/dist/installer/targets/gemini.d.ts +94 -8
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
- package/lib/dist/installer/targets/gemini.js +392 -33
- package/lib/dist/installer/targets/gemini.js.map +1 -1
- package/lib/dist/installer/targets/registry.d.ts +8 -1
- package/lib/dist/installer/targets/registry.d.ts.map +1 -1
- package/lib/dist/installer/targets/registry.js +14 -5
- package/lib/dist/installer/targets/registry.js.map +1 -1
- package/lib/dist/installer/targets/shared.d.ts +44 -1
- package/lib/dist/installer/targets/shared.d.ts.map +1 -1
- package/lib/dist/installer/targets/shared.js +130 -3
- package/lib/dist/installer/targets/shared.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +23 -1
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.d.ts +22 -2
- package/lib/dist/jira/auto-publish.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.js +54 -9
- package/lib/dist/jira/auto-publish.js.map +1 -1
- package/lib/dist/jira/board-first.d.ts +15 -2
- package/lib/dist/jira/board-first.d.ts.map +1 -1
- package/lib/dist/jira/board-first.js +16 -0
- package/lib/dist/jira/board-first.js.map +1 -1
- package/lib/dist/jira/ci-publish.d.ts +72 -0
- package/lib/dist/jira/ci-publish.d.ts.map +1 -0
- package/lib/dist/jira/ci-publish.js +162 -0
- package/lib/dist/jira/ci-publish.js.map +1 -0
- package/lib/dist/jira/publish.d.ts +23 -0
- package/lib/dist/jira/publish.d.ts.map +1 -1
- package/lib/dist/jira/publish.js +0 -0
- package/lib/dist/jira/publish.js.map +1 -1
- package/lib/dist/jira/regression-pack.d.ts.map +1 -1
- package/lib/dist/jira/regression-pack.js +7 -1
- package/lib/dist/jira/regression-pack.js.map +1 -1
- package/lib/dist/jira/repo-config.d.ts.map +1 -1
- package/lib/dist/jira/repo-config.js +8 -0
- package/lib/dist/jira/repo-config.js.map +1 -1
- package/lib/dist/jira/types.d.ts +8 -0
- package/lib/dist/jira/types.d.ts.map +1 -1
- package/lib/dist/jira/types.js.map +1 -1
- package/lib/dist/knowledge/enforce.d.ts.map +1 -1
- package/lib/dist/knowledge/enforce.js +6 -1
- package/lib/dist/knowledge/enforce.js.map +1 -1
- package/lib/dist/mcp/jira-tools.d.ts +5 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
- package/lib/dist/mcp/jira-tools.js +59 -4
- package/lib/dist/mcp/jira-tools.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +2 -1
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +16 -10
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +69 -20
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +11 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +43 -1
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +8 -0
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/reflect/apply.d.ts +2 -2
- package/lib/dist/reflect/apply.d.ts.map +1 -1
- package/lib/dist/reflect/apply.js +6 -5
- package/lib/dist/reflect/apply.js.map +1 -1
- package/lib/dist/reflect/miner.d.ts.map +1 -1
- package/lib/dist/reflect/miner.js +28 -15
- package/lib/dist/reflect/miner.js.map +1 -1
- package/lib/dist/reflect/session-outcomes.d.ts.map +1 -1
- package/lib/dist/reflect/session-outcomes.js +8 -3
- package/lib/dist/reflect/session-outcomes.js.map +1 -1
- package/lib/dist/reflect/targets.d.ts.map +1 -1
- package/lib/dist/reflect/targets.js +25 -5
- package/lib/dist/reflect/targets.js.map +1 -1
- package/lib/dist/reflect/types.d.ts +7 -0
- package/lib/dist/reflect/types.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +14 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +13 -3
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/server/canonical-tools.js +50 -0
- package/lib/dist/server/cli.js +2 -2
- package/lib/dist/server/host-profiles.js +39 -0
- package/lib/dist/server/ingest/index.js +2 -1
- package/lib/dist/server/ingest/ingestor.js +195 -76
- package/lib/dist/server/ingest/project-paths.js +8 -2
- package/lib/dist/server/ingest/recorded-projects.js +43 -0
- package/lib/dist/server/ingest/specship-classify.js +7 -2
- package/lib/dist/server/ingest/transcripts/claude-jsonl.js +54 -0
- package/lib/dist/server/ingest/transcripts/claude.js +64 -0
- package/lib/dist/server/ingest/transcripts/gemini.js +469 -0
- package/lib/dist/server/ingest/transcripts/index.js +14 -0
- package/lib/dist/server/ingest/{parser.js → transcripts/shared.js} +3 -49
- package/lib/dist/server/ingest/transcripts/types.js +4 -0
- package/lib/dist/server/ingest/watcher.js +31 -30
- package/lib/dist/server/routes/claude.js +27 -10
- package/lib/dist/server/routes/graph.js +64 -3
- package/lib/dist/server/routes/mcp.js +57 -15
- package/lib/dist/server/routes/memory.js +54 -35
- package/lib/dist/server/routes/projects.js +78 -7
- package/lib/dist/server/routes/spec.js +125 -0
- package/lib/dist/server/server.js +8 -2
- package/lib/dist/skills/design-md/SKILL.md +172 -0
- package/lib/dist/skills/design-md/references/authored-sections.md +90 -0
- package/lib/dist/skills/design-md/references/sources.md +71 -0
- package/lib/dist/skills/spec-author/SKILL.md +1 -1
- package/lib/dist/skills/spec-author/references/gap-questions.md +1 -1
- package/lib/dist/spec-lint/lint.d.ts +26 -1
- package/lib/dist/spec-lint/lint.d.ts.map +1 -1
- package/lib/dist/spec-lint/lint.js +96 -7
- package/lib/dist/spec-lint/lint.js.map +1 -1
- package/lib/dist/spec-quality/postcheck.d.ts +7 -1
- package/lib/dist/spec-quality/postcheck.d.ts.map +1 -1
- package/lib/dist/spec-quality/postcheck.js +31 -0
- package/lib/dist/spec-quality/postcheck.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +48 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +109 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/ui/assets/bricolage-latin-C23fTOVi.woff2 +0 -0
- package/lib/dist/ui/assets/bricolage-latin-ext-B82pxkPf.woff2 +0 -0
- package/lib/dist/ui/assets/index-54vorjyq.css +1 -0
- package/lib/dist/ui/assets/index-BZQonCVb.js +52 -0
- package/lib/dist/ui/assets/space-grotesk-latin-DPT1xrvW.woff2 +0 -0
- package/lib/dist/ui/assets/space-grotesk-latin-ext-wJGaI8U0.woff2 +0 -0
- package/lib/dist/ui/index.html +2 -2
- package/lib/dist/update/updater.d.ts +6 -0
- package/lib/dist/update/updater.d.ts.map +1 -1
- package/lib/dist/update/updater.js +13 -1
- package/lib/dist/update/updater.js.map +1 -1
- package/lib/dist/workflows/agents/claude.d.ts +9 -0
- package/lib/dist/workflows/agents/claude.d.ts.map +1 -1
- package/lib/dist/workflows/agents/claude.js +22 -3
- package/lib/dist/workflows/agents/claude.js.map +1 -1
- package/lib/dist/workflows/agents/gemini-version.d.ts +18 -0
- package/lib/dist/workflows/agents/gemini-version.d.ts.map +1 -0
- package/lib/dist/workflows/agents/gemini-version.js +81 -0
- package/lib/dist/workflows/agents/gemini-version.js.map +1 -0
- package/lib/dist/workflows/agents/gemini.d.ts +1 -1
- package/lib/dist/workflows/agents/gemini.d.ts.map +1 -1
- package/lib/dist/workflows/agents/gemini.js +12 -2
- package/lib/dist/workflows/agents/gemini.js.map +1 -1
- package/lib/dist/workflows/agents/index.d.ts +2 -0
- package/lib/dist/workflows/agents/index.d.ts.map +1 -1
- package/lib/dist/workflows/agents/index.js +5 -0
- package/lib/dist/workflows/agents/index.js.map +1 -1
- package/lib/dist/workflows/agents/types.d.ts +13 -0
- package/lib/dist/workflows/agents/types.d.ts.map +1 -1
- package/lib/dist/workflows/agents/types.js.map +1 -1
- package/lib/dist/workflows/condition-evaluator.d.ts +8 -0
- package/lib/dist/workflows/condition-evaluator.d.ts.map +1 -1
- package/lib/dist/workflows/condition-evaluator.js +7 -0
- package/lib/dist/workflows/condition-evaluator.js.map +1 -1
- package/lib/dist/workflows/defaults/claude-design-implement.yaml +44 -15
- package/lib/dist/workflows/defaults/spec-author.yaml +9 -9
- package/lib/dist/workflows/defaults/spec-fix.yaml +8 -8
- package/lib/dist/workflows/defaults/spec-implement.yaml +54 -23
- package/lib/dist/workflows/defaults/spec-relink.yaml +5 -5
- package/lib/dist/workflows/defaults/spec-verify.yaml +3 -3
- package/lib/dist/workflows/executor.d.ts +7 -0
- package/lib/dist/workflows/executor.d.ts.map +1 -1
- package/lib/dist/workflows/executor.js +22 -5
- package/lib/dist/workflows/executor.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +3 -2
- package/package.json +1 -1
- package/lib/dist/installer/config-writer.d.ts.map +0 -1
- package/lib/dist/installer/config-writer.js.map +0 -1
- package/lib/dist/installer/gemini-commands.d.ts.map +0 -1
- package/lib/dist/installer/gemini-commands.js.map +0 -1
- package/lib/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/lib/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/lib/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/lib/dist/ui/assets/index-BzuUWUAE.js +0 -50
- package/lib/dist/ui/assets/index-ezwX2EfG.css +0 -1
package/lib/dist/bin/specship.js
CHANGED
|
@@ -61,6 +61,7 @@ const path = __importStar(require("path"));
|
|
|
61
61
|
const fs = __importStar(require("fs"));
|
|
62
62
|
const os = __importStar(require("os"));
|
|
63
63
|
const directory_1 = require("../directory");
|
|
64
|
+
const hook_output_1 = require("../activation/hook-output");
|
|
64
65
|
const worktree_1 = require("../sync/worktree");
|
|
65
66
|
const shimmer_progress_1 = require("../ui/shimmer-progress");
|
|
66
67
|
const glyphs_1 = require("../ui/glyphs");
|
|
@@ -69,6 +70,7 @@ const updater_1 = require("../update/updater");
|
|
|
69
70
|
const resolve_latest_1 = require("../update/resolve-latest");
|
|
70
71
|
const run_installer_1 = require("../update/run-installer");
|
|
71
72
|
const wasm_runtime_flags_1 = require("../extraction/wasm-runtime-flags");
|
|
73
|
+
const parse_warnings_1 = require("../extraction/parse-warnings");
|
|
72
74
|
// Lazy-load heavy modules (SpecShip, runInstaller) to keep CLI startup fast.
|
|
73
75
|
async function loadSpecShip() {
|
|
74
76
|
try {
|
|
@@ -118,133 +120,6 @@ async function loadServerPackage() {
|
|
|
118
120
|
* The JSONL ingest watcher now ships inside `@specship/specship-server`
|
|
119
121
|
* itself — `createServer({ ingest: true })` starts it. No separate package.
|
|
120
122
|
*/
|
|
121
|
-
/**
|
|
122
|
-
* Claude Code's slug encoding (every non-alphanumeric char → '-') is lossy,
|
|
123
|
-
* so decoding by mapping '-' back to '/' mangles any path containing
|
|
124
|
-
* hyphens/dots/underscores. Recover real paths from `~/.claude.json` (its
|
|
125
|
-
* `projects` object is keyed by real absolute paths), then from a `"cwd"`
|
|
126
|
-
* value in the slug's newest transcript, before falling back to the lossy
|
|
127
|
-
* decode. Mirrors `server/src/ingest/project-paths.ts`
|
|
128
|
-
* (REQ-SLUGRES-004) — the server package depends on this one, so the CLI
|
|
129
|
-
* can't import it.
|
|
130
|
-
*/
|
|
131
|
-
function buildClaudeSlugIndex() {
|
|
132
|
-
const bySlug = new Map();
|
|
133
|
-
try {
|
|
134
|
-
const raw = fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8');
|
|
135
|
-
const parsed = JSON.parse(raw);
|
|
136
|
-
for (const realPath of Object.keys(parsed.projects ?? {})) {
|
|
137
|
-
bySlug.set(realPath.replace(/[^A-Za-z0-9]/g, '-'), realPath);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
catch { /* missing or malformed — sniff/fallback below still work */ }
|
|
141
|
-
return bySlug;
|
|
142
|
-
}
|
|
143
|
-
function sniffClaudeSlugCwd(claudeRoot, slug) {
|
|
144
|
-
const dir = path.join(claudeRoot, slug);
|
|
145
|
-
let newest = null;
|
|
146
|
-
try {
|
|
147
|
-
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
148
|
-
if (!f.isFile() || !f.name.toLowerCase().endsWith('.jsonl'))
|
|
149
|
-
continue;
|
|
150
|
-
const st = fs.statSync(path.join(dir, f.name));
|
|
151
|
-
if (!newest || st.mtimeMs > newest.mtimeMs)
|
|
152
|
-
newest = { file: f.name, mtimeMs: st.mtimeMs };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
if (!newest)
|
|
159
|
-
return null;
|
|
160
|
-
try {
|
|
161
|
-
const fd = fs.openSync(path.join(dir, newest.file), 'r');
|
|
162
|
-
let chunk;
|
|
163
|
-
try {
|
|
164
|
-
const buf = Buffer.alloc(64 * 1024);
|
|
165
|
-
const read = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
166
|
-
chunk = buf.toString('utf8', 0, read);
|
|
167
|
-
}
|
|
168
|
-
finally {
|
|
169
|
-
fs.closeSync(fd);
|
|
170
|
-
}
|
|
171
|
-
const m = /"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(chunk);
|
|
172
|
-
if (!m)
|
|
173
|
-
return null;
|
|
174
|
-
let cwd = JSON.parse(`"${m[1]}"`);
|
|
175
|
-
// Accept the cwd or an ancestor of it — sessions may record a subdir.
|
|
176
|
-
for (;;) {
|
|
177
|
-
if (cwd.replace(/[^A-Za-z0-9]/g, '-') === slug)
|
|
178
|
-
return cwd;
|
|
179
|
-
const parent = path.dirname(cwd);
|
|
180
|
-
if (parent === cwd)
|
|
181
|
-
return null;
|
|
182
|
-
cwd = parent;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Auto-pick a default project for `specship desktop` when the user
|
|
191
|
-
* didn't pass `-p` and the cwd isn't initialized either. Walks
|
|
192
|
-
* `~/.claude/projects/*` (Claude Code's per-project transcript dirs),
|
|
193
|
-
* resolves each slug back to a real path, and returns the most-recently-
|
|
194
|
-
* touched one that's still on disk AND has been `specship init`'d.
|
|
195
|
-
*
|
|
196
|
-
* Returns null when nothing qualifies — the server boots projectless and
|
|
197
|
-
* the desktop UI prompts the user to pick.
|
|
198
|
-
*/
|
|
199
|
-
async function pickRecentInitializedProject() {
|
|
200
|
-
const claudeRoot = path.join(os.homedir(), '.claude', 'projects');
|
|
201
|
-
let entries;
|
|
202
|
-
try {
|
|
203
|
-
entries = fs.readdirSync(claudeRoot, { withFileTypes: true });
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
return null;
|
|
207
|
-
}
|
|
208
|
-
const slugIndex = buildClaudeSlugIndex();
|
|
209
|
-
const candidates = [];
|
|
210
|
-
for (const ent of entries) {
|
|
211
|
-
if (!ent.isDirectory())
|
|
212
|
-
continue;
|
|
213
|
-
const slug = ent.name;
|
|
214
|
-
const decoded = slugIndex.get(slug) ??
|
|
215
|
-
sniffClaudeSlugCwd(claudeRoot, slug) ??
|
|
216
|
-
(slug.startsWith('-') ? '/' + slug.slice(1).replace(/-/g, '/') : slug);
|
|
217
|
-
try {
|
|
218
|
-
const st = fs.statSync(decoded);
|
|
219
|
-
if (!st.isDirectory())
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
if (!(0, directory_1.isInitialized)(decoded))
|
|
226
|
-
continue;
|
|
227
|
-
// Use the newest .jsonl mtime in the slug dir as the activity signal.
|
|
228
|
-
let lastTouched = 0;
|
|
229
|
-
try {
|
|
230
|
-
const files = fs.readdirSync(path.join(claudeRoot, slug), { withFileTypes: true });
|
|
231
|
-
for (const f of files) {
|
|
232
|
-
if (!f.isFile() || !f.name.toLowerCase().endsWith('.jsonl'))
|
|
233
|
-
continue;
|
|
234
|
-
try {
|
|
235
|
-
const st = fs.statSync(path.join(claudeRoot, slug, f.name));
|
|
236
|
-
if (st.mtimeMs > lastTouched)
|
|
237
|
-
lastTouched = st.mtimeMs;
|
|
238
|
-
}
|
|
239
|
-
catch { /* ignore */ }
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
catch { /* ignore */ }
|
|
243
|
-
candidates.push({ decoded, lastTouched });
|
|
244
|
-
}
|
|
245
|
-
candidates.sort((a, b) => b.lastTouched - a.lastTouched);
|
|
246
|
-
return candidates[0]?.decoded ?? null;
|
|
247
|
-
}
|
|
248
123
|
// Dynamic import helper — tsc compiles import() to require() in CJS mode,
|
|
249
124
|
// which fails for ESM-only packages. This bypasses the transformation.
|
|
250
125
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
@@ -333,9 +208,79 @@ function main() {
|
|
|
333
208
|
.name('specship')
|
|
334
209
|
.description('Code intelligence and knowledge graph for any codebase')
|
|
335
210
|
.version(packageJson.version);
|
|
211
|
+
/**
|
|
212
|
+
* Commands that run on every agent turn / tool call (the installed hooks, the
|
|
213
|
+
* status line) or as the long-lived MCP server. They must stay exactly as
|
|
214
|
+
* fast as before, so the installed-agents record is never checked on them.
|
|
215
|
+
*/
|
|
216
|
+
const AGENT_RECORD_SKIP_COMMANDS = new Set([
|
|
217
|
+
'serve',
|
|
218
|
+
'cheatsheet',
|
|
219
|
+
'spec-nudge',
|
|
220
|
+
'steer-nudge',
|
|
221
|
+
'search-intercept',
|
|
222
|
+
'statusline',
|
|
223
|
+
'sync',
|
|
224
|
+
'sync-if-dirty',
|
|
225
|
+
'mark-dirty',
|
|
226
|
+
'auto-sync',
|
|
227
|
+
'hook',
|
|
228
|
+
]);
|
|
229
|
+
// REQ-AGENTSWITCH-006.A2: any other command rebuilds a missing/unreadable
|
|
230
|
+
// installed-agents record from the agents' configs. A present record costs one
|
|
231
|
+
// file read and no write. Silent: the record never blocks or fails a command.
|
|
232
|
+
/**
|
|
233
|
+
* Commands that never auto-set-up a newly used agent (REQ-AGENTSWITCH-008):
|
|
234
|
+
* the install commands do it explicitly, and the MCP server detects its
|
|
235
|
+
* agent from the client name in its own session instead.
|
|
236
|
+
*/
|
|
237
|
+
const AUTO_MIGRATE_SKIP_COMMANDS = new Set(['install', 'uninstall', 'serve', 'update']);
|
|
238
|
+
program.hook('preAction', (_root, actionCommand) => {
|
|
239
|
+
let top = actionCommand;
|
|
240
|
+
while (top.parent && top.parent !== program)
|
|
241
|
+
top = top.parent;
|
|
242
|
+
// REQ-AGENTSWITCH-008: a `specship` command in a supported agent's shell —
|
|
243
|
+
// typed by the user, run by a hook, or by the git hook a commit from that
|
|
244
|
+
// shell fires — sets SpecShip up for that agent if the record doesn't list
|
|
245
|
+
// it here. Unknown agent → no file access; recorded agent → one small read.
|
|
246
|
+
// The notice goes to stderr so hook stdout stays the host's JSON.
|
|
247
|
+
if (!AUTO_MIGRATE_SKIP_COMMANDS.has(top.name())) {
|
|
248
|
+
try {
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
250
|
+
const { agentFromEnv } = require('../agents/detect');
|
|
251
|
+
const agent = agentFromEnv();
|
|
252
|
+
if (agent !== 'unknown') {
|
|
253
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
254
|
+
const { maybeAutoMigrate, migrationLine } = require('../installer/auto-migrate');
|
|
255
|
+
const line = migrationLine(maybeAutoMigrate(agent));
|
|
256
|
+
if (line)
|
|
257
|
+
process.stderr.write(line + '\n');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
/* advisory — never blocks or fails a command */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (AGENT_RECORD_SKIP_COMMANDS.has(top.name()))
|
|
265
|
+
return;
|
|
266
|
+
try {
|
|
267
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
268
|
+
const { ensureAgentRecord } = require('../installer/agent-record');
|
|
269
|
+
ensureAgentRecord(process.cwd());
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
/* advisory */
|
|
273
|
+
}
|
|
274
|
+
});
|
|
336
275
|
// =============================================================================
|
|
337
276
|
// Helper Functions
|
|
338
277
|
// =============================================================================
|
|
278
|
+
/** Every supported agent's display name, from the target registry (REQ-AGENTSWITCH-012). */
|
|
279
|
+
function agentNames() {
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
281
|
+
const { allTargets } = require('../installer/targets/registry');
|
|
282
|
+
return allTargets().map((t) => t.displayName).join(', ');
|
|
283
|
+
}
|
|
339
284
|
/**
|
|
340
285
|
* Resolve project path from argument or current directory
|
|
341
286
|
* Walks up parent directories to find nearest initialized SpecShip project
|
|
@@ -496,7 +441,7 @@ function main() {
|
|
|
496
441
|
if (!sp)
|
|
497
442
|
return;
|
|
498
443
|
console.log();
|
|
499
|
-
console.log(chalk.bold('Try this first — ask
|
|
444
|
+
console.log(chalk.bold('Try this first — ask your coding agent:'));
|
|
500
445
|
console.log(' ' + chalk.cyan(sp.prompt));
|
|
501
446
|
console.log(chalk.dim(" (it'll explore the index instead of reading files)"));
|
|
502
447
|
}
|
|
@@ -540,6 +485,10 @@ function main() {
|
|
|
540
485
|
else {
|
|
541
486
|
clack.log.warn('No files found to index');
|
|
542
487
|
}
|
|
488
|
+
// Parse-error regions that may have swallowed declarations (REQ-HTMLLEX-003).
|
|
489
|
+
const parseWarning = (0, parse_warnings_1.formatParseWarningSummary)(result.filesWithParseWarnings);
|
|
490
|
+
if (parseWarning)
|
|
491
|
+
clack.log.warn(parseWarning);
|
|
543
492
|
if (hasErrors) {
|
|
544
493
|
const errorsByCode = new Map();
|
|
545
494
|
for (const err of result.errors) {
|
|
@@ -854,10 +803,12 @@ function main() {
|
|
|
854
803
|
// CLAUDE.md governance findings ride the same SessionStart line
|
|
855
804
|
// (CLAUDEMD-DOC, REQ-CLAUDEMD-003) — silent at zero.
|
|
856
805
|
try {
|
|
857
|
-
const { readClaudeMdAudit } = require('../claudemd/audit');
|
|
806
|
+
const { readClaudeMdAudit, auditMemoryFiles } = require('../claudemd/audit');
|
|
858
807
|
const audit = readClaudeMdAudit(projectPath);
|
|
859
808
|
if (audit && audit.findings.length > 0) {
|
|
860
|
-
|
|
809
|
+
// Name the memory file(s) the findings are about (REQ-AGENTSWITCH-003).
|
|
810
|
+
const files = auditMemoryFiles(audit).join(' and ');
|
|
811
|
+
console.log(`⚠ ${audit.findings.length} ${files} finding(s) — review with /specship:check claudemd`);
|
|
861
812
|
}
|
|
862
813
|
}
|
|
863
814
|
catch {
|
|
@@ -997,6 +948,10 @@ function main() {
|
|
|
997
948
|
parts.push(`${specWarnings} warning${specWarnings === 1 ? '' : 's'}`);
|
|
998
949
|
clack.log.warn(`Spec parse: ${parts.join(', ')}`);
|
|
999
950
|
}
|
|
951
|
+
// Parse-error regions that may have swallowed declarations (REQ-HTMLLEX-003).
|
|
952
|
+
const parseWarning = (0, parse_warnings_1.formatParseWarningSummary)(result.filesWithParseWarnings);
|
|
953
|
+
if (parseWarning)
|
|
954
|
+
clack.log.warn(parseWarning);
|
|
1000
955
|
pushDriftNotices(result);
|
|
1001
956
|
await pushJiraDriftComments(cg, result);
|
|
1002
957
|
await pushJiraAutoPublish(result);
|
|
@@ -1026,6 +981,10 @@ function main() {
|
|
|
1026
981
|
// working tree (e.g. a nested worktree borrowing the main checkout's index).
|
|
1027
982
|
const startPath = path.resolve(pathArg || process.cwd());
|
|
1028
983
|
const worktreeMismatch = (0, worktree_1.detectWorktreeIndexMismatch)(startPath, projectPath);
|
|
984
|
+
// The agent whose shell ran this command, from its launcher env vars
|
|
985
|
+
// (REQ-AGENTSWITCH-007). Report-only: detection never changes anything.
|
|
986
|
+
const { agentFromEnv, agentDisplayName } = await Promise.resolve().then(() => __importStar(require('../agents/detect')));
|
|
987
|
+
const currentAgent = agentFromEnv(process.env);
|
|
1029
988
|
try {
|
|
1030
989
|
if (!(0, directory_1.isInitialized)(projectPath)) {
|
|
1031
990
|
if (options.json) {
|
|
@@ -1035,11 +994,13 @@ function main() {
|
|
|
1035
994
|
projectPath,
|
|
1036
995
|
indexPath: (0, directory_1.getSpecShipDir)(projectPath),
|
|
1037
996
|
lastIndexed: null,
|
|
997
|
+
currentAgent,
|
|
1038
998
|
}));
|
|
1039
999
|
return;
|
|
1040
1000
|
}
|
|
1041
1001
|
console.log(chalk.bold('\nSpecShip Status\n'));
|
|
1042
1002
|
info(`Project: ${projectPath}`);
|
|
1003
|
+
info(`Agent: ${agentDisplayName(currentAgent)}`);
|
|
1043
1004
|
warn('Not initialized');
|
|
1044
1005
|
info('Run "specship init" to initialize');
|
|
1045
1006
|
return;
|
|
@@ -1050,6 +1011,9 @@ function main() {
|
|
|
1050
1011
|
const changes = cg.getChangedFiles();
|
|
1051
1012
|
const backend = cg.getBackend();
|
|
1052
1013
|
const journalMode = cg.getJournalMode();
|
|
1014
|
+
// Recorded index revision vs live HEAD (REQ-MULTIDEV-004). Report-only:
|
|
1015
|
+
// nothing here syncs because HEAD moved.
|
|
1016
|
+
const freshness = cg.getIndexFreshness();
|
|
1053
1017
|
// JSON output mode
|
|
1054
1018
|
if (options.json) {
|
|
1055
1019
|
const lastIndexedMs = cg.getLastIndexedAt();
|
|
@@ -1075,6 +1039,8 @@ function main() {
|
|
|
1075
1039
|
worktreeMismatch: worktreeMismatch
|
|
1076
1040
|
? { worktreeRoot: worktreeMismatch.worktreeRoot, indexRoot: worktreeMismatch.indexRoot }
|
|
1077
1041
|
: null,
|
|
1042
|
+
...(0, worktree_1.indexFreshnessJsonFields)(freshness),
|
|
1043
|
+
currentAgent,
|
|
1078
1044
|
}));
|
|
1079
1045
|
cg.destroy();
|
|
1080
1046
|
return;
|
|
@@ -1082,9 +1048,16 @@ function main() {
|
|
|
1082
1048
|
console.log(chalk.bold('\nSpecShip Status\n'));
|
|
1083
1049
|
// Project info
|
|
1084
1050
|
console.log(chalk.cyan('Project:'), projectPath);
|
|
1051
|
+
console.log(chalk.cyan('Agent: '), agentDisplayName(currentAgent));
|
|
1085
1052
|
if (worktreeMismatch) {
|
|
1086
1053
|
warn((0, worktree_1.worktreeMismatchWarning)(worktreeMismatch));
|
|
1087
1054
|
}
|
|
1055
|
+
if (freshness) {
|
|
1056
|
+
console.log(chalk.cyan('Indexed:'), (0, worktree_1.formatRevision)(freshness.indexedBranch, freshness.indexedCommit));
|
|
1057
|
+
console.log(chalk.cyan('HEAD: '), (0, worktree_1.formatRevision)(freshness.headBranch, freshness.headCommit));
|
|
1058
|
+
if (freshness.stale)
|
|
1059
|
+
warn((0, worktree_1.indexStaleNotice)(freshness));
|
|
1060
|
+
}
|
|
1088
1061
|
console.log();
|
|
1089
1062
|
// Index stats
|
|
1090
1063
|
console.log(chalk.bold('Index Statistics:'));
|
|
@@ -1163,7 +1136,7 @@ function main() {
|
|
|
1163
1136
|
*/
|
|
1164
1137
|
program
|
|
1165
1138
|
.command('statusline')
|
|
1166
|
-
.description('Print a SpecShip status-line segment (reads
|
|
1139
|
+
.description('Print a SpecShip status-line segment (Claude-only surface — reads the status-line JSON the agent pipes on stdin)')
|
|
1167
1140
|
.action(async () => {
|
|
1168
1141
|
const { buildSegment } = await Promise.resolve().then(() => __importStar(require('../statusline/index')));
|
|
1169
1142
|
let raw = '';
|
|
@@ -1548,7 +1521,7 @@ function main() {
|
|
|
1548
1521
|
.command('capture')
|
|
1549
1522
|
.description('Capture a lesson/anti-pattern as a human-gated memory rule (lesson text on stdin).')
|
|
1550
1523
|
.requiredOption('--title <title>', 'short title for the lesson')
|
|
1551
|
-
.option('--target <target>', 'where apply would write it: "memory" (portable
|
|
1524
|
+
.option('--target <target>', 'where apply would write it: "memory" (portable user memory, default) or "claude-md" (the project memory file of the active agent)', 'memory')
|
|
1552
1525
|
.option('--path <projectRoot>', 'project root (default: cwd)')
|
|
1553
1526
|
.option('-j, --json', 'output the created proposal as JSON')
|
|
1554
1527
|
.action(async (options) => {
|
|
@@ -1578,7 +1551,7 @@ function main() {
|
|
|
1578
1551
|
}
|
|
1579
1552
|
else {
|
|
1580
1553
|
info(`Captured lesson as proposal ${p.contentHash.slice(0, 12)} (${p.state}): ${p.title}`);
|
|
1581
|
-
info(`Target: ${p
|
|
1554
|
+
info(`Target: ${memoryTargetLabel(p, projectPath)} — nothing written yet.`);
|
|
1582
1555
|
info('Review with `specship reflect`; manage applied memory rules with `specship memory list`.');
|
|
1583
1556
|
}
|
|
1584
1557
|
cg.destroy();
|
|
@@ -1588,6 +1561,20 @@ function main() {
|
|
|
1588
1561
|
process.exit(1);
|
|
1589
1562
|
}
|
|
1590
1563
|
});
|
|
1564
|
+
/**
|
|
1565
|
+
* Where a memory_rule proposal writes, named from its own target path so each
|
|
1566
|
+
* agent's memory file shows (REQ-AGENTSWITCH-003).
|
|
1567
|
+
*/
|
|
1568
|
+
function memoryTargetLabel(p, projectRoot) {
|
|
1569
|
+
if (p.targetKind !== 'claude_md') {
|
|
1570
|
+
const dir = path.dirname(p.targetPath);
|
|
1571
|
+
const home = os.homedir();
|
|
1572
|
+
return `portable memory note in ${dir.startsWith(home) ? '~' + dir.slice(home.length) : dir}`;
|
|
1573
|
+
}
|
|
1574
|
+
const file = path.basename(p.targetPath);
|
|
1575
|
+
const inProject = !path.relative(path.resolve(projectRoot), p.targetPath).startsWith('..');
|
|
1576
|
+
return inProject ? `project ${file}` : `portable user ${file}`;
|
|
1577
|
+
}
|
|
1591
1578
|
// Body text of a memory_rule proposal's payload (the note or the CLAUDE.md block).
|
|
1592
1579
|
function memoryRuleBody(p) {
|
|
1593
1580
|
if (p.payload.kind === 'memory_note')
|
|
@@ -1623,7 +1610,7 @@ function main() {
|
|
|
1623
1610
|
return;
|
|
1624
1611
|
}
|
|
1625
1612
|
for (const p of items) {
|
|
1626
|
-
const target = p.targetKind === 'claude_md' ?
|
|
1613
|
+
const target = p.targetKind === 'claude_md' ? path.basename(p.targetPath) : 'memory note';
|
|
1627
1614
|
success(`${p.contentHash.slice(0, 12)} [${target}] ${p.title}`);
|
|
1628
1615
|
}
|
|
1629
1616
|
cg.destroy();
|
|
@@ -1939,6 +1926,43 @@ function main() {
|
|
|
1939
1926
|
process.exit(1);
|
|
1940
1927
|
}
|
|
1941
1928
|
});
|
|
1929
|
+
/**
|
|
1930
|
+
* design.md (DESIGNMD-DOC). `extract` turns a Claude Design snapshot or a
|
|
1931
|
+
* Figma export into a pixel-exact reference; `check` fails one that is
|
|
1932
|
+
* incomplete, stale or vague. File-based, needs no index.
|
|
1933
|
+
*/
|
|
1934
|
+
program
|
|
1935
|
+
.command('design <action> [paths...]')
|
|
1936
|
+
.description('design.md reference: extract <source...> [--out|--update <design.md>] | check <design.md> | verify <build.html> --against <design.md>')
|
|
1937
|
+
.option('-p, --path <path>', 'Project path (paths are resolved against it; defaults to cwd)')
|
|
1938
|
+
.option('-o, --out <file>', 'extract: write a new design.md')
|
|
1939
|
+
.option('-u, --update <file>', 'extract: refresh the generated block of an existing design.md')
|
|
1940
|
+
.option('-t, --title <title>', 'extract --out: the design title')
|
|
1941
|
+
.option('--origin <link>', 'extract --out: where the design came from (claude.ai/design or figma.com link)')
|
|
1942
|
+
.option('-j, --json', 'check: output as JSON')
|
|
1943
|
+
.option('-a, --against <file>', 'verify: the design.md to compare the build with')
|
|
1944
|
+
.action(async (action, paths, options) => {
|
|
1945
|
+
const projectRoot = resolveProjectPath(options.path);
|
|
1946
|
+
const { runDesignExtractCli, runDesignCheckCli, runDesignVerifyCli } = await Promise.resolve().then(() => __importStar(require('../design-md/check')));
|
|
1947
|
+
const flags = (pairs) => pairs.flatMap(([k, v]) => (v ? [k, v] : []));
|
|
1948
|
+
let result;
|
|
1949
|
+
if (action === 'extract') {
|
|
1950
|
+
result = runDesignExtractCli([...(paths ?? []), ...flags([['--out', options.out], ['--update', options.update], ['--title', options.title], ['--origin', options.origin]])], { projectRoot });
|
|
1951
|
+
}
|
|
1952
|
+
else if (action === 'check') {
|
|
1953
|
+
result = runDesignCheckCli([...(paths ?? []), ...(options.json ? ['--json'] : [])], { projectRoot });
|
|
1954
|
+
}
|
|
1955
|
+
else if (action === 'verify') {
|
|
1956
|
+
result = runDesignVerifyCli([...(paths ?? []), ...flags([['--against', options.against]])], { projectRoot });
|
|
1957
|
+
}
|
|
1958
|
+
else {
|
|
1959
|
+
error(`unknown design action "${action}" — use extract, check or verify`);
|
|
1960
|
+
process.exit(1);
|
|
1961
|
+
}
|
|
1962
|
+
console.log(result.output);
|
|
1963
|
+
if (result.exitCode !== 0)
|
|
1964
|
+
process.exit(result.exitCode);
|
|
1965
|
+
});
|
|
1942
1966
|
/**
|
|
1943
1967
|
* specship knowledge <action> — the knowledge layer (KNOWLEDGE-DOC).
|
|
1944
1968
|
* stale external facts whose dependency version changed (REQ-KNOW-002/006)
|
|
@@ -2223,7 +2247,7 @@ function main() {
|
|
|
2223
2247
|
.option('-j, --json', 'Output as JSON')
|
|
2224
2248
|
.option('--strict', 'Treat every check as gating for this run only (nothing read from or written to config)')
|
|
2225
2249
|
.option('--enable-gate <checks...>', 'Persist gating for the named checks into specship.config.json, then run the gate')
|
|
2226
|
-
.option('--since <ref>', 'Evaluate only requirements whose links or spec files touch files changed since <ref> (drift + behaviour; fitness and maintainability stay repo-global)')
|
|
2250
|
+
.option('--since <ref>', 'Evaluate only requirements whose links or spec files touch files changed since <ref> (drift + behaviour; fitness and maintainability stay repo-global), and report links that resolved at <ref> but this branch broke (branchBroken)')
|
|
2227
2251
|
.action(async (pathArg, options) => {
|
|
2228
2252
|
const projectPath = resolveProjectPath(pathArg);
|
|
2229
2253
|
try {
|
|
@@ -2263,6 +2287,8 @@ function main() {
|
|
|
2263
2287
|
}
|
|
2264
2288
|
const { default: SpecShip } = await loadSpecShip();
|
|
2265
2289
|
const cg = await SpecShip.open(projectPath);
|
|
2290
|
+
// The branch-broken check parses link targets at the ref (REQ-MULTIDEV-002).
|
|
2291
|
+
await cg.prepareEnforce(scope);
|
|
2266
2292
|
// --strict gates everything for this run only (REQ-ENFORCE-004.A2).
|
|
2267
2293
|
const r = options.strict
|
|
2268
2294
|
? cg.getEnforce(enforce.strictEnforceConfig(), scope)
|
|
@@ -2275,7 +2301,8 @@ function main() {
|
|
|
2275
2301
|
console.log(chalk.bold('\nEnforcement gate\n'));
|
|
2276
2302
|
if (scope) {
|
|
2277
2303
|
console.log(chalk.dim(` scoped to ${scope.changedFiles.length} file(s) changed since ${scope.since}` +
|
|
2278
|
-
' — drift and behaviour are link-scoped;
|
|
2304
|
+
' — drift and behaviour are link-scoped; branchBroken compares against that ref;' +
|
|
2305
|
+
' fitness, maintainability and undeclaredLinks are repo-global and ran unscoped.\n'));
|
|
2279
2306
|
}
|
|
2280
2307
|
for (const c of r.checks) {
|
|
2281
2308
|
const tag = c.gating ? chalk.dim('[gating]') : chalk.dim('[advisory]');
|
|
@@ -2353,7 +2380,7 @@ function main() {
|
|
|
2353
2380
|
root = process.cwd();
|
|
2354
2381
|
}
|
|
2355
2382
|
else {
|
|
2356
|
-
root = await pickRecentInitializedProject();
|
|
2383
|
+
root = await (await Promise.resolve().then(() => __importStar(require('../agents/recent-project')))).pickRecentInitializedProject();
|
|
2357
2384
|
}
|
|
2358
2385
|
const port = options.port ? parseInt(options.port, 10) : 4242;
|
|
2359
2386
|
const host = options.host ?? '127.0.0.1';
|
|
@@ -2394,7 +2421,7 @@ function main() {
|
|
|
2394
2421
|
console.error(chalk.green((0, glyphs_1.getGlyphs)().ok) + ` Dashboard: ${handle.url}/`);
|
|
2395
2422
|
}
|
|
2396
2423
|
if (options.ingest !== false) {
|
|
2397
|
-
console.error(chalk.dim(
|
|
2424
|
+
console.error(chalk.dim(` Transcript ingest watcher active (${agentNames()})`));
|
|
2398
2425
|
}
|
|
2399
2426
|
if (options.mcp) {
|
|
2400
2427
|
if (!root) {
|
|
@@ -2496,7 +2523,7 @@ function main() {
|
|
|
2496
2523
|
// Use stderr so stdout stays clean for any piped/stdio usage.
|
|
2497
2524
|
console.error(chalk.bold('\nSpecShip MCP Server\n'));
|
|
2498
2525
|
console.error(chalk.blue((0, glyphs_1.getGlyphs)().info) + ' Use --mcp flag to start the MCP server');
|
|
2499
|
-
console.error('\nTo use
|
|
2526
|
+
console.error('\nTo use it from your coding agent, add to its MCP configuration (or run `specship install --print-config --target <agent>`):');
|
|
2500
2527
|
console.error(chalk.dim(`
|
|
2501
2528
|
{
|
|
2502
2529
|
"mcpServers": {
|
|
@@ -2533,8 +2560,8 @@ function main() {
|
|
|
2533
2560
|
.option('-p, --path <path>', 'Project path (default: cwd, else your most recent initialized project)')
|
|
2534
2561
|
.option('--port <n>', 'HTTP port (default 4242)')
|
|
2535
2562
|
.option('--host <h>', 'HTTP bind host (default 127.0.0.1)')
|
|
2536
|
-
.option('--ingest', 'Enable the
|
|
2537
|
-
.option('--no-ingest', 'Disable the
|
|
2563
|
+
.option('--ingest', 'Enable the agent transcript watcher (every supported agent)', true)
|
|
2564
|
+
.option('--no-ingest', 'Disable the agent transcript watcher')
|
|
2538
2565
|
.option('--web-dir <path>', 'Path to a built desktop SPA (index.html lives here); auto-detected by default from the bundled ui/dist')
|
|
2539
2566
|
.option('--no-web', 'Run headless (API only, no dashboard SPA)')
|
|
2540
2567
|
.option('--no-watch', 'Disable the file watcher (no auto-sync; useful on slow filesystems like WSL2 /mnt drives)')
|
|
@@ -2938,7 +2965,7 @@ function main() {
|
|
|
2938
2965
|
*/
|
|
2939
2966
|
program
|
|
2940
2967
|
.command('install')
|
|
2941
|
-
.description('Install specship MCP server into
|
|
2968
|
+
.description('Install the specship MCP server into your coding agent (choose one with --target)')
|
|
2942
2969
|
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt (local)')
|
|
2943
2970
|
.option('-y, --yes', 'Non-interactive: defaults to --location=local (the only question an interactive install asks)')
|
|
2944
2971
|
.option('--no-permissions', 'Skip writing the auto-allow permissions list (written by default)')
|
|
@@ -2947,30 +2974,47 @@ function main() {
|
|
|
2947
2974
|
.option('--path <repo>', 'Target repo to wire and initialize (default: current directory). Project-local files and the .specship index land there')
|
|
2948
2975
|
.option('--with-jira', 'Enable the optional JIRA integration (talks to your Atlassian instance; off by default — the core install is 100% local)')
|
|
2949
2976
|
.option('--with-designer', 'Enable the optional Designer integration (EXPERIMENTAL — drives claude.ai/design via a debug Chrome session and may break without notice; off by default)')
|
|
2950
|
-
.option('--statusline', '(default) Wire the SpecShip status-line segment
|
|
2977
|
+
.option('--statusline', '(default; Claude-only surface) Wire the SpecShip status-line segment; never overwrites an existing status line')
|
|
2951
2978
|
.option('--skip-statusline', 'Do not add the status-line segment (it is added by default)')
|
|
2952
2979
|
.option('--skip-index', 'Do not index the current project (it is indexed by default)')
|
|
2953
|
-
.option('--print-config', 'Print the MCP config snippet and exit (no file writes)
|
|
2980
|
+
.option('--print-config', 'Print the target agent\'s MCP config snippet and exit (no file writes); pick the agent with --target')
|
|
2954
2981
|
// -t/--target SELECTS the agents to wire (GEMINI-TARGET-DOC, REQ-GEMINI-002)
|
|
2955
2982
|
// and also picks the snippet --print-config prints. The legacy spellings
|
|
2956
2983
|
// ("claude" / "auto" / "all" / "none") keep their Claude-only meanings, so
|
|
2957
2984
|
// existing invocations — including our own offline-install scripts — behave
|
|
2958
2985
|
// exactly as before.
|
|
2959
2986
|
.option('-t, --target <ids>', 'Agents to wire, comma-separated: "claude" (default) | "gemini" | "claude,gemini". Also selects the snippet `--print-config` prints. Legacy: "auto"/"all" = claude, "none" = skip')
|
|
2960
|
-
.option('--trust',
|
|
2987
|
+
.option('--trust', `Gemini only (REQ-GEMINI-009): write ~/${(0, hook_output_1.hostTraitsFor)('gemini').configDir}/policies/specship.toml auto-allowing SpecShip's read-only tools. Opt-in — an admin policy still wins and run_shell_command is unaffected`)
|
|
2988
|
+
// REQ-AGENTSWITCH-006.A3: reinstall for every recorded agent. Run by
|
|
2989
|
+
// `specship update` (through the new binary) and the bundle installer; exits
|
|
2990
|
+
// 3 when nothing is recorded so the bundle installer can fall back.
|
|
2991
|
+
.addOption(new commander_1.Option('--replay-recorded', 'Reinstall for every agent in the installed-agents record').hideHelp())
|
|
2961
2992
|
.action(async (opts) => {
|
|
2993
|
+
if (opts.replayRecorded) {
|
|
2994
|
+
const { reinstallRecordedAgents } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
2995
|
+
const reports = reinstallRecordedAgents();
|
|
2996
|
+
if (reports.length === 0) {
|
|
2997
|
+
info('No agents recorded — nothing to reinstall.');
|
|
2998
|
+
process.exit(3);
|
|
2999
|
+
}
|
|
3000
|
+
for (const r of reports) {
|
|
3001
|
+
if (r.status === 'reinstalled')
|
|
3002
|
+
success(r.detail);
|
|
3003
|
+
else
|
|
3004
|
+
warn(r.detail);
|
|
3005
|
+
}
|
|
3006
|
+
if (reports.some((r) => r.status === 'failed'))
|
|
3007
|
+
process.exit(1);
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
2962
3010
|
if (opts.printConfig) {
|
|
2963
3011
|
const loc = (opts.location === 'local' ? 'local' : 'global');
|
|
2964
|
-
//
|
|
2965
|
-
// (including the vestigial "auto" / "all" / "none")
|
|
2966
|
-
// existing invocations print exactly what they printed before.
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
return;
|
|
2971
|
-
}
|
|
2972
|
-
const { claudeTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/claude')));
|
|
2973
|
-
process.stdout.write(claudeTarget.printConfig(loc));
|
|
3012
|
+
// A registered target id prints that target's snippet; any other value
|
|
3013
|
+
// (including the vestigial "auto" / "all" / "none") prints the default
|
|
3014
|
+
// host's, so existing invocations print exactly what they printed before.
|
|
3015
|
+
const { getTarget } = await Promise.resolve().then(() => __importStar(require('../installer/targets/registry')));
|
|
3016
|
+
const target = getTarget(String(opts.target ?? '')) ?? getTarget(hook_output_1.DEFAULT_HOST_TRAITS.id);
|
|
3017
|
+
process.stdout.write(target.printConfig(loc));
|
|
2974
3018
|
return;
|
|
2975
3019
|
}
|
|
2976
3020
|
// Target repo (INSTALL-SCOPE-DOC, REQ-SCOPE-002): the project-local
|
|
@@ -3223,7 +3267,12 @@ function main() {
|
|
|
3223
3267
|
}
|
|
3224
3268
|
}
|
|
3225
3269
|
catch { /* store unavailable → omit the line */ }
|
|
3226
|
-
|
|
3270
|
+
// SessionStart is sent by every host, so the launcher env identifies the
|
|
3271
|
+
// agent; unknown → today's text (REQ-AGENTSWITCH-002).
|
|
3272
|
+
const { agentFromEnv, UNKNOWN_AGENT } = await Promise.resolve().then(() => __importStar(require('../agents/detect')));
|
|
3273
|
+
const agent = agentFromEnv(process.env);
|
|
3274
|
+
const traits = agent === UNKNOWN_AGENT ? undefined : (0, hook_output_1.hostTraitsFor)(agent);
|
|
3275
|
+
const payload = buildCheatsheetPayload(process.env, { conventions, openProposals }, traits);
|
|
3227
3276
|
if (payload)
|
|
3228
3277
|
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
3229
3278
|
process.exit(0);
|
|
@@ -3239,7 +3288,7 @@ function main() {
|
|
|
3239
3288
|
*/
|
|
3240
3289
|
program
|
|
3241
3290
|
.command('spec-nudge')
|
|
3242
|
-
.description(
|
|
3291
|
+
.description(`Internal hook: nudge toward spec-author on feature/bug intent (${hook_output_1.DEFAULT_HOST_TRAITS.fallbackPromptEvent})`)
|
|
3243
3292
|
.action(async () => {
|
|
3244
3293
|
const shouldNudge = (prompt) => {
|
|
3245
3294
|
const text = (prompt || '').trim();
|
|
@@ -3282,7 +3331,7 @@ function main() {
|
|
|
3282
3331
|
*/
|
|
3283
3332
|
program
|
|
3284
3333
|
.command('steer-nudge')
|
|
3285
|
-
.description(
|
|
3334
|
+
.description(`Internal hook: steer flow/structure questions to specship_explore (${hook_output_1.DEFAULT_HOST_TRAITS.fallbackPromptEvent})`)
|
|
3286
3335
|
.action(async () => {
|
|
3287
3336
|
// Prefer the hook payload's cwd (Claude Code / Gemini CLI both send it
|
|
3288
3337
|
// on stdin) so the check targets the project the prompt belongs to, not
|
|
@@ -3313,7 +3362,7 @@ function main() {
|
|
|
3313
3362
|
catch { /* telemetry must never break the hook */ }
|
|
3314
3363
|
}
|
|
3315
3364
|
const { buildSteeringNudge } = await Promise.resolve().then(() => __importStar(require('../activation/steering')));
|
|
3316
|
-
const additionalContext = buildSteeringNudge(cwd, process.env
|
|
3365
|
+
const additionalContext = buildSteeringNudge(cwd, traits, process.env);
|
|
3317
3366
|
if (additionalContext) {
|
|
3318
3367
|
process.stdout.write(buildHookAdditionalContext(additionalContext, payload.hookEventName, traits.fallbackPromptEvent));
|
|
3319
3368
|
}
|
|
@@ -3357,10 +3406,6 @@ function main() {
|
|
|
3357
3406
|
}
|
|
3358
3407
|
}
|
|
3359
3408
|
catch { /* not JSON — strict path stays off, advisory still works via payload */ }
|
|
3360
|
-
const DENY_TOOLS = new Set(traits.denyEligibleTools);
|
|
3361
|
-
const shellCommand = traits.shellTool !== null && toolName === traits.shellTool && typeof toolInput.command === 'string'
|
|
3362
|
-
? toolInput.command
|
|
3363
|
-
: null;
|
|
3364
3409
|
const { resolveSetting } = await Promise.resolve().then(() => __importStar(require('../config/runtime-settings')));
|
|
3365
3410
|
const strict = traits.supportsBeforeToolDeny &&
|
|
3366
3411
|
resolveSetting('SPECSHIP_GEMINI_STRICT', cwd, process.env, os.homedir()) === '1';
|
|
@@ -3372,11 +3417,10 @@ function main() {
|
|
|
3372
3417
|
cwd,
|
|
3373
3418
|
sessionId: payload.sessionId,
|
|
3374
3419
|
transcriptPath: payload.transcriptPath,
|
|
3375
|
-
|
|
3376
|
-
|
|
3420
|
+
traits,
|
|
3421
|
+
toolName,
|
|
3422
|
+
toolInput,
|
|
3377
3423
|
strict,
|
|
3378
|
-
denyEligible: DENY_TOOLS.has(toolName),
|
|
3379
|
-
shellCommand,
|
|
3380
3424
|
searchPattern,
|
|
3381
3425
|
});
|
|
3382
3426
|
if (result) {
|
|
@@ -3406,12 +3450,28 @@ function main() {
|
|
|
3406
3450
|
.description('Completely remove SpecShip (wiring, indexes, ~/.specship data, and the binary)')
|
|
3407
3451
|
.option('-y, --yes', 'Skip the confirmation prompt (also: --force)')
|
|
3408
3452
|
.option('-f, --force', 'Alias for --yes')
|
|
3409
|
-
.option('--keep-data', 'Only unwire from
|
|
3453
|
+
.option('--keep-data', 'Only unwire from the selected agent(s); keep the indexes, ~/.specship data, and the binary')
|
|
3410
3454
|
.option('-l, --location <where>', 'With --keep-data: which wiring to remove ("global" or "local")')
|
|
3411
|
-
|
|
3412
|
-
.option('
|
|
3455
|
+
.option('--local', 'Only unwire this project (same as --keep-data --location local); keeps indexes, data and the binary')
|
|
3456
|
+
.option('--global', 'Only unwire the user-wide install (same as --keep-data --location global); keeps indexes, data and the binary')
|
|
3457
|
+
.option('-t, --target <ids>', 'Agents to unwire: "claude" | "gemini" | "claude,gemini". With --keep-data/--local/--global the default is claude; a full purge with no --target unwires every agent')
|
|
3413
3458
|
.action(async (opts) => {
|
|
3414
3459
|
const { runUninstaller } = await Promise.resolve().then(() => __importStar(require('../installer')));
|
|
3460
|
+
// `--local` / `--global` (REQ-AGENTSWITCH-008): the undo command an
|
|
3461
|
+
// automatic agent setup prints — wiring-only, never the full purge.
|
|
3462
|
+
if (opts.local && opts.global) {
|
|
3463
|
+
error('Use only one of --local and --global.');
|
|
3464
|
+
process.exit(1);
|
|
3465
|
+
}
|
|
3466
|
+
if (opts.local || opts.global) {
|
|
3467
|
+
const where = opts.local ? 'local' : 'global';
|
|
3468
|
+
if (opts.location && opts.location !== where) {
|
|
3469
|
+
error(`--${where} conflicts with --location ${opts.location}.`);
|
|
3470
|
+
process.exit(1);
|
|
3471
|
+
}
|
|
3472
|
+
opts.location = where;
|
|
3473
|
+
opts.keepData = true;
|
|
3474
|
+
}
|
|
3415
3475
|
if (opts.location && opts.location !== 'global' && opts.location !== 'local') {
|
|
3416
3476
|
error(`--location must be "global" or "local" (got "${opts.location}").`);
|
|
3417
3477
|
process.exit(1);
|
|
@@ -4331,6 +4391,9 @@ function main() {
|
|
|
4331
4391
|
detect: () => (0, updater_1.detectInstallMethod)(__dirname, installDir),
|
|
4332
4392
|
resolveLatest: (method) => (0, resolve_latest_1.resolveLatestVersion)(method),
|
|
4333
4393
|
runInstaller: (method) => (0, run_installer_1.runInstaller)(method, { installDir, binDir }),
|
|
4394
|
+
// REQ-AGENTSWITCH-006.A3: the old code is still loaded in this
|
|
4395
|
+
// process, so the reinstall runs through the NEW binary.
|
|
4396
|
+
reinstallAgents: () => replayRecordedAgents((0, updater_1.detectInstallMethod)(__dirname, installDir), binDir),
|
|
4334
4397
|
installDir,
|
|
4335
4398
|
binDir,
|
|
4336
4399
|
}, { check: options.check });
|
|
@@ -4343,6 +4406,25 @@ function main() {
|
|
|
4343
4406
|
if (result.exitCode !== 0)
|
|
4344
4407
|
process.exit(result.exitCode);
|
|
4345
4408
|
});
|
|
4409
|
+
/**
|
|
4410
|
+
* Run `specship install --replay-recorded` on the freshly installed binary and
|
|
4411
|
+
* return its output lines. Exit 3 means nothing was recorded.
|
|
4412
|
+
*/
|
|
4413
|
+
function replayRecordedAgents(method, binDir) {
|
|
4414
|
+
const bundleLink = path.join(binDir, 'specship');
|
|
4415
|
+
const bin = method === 'bundle' && fs.existsSync(bundleLink) ? bundleLink : 'specship';
|
|
4416
|
+
return new Promise((resolve, reject) => {
|
|
4417
|
+
const { execFile } = require('child_process');
|
|
4418
|
+
execFile(bin, ['install', '--replay-recorded'], { encoding: 'utf8', shell: process.platform === 'win32', windowsHide: true }, (err, stdout) => {
|
|
4419
|
+
const lines = String(stdout ?? '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
|
|
4420
|
+
const code = err?.code;
|
|
4421
|
+
if (!err || code === 3)
|
|
4422
|
+
resolve(lines);
|
|
4423
|
+
else
|
|
4424
|
+
reject(new Error(lines.at(-1) ?? err.message));
|
|
4425
|
+
});
|
|
4426
|
+
});
|
|
4427
|
+
}
|
|
4346
4428
|
/**
|
|
4347
4429
|
* specship jira — connect SpecShip to a JIRA instance (REQ-JIRA-001).
|
|
4348
4430
|
*
|