@specship/specship-win32-arm64 0.11.8 → 0.12.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/bin/specship.js +141 -66
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/commands/specship/check.md +5 -0
- package/lib/dist/commands/specship/spec.md +235 -15
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +1 -0
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/enforce/enforce.d.ts +15 -0
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +43 -1
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +6 -0
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/index.d.ts +9 -1
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +53 -9
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +35 -0
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/instructions-template.d.ts +2 -2
- package/lib/dist/installer/instructions-template.js +3 -3
- package/lib/dist/installer/targets/claude.d.ts +10 -0
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +33 -7
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/mcp/designer-tools.js +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +1 -1
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +1 -1
- package/lib/dist/mcp/spec-tools.d.ts +10 -0
- package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
- package/lib/dist/mcp/spec-tools.js +228 -2
- package/lib/dist/mcp/spec-tools.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +12 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +34 -1
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/reflect/index.d.ts +1 -1
- package/lib/dist/reflect/index.d.ts.map +1 -1
- package/lib/dist/reflect/index.js +2 -1
- package/lib/dist/reflect/index.js.map +1 -1
- package/lib/dist/reflect/miner.d.ts +8 -0
- package/lib/dist/reflect/miner.d.ts.map +1 -1
- package/lib/dist/reflect/miner.js +64 -36
- package/lib/dist/reflect/miner.js.map +1 -1
- package/lib/dist/resolution/brief-link-resolver.d.ts +20 -0
- package/lib/dist/resolution/brief-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/brief-link-resolver.js +65 -1
- package/lib/dist/resolution/brief-link-resolver.js.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +16 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +19 -1
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/server/cli.js +5 -49
- package/lib/dist/server/ingest/ingestor.js +17 -17
- package/lib/dist/server/ingest/pricing-backfill.js +55 -0
- package/lib/dist/server/ingest/pricing.js +3 -0
- package/lib/dist/server/ingest/project-paths.js +152 -0
- package/lib/dist/server/project-registry.js +15 -2
- package/lib/dist/server/routes/claude.js +47 -18
- package/lib/dist/server/routes/events.js +20 -1
- package/lib/dist/server/routes/projects.js +6 -3
- package/lib/dist/server/routes/spec.js +22 -2
- package/lib/dist/server/server.js +38 -4
- package/lib/dist/server/ssr/md.mjs +83 -0
- package/lib/dist/server/ssr/public/app.css +1 -0
- package/lib/dist/server/ssr/render.mjs +564 -0
- package/lib/dist/server/ssr/routes.js +73 -0
- package/lib/dist/server/static-handler.js +30 -0
- package/lib/dist/workflows/defaults/claude-design-implement.yaml +3 -3
- package/lib/dist/workflows/defaults/spec-implement.yaml +41 -9
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +3 -3
- package/package.json +1 -1
- package/lib/dist/commands/specship/design-implement.md +0 -84
- package/lib/dist/commands/specship/design-loop.md +0 -125
- package/lib/dist/commands/ss-behaviour.md +0 -116
- package/lib/dist/web/chunk-2AJCHB7P.js +0 -1
- package/lib/dist/web/chunk-2CPLUFCH.js +0 -2
- package/lib/dist/web/chunk-2I7L37NS.js +0 -1
- package/lib/dist/web/chunk-2IVSOVQZ.js +0 -1
- package/lib/dist/web/chunk-2NAWAJB5.js +0 -1
- package/lib/dist/web/chunk-2OJBIPE4.js +0 -1
- package/lib/dist/web/chunk-3E2WB6D5.js +0 -1
- package/lib/dist/web/chunk-3EBFYSCH.js +0 -2
- package/lib/dist/web/chunk-3QCQ4BXS.js +0 -1
- package/lib/dist/web/chunk-42XVAQ6I.js +0 -1
- package/lib/dist/web/chunk-4G7XU55L.js +0 -1
- package/lib/dist/web/chunk-4IMMPEYM.js +0 -1
- package/lib/dist/web/chunk-4TJQJPCZ.js +0 -1
- package/lib/dist/web/chunk-4WZIHTPC.js +0 -1
- package/lib/dist/web/chunk-4YVSYOSD.js +0 -1
- package/lib/dist/web/chunk-5BQIOYKW.js +0 -1
- package/lib/dist/web/chunk-5HGWHUJA.js +0 -1
- package/lib/dist/web/chunk-5NTGUSXJ.js +0 -1
- package/lib/dist/web/chunk-5Y244R4G.js +0 -1
- package/lib/dist/web/chunk-6RRDPT5Z.js +0 -1
- package/lib/dist/web/chunk-6VKB2ZWM.js +0 -1
- package/lib/dist/web/chunk-7656MA24.js +0 -1
- package/lib/dist/web/chunk-7DMFVTU4.js +0 -1
- package/lib/dist/web/chunk-7KWBINA7.js +0 -1
- package/lib/dist/web/chunk-7MUNFW56.js +0 -1
- package/lib/dist/web/chunk-7SMPKVEP.js +0 -1
- package/lib/dist/web/chunk-AZJVTPLU.js +0 -1
- package/lib/dist/web/chunk-AZUUBUEJ.js +0 -1
- package/lib/dist/web/chunk-BLBRMCN2.js +0 -1
- package/lib/dist/web/chunk-BMIAXD2V.js +0 -2
- package/lib/dist/web/chunk-BPCJLNBS.js +0 -47
- package/lib/dist/web/chunk-BUXWEHIY.js +0 -1
- package/lib/dist/web/chunk-CYXQILZI.js +0 -1
- package/lib/dist/web/chunk-DLQPZWSI.css +0 -1
- package/lib/dist/web/chunk-DOKZOFKM.js +0 -1
- package/lib/dist/web/chunk-DTWIJ3GJ.js +0 -1
- package/lib/dist/web/chunk-DYRFLPJA.js +0 -1
- package/lib/dist/web/chunk-E27QAKAB.js +0 -1
- package/lib/dist/web/chunk-E3J3CXR5.js +0 -1
- package/lib/dist/web/chunk-E73OX2P7.js +0 -1
- package/lib/dist/web/chunk-EAXRKDLV.js +0 -1
- package/lib/dist/web/chunk-EBKKDHYI.js +0 -1
- package/lib/dist/web/chunk-EE7V7Q5P.js +0 -1
- package/lib/dist/web/chunk-EKY2FUHU.js +0 -1
- package/lib/dist/web/chunk-EP6XOPXH.js +0 -1
- package/lib/dist/web/chunk-ESGDLJOJ.js +0 -1
- package/lib/dist/web/chunk-ETJG7NCY.js +0 -1
- package/lib/dist/web/chunk-EUUEFEDI.js +0 -1
- package/lib/dist/web/chunk-EXBUKHH2.js +0 -17
- package/lib/dist/web/chunk-F5TBUYDH.js +0 -1
- package/lib/dist/web/chunk-FC3R7YHR.js +0 -4
- package/lib/dist/web/chunk-FGNZDHTL.js +0 -11
- package/lib/dist/web/chunk-FIJW2UNJ.js +0 -1
- package/lib/dist/web/chunk-FMV5PXRC.js +0 -5
- package/lib/dist/web/chunk-G7RXFHYO.js +0 -1
- package/lib/dist/web/chunk-G7VZT5KB.js +0 -3
- package/lib/dist/web/chunk-GRZYXPSO.js +0 -7
- package/lib/dist/web/chunk-GYGPS3AN.js +0 -1
- package/lib/dist/web/chunk-H723TYYD.js +0 -2
- package/lib/dist/web/chunk-H7AF7YS4.js +0 -1
- package/lib/dist/web/chunk-HDZDQILN.js +0 -1
- package/lib/dist/web/chunk-HMK6UO6N.js +0 -1
- package/lib/dist/web/chunk-HSJNHNOH.js +0 -6
- package/lib/dist/web/chunk-IHEE5NYJ.js +0 -1
- package/lib/dist/web/chunk-IOB4JJT4.js +0 -1
- package/lib/dist/web/chunk-ISNEBICW.js +0 -1
- package/lib/dist/web/chunk-J2GZVLHH.js +0 -1
- package/lib/dist/web/chunk-JGDK6YKG.js +0 -1
- package/lib/dist/web/chunk-JLOJCDIY.js +0 -1
- package/lib/dist/web/chunk-JPSYO24K.js +0 -1
- package/lib/dist/web/chunk-JTFXTIPE.js +0 -903
- package/lib/dist/web/chunk-K7UD24Z4.js +0 -1
- package/lib/dist/web/chunk-KYSTCHAH.js +0 -1
- package/lib/dist/web/chunk-LB6JPLX2.js +0 -1
- package/lib/dist/web/chunk-LNSVDHCI.js +0 -1
- package/lib/dist/web/chunk-LVGIY3SO.js +0 -1
- package/lib/dist/web/chunk-LXLHIHEN.js +0 -1
- package/lib/dist/web/chunk-MNFTHCSJ.js +0 -1
- package/lib/dist/web/chunk-N6SS4G6S.js +0 -1
- package/lib/dist/web/chunk-NKPK3BR3.js +0 -1
- package/lib/dist/web/chunk-NTBJG6SJ.js +0 -1
- package/lib/dist/web/chunk-NUDB3Q2Y.js +0 -3
- package/lib/dist/web/chunk-OTQR5VFM.js +0 -1
- package/lib/dist/web/chunk-OXEF5E3E.js +0 -1
- package/lib/dist/web/chunk-P62EJGSF.js +0 -1
- package/lib/dist/web/chunk-PCW6R7OP.js +0 -1
- package/lib/dist/web/chunk-PGGJPDJG.js +0 -1
- package/lib/dist/web/chunk-POCVU4VK.js +0 -2
- package/lib/dist/web/chunk-PS6HRYQQ.js +0 -3
- package/lib/dist/web/chunk-PUYSJNJR.js +0 -1
- package/lib/dist/web/chunk-PZSU53SS.js +0 -1
- package/lib/dist/web/chunk-Q2RVFS45.js +0 -1
- package/lib/dist/web/chunk-Q7L6LLAK.js +0 -1
- package/lib/dist/web/chunk-QCMKJIWY.js +0 -1
- package/lib/dist/web/chunk-QH6CF3M3.js +0 -1
- package/lib/dist/web/chunk-QIZ65CGB.js +0 -1
- package/lib/dist/web/chunk-QPA2REUE.js +0 -1
- package/lib/dist/web/chunk-QQ5LD7PI.js +0 -1
- package/lib/dist/web/chunk-QR6L3KAC.js +0 -1
- package/lib/dist/web/chunk-R2DLK4HO.js +0 -1
- package/lib/dist/web/chunk-RD6TVPOT.js +0 -1
- package/lib/dist/web/chunk-RKY4EJYJ.js +0 -1
- package/lib/dist/web/chunk-RONYWVY7.js +0 -1
- package/lib/dist/web/chunk-RXKXYF2C.js +0 -1
- package/lib/dist/web/chunk-SQPYSOV6.js +0 -1
- package/lib/dist/web/chunk-SWKJRNYY.js +0 -1
- package/lib/dist/web/chunk-SYGIPC2P.js +0 -4
- package/lib/dist/web/chunk-T7AZ65JP.js +0 -1
- package/lib/dist/web/chunk-TCZDVOHD.js +0 -1
- package/lib/dist/web/chunk-TPTITA3V.js +0 -1
- package/lib/dist/web/chunk-TR335633.js +0 -1
- package/lib/dist/web/chunk-TWVVIWKO.js +0 -1
- package/lib/dist/web/chunk-U67LRNER.js +0 -1
- package/lib/dist/web/chunk-UR5KDXPX.js +0 -1
- package/lib/dist/web/chunk-UR6O2GEH.js +0 -1
- package/lib/dist/web/chunk-USVLLFVR.js +0 -1
- package/lib/dist/web/chunk-UTNMGWTP.js +0 -1
- package/lib/dist/web/chunk-V74ZPP4B.js +0 -1
- package/lib/dist/web/chunk-VECWMHJP.js +0 -1
- package/lib/dist/web/chunk-VUACT35R.js +0 -3
- package/lib/dist/web/chunk-VZI7H4SZ.js +0 -1
- package/lib/dist/web/chunk-WB6YHOD4.js +0 -1
- package/lib/dist/web/chunk-WBJ5VEB5.js +0 -1
- package/lib/dist/web/chunk-WBT64AWV.js +0 -1
- package/lib/dist/web/chunk-WFXJIXZE.js +0 -4
- package/lib/dist/web/chunk-WTGYRH3Z.js +0 -298
- package/lib/dist/web/chunk-WXTCVDTP.js +0 -1
- package/lib/dist/web/chunk-XAUQEKZX.js +0 -1
- package/lib/dist/web/chunk-XCDHWLVH.js +0 -1
- package/lib/dist/web/chunk-Y3H6FFUZ.js +0 -1
- package/lib/dist/web/chunk-Y4F5ULGJ.js +0 -1
- package/lib/dist/web/chunk-YEGKAAEE.js +0 -1
- package/lib/dist/web/chunk-YM2KU57F.js +0 -1
- package/lib/dist/web/chunk-YNYVOPWF.js +0 -1
- package/lib/dist/web/chunk-YRERBP6T.js +0 -1
- package/lib/dist/web/chunk-YV5LB22R.js +0 -1
- package/lib/dist/web/chunk-YWIRU745.js +0 -1
- package/lib/dist/web/chunk-YXYPTZ6E.js +0 -1
- package/lib/dist/web/chunk-ZLV4VCDG.js +0 -3
- package/lib/dist/web/chunk-ZTVI5KFF.js +0 -1
- package/lib/dist/web/favicon-16.png +0 -0
- package/lib/dist/web/favicon-180.png +0 -0
- package/lib/dist/web/favicon-32.png +0 -0
- package/lib/dist/web/favicon-512.png +0 -0
- package/lib/dist/web/favicon-small.svg +0 -15
- package/lib/dist/web/favicon.ico +0 -0
- package/lib/dist/web/favicon.svg +0 -20
- package/lib/dist/web/icon-192.png +0 -0
- package/lib/dist/web/icon-512.png +0 -0
- package/lib/dist/web/index.html +0 -146
- package/lib/dist/web/main-ESADRXN2.css +0 -1
- package/lib/dist/web/main-FY3HQ4PX.js +0 -1
- package/lib/dist/web/manifest.webmanifest +0 -15
- package/lib/dist/web/media/codicon-LN6W7LCM.ttf +0 -0
- package/lib/dist/web/styles-KSOPUVDA.css +0 -1
- package/lib/dist/web/sw.js +0 -69
package/lib/dist/bin/specship.js
CHANGED
|
@@ -115,11 +115,79 @@ async function loadServerPackage() {
|
|
|
115
115
|
* The JSONL ingest watcher now ships inside `@specship/specship-server`
|
|
116
116
|
* itself — `createServer({ ingest: true })` starts it. No separate package.
|
|
117
117
|
*/
|
|
118
|
+
/**
|
|
119
|
+
* Claude Code's slug encoding (every non-alphanumeric char → '-') is lossy,
|
|
120
|
+
* so decoding by mapping '-' back to '/' mangles any path containing
|
|
121
|
+
* hyphens/dots/underscores. Recover real paths from `~/.claude.json` (its
|
|
122
|
+
* `projects` object is keyed by real absolute paths), then from a `"cwd"`
|
|
123
|
+
* value in the slug's newest transcript, before falling back to the lossy
|
|
124
|
+
* decode. Mirrors `packages/server/src/ingest/project-paths.ts`
|
|
125
|
+
* (REQ-SLUGRES-004) — the server package depends on this one, so the CLI
|
|
126
|
+
* can't import it.
|
|
127
|
+
*/
|
|
128
|
+
function buildClaudeSlugIndex() {
|
|
129
|
+
const bySlug = new Map();
|
|
130
|
+
try {
|
|
131
|
+
const raw = fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8');
|
|
132
|
+
const parsed = JSON.parse(raw);
|
|
133
|
+
for (const realPath of Object.keys(parsed.projects ?? {})) {
|
|
134
|
+
bySlug.set(realPath.replace(/[^A-Za-z0-9]/g, '-'), realPath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch { /* missing or malformed — sniff/fallback below still work */ }
|
|
138
|
+
return bySlug;
|
|
139
|
+
}
|
|
140
|
+
function sniffClaudeSlugCwd(claudeRoot, slug) {
|
|
141
|
+
const dir = path.join(claudeRoot, slug);
|
|
142
|
+
let newest = null;
|
|
143
|
+
try {
|
|
144
|
+
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
145
|
+
if (!f.isFile() || !f.name.toLowerCase().endsWith('.jsonl'))
|
|
146
|
+
continue;
|
|
147
|
+
const st = fs.statSync(path.join(dir, f.name));
|
|
148
|
+
if (!newest || st.mtimeMs > newest.mtimeMs)
|
|
149
|
+
newest = { file: f.name, mtimeMs: st.mtimeMs };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
if (!newest)
|
|
156
|
+
return null;
|
|
157
|
+
try {
|
|
158
|
+
const fd = fs.openSync(path.join(dir, newest.file), 'r');
|
|
159
|
+
let chunk;
|
|
160
|
+
try {
|
|
161
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
162
|
+
const read = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
163
|
+
chunk = buf.toString('utf8', 0, read);
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
fs.closeSync(fd);
|
|
167
|
+
}
|
|
168
|
+
const m = /"cwd"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(chunk);
|
|
169
|
+
if (!m)
|
|
170
|
+
return null;
|
|
171
|
+
let cwd = JSON.parse(`"${m[1]}"`);
|
|
172
|
+
// Accept the cwd or an ancestor of it — sessions may record a subdir.
|
|
173
|
+
for (;;) {
|
|
174
|
+
if (cwd.replace(/[^A-Za-z0-9]/g, '-') === slug)
|
|
175
|
+
return cwd;
|
|
176
|
+
const parent = path.dirname(cwd);
|
|
177
|
+
if (parent === cwd)
|
|
178
|
+
return null;
|
|
179
|
+
cwd = parent;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
118
186
|
/**
|
|
119
187
|
* Auto-pick a default project for `specship serve --ui` when the user
|
|
120
188
|
* didn't pass `-p` and the cwd isn't initialized either. Walks
|
|
121
189
|
* `~/.claude/projects/*` (Claude Code's per-project transcript dirs),
|
|
122
|
-
*
|
|
190
|
+
* resolves each slug back to a real path, and returns the most-recently-
|
|
123
191
|
* touched one that's still on disk AND has been `specship init`'d.
|
|
124
192
|
*
|
|
125
193
|
* Returns null when nothing qualifies — the server boots projectless and
|
|
@@ -134,13 +202,15 @@ async function pickRecentInitializedProject() {
|
|
|
134
202
|
catch {
|
|
135
203
|
return null;
|
|
136
204
|
}
|
|
205
|
+
const slugIndex = buildClaudeSlugIndex();
|
|
137
206
|
const candidates = [];
|
|
138
207
|
for (const ent of entries) {
|
|
139
208
|
if (!ent.isDirectory())
|
|
140
209
|
continue;
|
|
141
210
|
const slug = ent.name;
|
|
142
|
-
|
|
143
|
-
|
|
211
|
+
const decoded = slugIndex.get(slug) ??
|
|
212
|
+
sniffClaudeSlugCwd(claudeRoot, slug) ??
|
|
213
|
+
(slug.startsWith('-') ? '/' + slug.slice(1).replace(/-/g, '/') : slug);
|
|
144
214
|
try {
|
|
145
215
|
const st = fs.statSync(decoded);
|
|
146
216
|
if (!st.isDirectory())
|
|
@@ -172,54 +242,6 @@ async function pickRecentInitializedProject() {
|
|
|
172
242
|
candidates.sort((a, b) => b.lastTouched - a.lastTouched);
|
|
173
243
|
return candidates[0]?.decoded ?? null;
|
|
174
244
|
}
|
|
175
|
-
/**
|
|
176
|
-
* Locate the built Angular UI for `specship serve --ui`. Mirrors the
|
|
177
|
-
* lookup order in `packages/server/src/cli.ts:locateWebDir` so both entry
|
|
178
|
-
* points behave identically. Returns null when nothing is found — the
|
|
179
|
-
* server then runs API-only.
|
|
180
|
-
*/
|
|
181
|
-
function locateWebDir(explicit) {
|
|
182
|
-
const envDir = process.env.SPECSHIP_WEB_DIR ?? process.env.SPECSHIP_WEB_DIR;
|
|
183
|
-
const candidates = [];
|
|
184
|
-
if (explicit)
|
|
185
|
-
candidates.push(path.resolve(explicit));
|
|
186
|
-
if (envDir)
|
|
187
|
-
candidates.push(path.resolve(envDir));
|
|
188
|
-
// 1. Bundled mode — `dist/web/` next to this CLI. This is what every
|
|
189
|
-
// `npm i -g @specship/specship` install hits.
|
|
190
|
-
candidates.push(path.resolve(__dirname, '..', 'web'));
|
|
191
|
-
// 2. Legacy / forward-compat: the old `@specship/specship-server`
|
|
192
|
-
// used to ship the SPA at <pkg>/public/web.
|
|
193
|
-
try {
|
|
194
|
-
const pkgJson = require.resolve('@specship/specship-server/package.json');
|
|
195
|
-
candidates.push(path.resolve(path.dirname(pkgJson), 'public', 'web'));
|
|
196
|
-
}
|
|
197
|
-
catch { /* not installed as separate package — fall through */ }
|
|
198
|
-
// 3. Dev monorepo layouts (running from a checkout).
|
|
199
|
-
candidates.push(path.resolve(__dirname, '..', '..', 'packages', 'server', 'public', 'web'));
|
|
200
|
-
candidates.push(path.resolve(__dirname, '..', '..', '..', 'packages', 'server', 'public', 'web'));
|
|
201
|
-
candidates.push(path.resolve(__dirname, '..', '..', 'packages', 'web-ng', 'dist', 'web-ng', 'browser'));
|
|
202
|
-
candidates.push(path.resolve(__dirname, '..', '..', '..', 'packages', 'web-ng', 'dist', 'web-ng', 'browser'));
|
|
203
|
-
// 4. Walk up from cwd in case the bin is run inside a checkout.
|
|
204
|
-
let cur = process.cwd();
|
|
205
|
-
for (let depth = 0; depth < 6; depth++) {
|
|
206
|
-
candidates.push(path.join(cur, 'dist', 'web'));
|
|
207
|
-
candidates.push(path.join(cur, 'packages', 'server', 'public', 'web'));
|
|
208
|
-
candidates.push(path.join(cur, 'packages', 'web-ng', 'dist', 'web-ng', 'browser'));
|
|
209
|
-
const parent = path.dirname(cur);
|
|
210
|
-
if (parent === cur)
|
|
211
|
-
break;
|
|
212
|
-
cur = parent;
|
|
213
|
-
}
|
|
214
|
-
for (const c of candidates) {
|
|
215
|
-
try {
|
|
216
|
-
if (fs.existsSync(path.join(c, 'index.html')))
|
|
217
|
-
return c;
|
|
218
|
-
}
|
|
219
|
-
catch { /* ignore */ }
|
|
220
|
-
}
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
245
|
// Dynamic import helper — tsc compiles import() to require() in CJS mode,
|
|
224
246
|
// which fails for ESM-only packages. This bypasses the transformation.
|
|
225
247
|
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
@@ -769,9 +791,28 @@ function main() {
|
|
|
769
791
|
program
|
|
770
792
|
.command('sync [path]')
|
|
771
793
|
.description('Sync changes since last index')
|
|
772
|
-
.option('-q, --quiet', 'Suppress output (for git hooks)')
|
|
794
|
+
.option('-q, --quiet', 'Suppress output (for git hooks); drift-transition notices still print')
|
|
795
|
+
.option('--drift-summary', 'After syncing, print a one-line drifted-link summary when the queue is non-empty (for the SessionStart hook)')
|
|
773
796
|
.action(async (pathArg, options) => {
|
|
774
797
|
const projectPath = resolveProjectPath(pathArg);
|
|
798
|
+
// One line per link that TRANSITIONED into drifted this sync
|
|
799
|
+
// (REQ-DRIFT-PUSH-001). Deliberately printed even under --quiet — the
|
|
800
|
+
// auto-sync hook runs with --quiet, and these notices are its payload.
|
|
801
|
+
const pushDriftNotices = (result) => {
|
|
802
|
+
for (const t of result.driftedTransitions ?? []) {
|
|
803
|
+
console.log(`⚠ spec drift: ${t.specId} ${t.fromState}→drifted (${t.symbol} ${t.axis === 'spec' ? 'spec changed' : 'changed'}) — ` +
|
|
804
|
+
`re-assert with specship_link_assert, or run /specship:check fix ${t.specId}`);
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
// One-line drift-queue summary, only when non-empty (REQ-DRIFT-PUSH-002).
|
|
808
|
+
const pushDriftSummary = (cg) => {
|
|
809
|
+
if (!options.driftSummary)
|
|
810
|
+
return;
|
|
811
|
+
const drifted = cg.getSpecQueries().getLinksByState(['drifted']).length;
|
|
812
|
+
if (drifted > 0) {
|
|
813
|
+
console.log(`⚠ ${drifted} spec link(s) drifted — review with /specship:check drifted`);
|
|
814
|
+
}
|
|
815
|
+
};
|
|
775
816
|
try {
|
|
776
817
|
if (!(0, directory_1.isInitialized)(projectPath)) {
|
|
777
818
|
if (!options.quiet) {
|
|
@@ -782,7 +823,9 @@ function main() {
|
|
|
782
823
|
const { default: SpecShip } = await loadSpecShip();
|
|
783
824
|
const cg = await SpecShip.open(projectPath);
|
|
784
825
|
if (options.quiet) {
|
|
785
|
-
await cg.sync();
|
|
826
|
+
const result = await cg.sync();
|
|
827
|
+
pushDriftNotices(result);
|
|
828
|
+
pushDriftSummary(cg);
|
|
786
829
|
cg.destroy();
|
|
787
830
|
return;
|
|
788
831
|
}
|
|
@@ -809,6 +852,8 @@ function main() {
|
|
|
809
852
|
details.push(`Removed: ${result.filesRemoved}`);
|
|
810
853
|
clack.log.info(`${details.join(', ')} ${(0, glyphs_1.getGlyphs)().dash} ${formatNumber(result.nodesUpdated)} nodes in ${formatDuration(result.durationMs)}`);
|
|
811
854
|
}
|
|
855
|
+
pushDriftNotices(result);
|
|
856
|
+
pushDriftSummary(cg);
|
|
812
857
|
clack.outro('Done');
|
|
813
858
|
cg.destroy();
|
|
814
859
|
}
|
|
@@ -1450,11 +1495,18 @@ function main() {
|
|
|
1450
1495
|
* GATING check fails. Which checks gate vs advise is configured in
|
|
1451
1496
|
* specship.config.json `enforce.gate`; with no config every check is advisory
|
|
1452
1497
|
* and the command exits 0 (opt-in — never breaks an existing repo).
|
|
1498
|
+
*
|
|
1499
|
+
* The graduation ramp (REQ-ENFORCE-004): `--strict` gates every check for
|
|
1500
|
+
* one run; `--enable-gate <checks...>` persists gating into the config and
|
|
1501
|
+
* then runs; an advisory run with would-be-gating findings ends by printing
|
|
1502
|
+
* the exact `--enable-gate` command that turns the gate on.
|
|
1453
1503
|
*/
|
|
1454
1504
|
program
|
|
1455
1505
|
.command('check [path]')
|
|
1456
1506
|
.description('Run the enforcement gate (drift + fitness + maintainability + behaviour); exits non-zero on a gating failure')
|
|
1457
1507
|
.option('-j, --json', 'Output as JSON')
|
|
1508
|
+
.option('--strict', 'Treat every check as gating for this run only (nothing read from or written to config)')
|
|
1509
|
+
.option('--enable-gate <checks...>', 'Persist gating for the named checks into specship.config.json, then run the gate')
|
|
1458
1510
|
.action(async (pathArg, options) => {
|
|
1459
1511
|
const projectPath = resolveProjectPath(pathArg);
|
|
1460
1512
|
try {
|
|
@@ -1462,9 +1514,25 @@ function main() {
|
|
|
1462
1514
|
error(`SpecShip not initialized in ${projectPath}`);
|
|
1463
1515
|
process.exit(1);
|
|
1464
1516
|
}
|
|
1517
|
+
const enforce = await Promise.resolve().then(() => __importStar(require('../enforce/enforce')));
|
|
1518
|
+
// --enable-gate writes the opt-in config, then falls through to a
|
|
1519
|
+
// normal run so the user immediately sees the gate with teeth
|
|
1520
|
+
// (REQ-ENFORCE-004.A1).
|
|
1521
|
+
if (options.enableGate?.length) {
|
|
1522
|
+
const invalid = options.enableGate.filter((c) => !enforce.ALL_CHECKS.includes(c));
|
|
1523
|
+
if (invalid.length) {
|
|
1524
|
+
error(`Unknown check(s): ${invalid.join(', ')}. Valid: ${enforce.ALL_CHECKS.join(', ')}`);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
const enabled = enforce.enableGateChecks(projectPath, options.enableGate);
|
|
1528
|
+
if (enabled.length) {
|
|
1529
|
+
success(`Gating enabled for ${enabled.join(', ')} — written to specship.config.json.`);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1465
1532
|
const { default: SpecShip } = await loadSpecShip();
|
|
1466
1533
|
const cg = await SpecShip.open(projectPath);
|
|
1467
|
-
|
|
1534
|
+
// --strict gates everything for this run only (REQ-ENFORCE-004.A2).
|
|
1535
|
+
const r = options.strict ? cg.getEnforce(enforce.strictEnforceConfig()) : cg.getEnforce();
|
|
1468
1536
|
if (options.json) {
|
|
1469
1537
|
console.log(JSON.stringify(r, null, 2));
|
|
1470
1538
|
cg.destroy();
|
|
@@ -1486,6 +1554,16 @@ function main() {
|
|
|
1486
1554
|
success(r.gatedFailures.length === 0 && r.checks.some((c) => c.gating)
|
|
1487
1555
|
? 'All gating checks pass.'
|
|
1488
1556
|
: 'Pass (no gating checks failed).');
|
|
1557
|
+
// The advisory sell (REQ-ENFORCE-004.A1): findings that would fail
|
|
1558
|
+
// a gated run end the report with the exact opt-in command.
|
|
1559
|
+
const wouldGate = r.checks.filter((c) => !c.gating && !c.passed).map((c) => c.check);
|
|
1560
|
+
if (wouldGate.length) {
|
|
1561
|
+
const total = r.checks
|
|
1562
|
+
.filter((c) => wouldGate.includes(c.check))
|
|
1563
|
+
.reduce((n, c) => n + c.findings.length, 0);
|
|
1564
|
+
console.log(chalk.dim(`\n ${total} advisory finding(s) would fail a gated run.` +
|
|
1565
|
+
`\n Enable: specship check --enable-gate ${wouldGate.join(' ')}\n`));
|
|
1566
|
+
}
|
|
1489
1567
|
cg.destroy();
|
|
1490
1568
|
return;
|
|
1491
1569
|
}
|
|
@@ -1555,10 +1633,10 @@ function main() {
|
|
|
1555
1633
|
// node_modules/@specship/specship-server, OR (dev) the
|
|
1556
1634
|
// sibling packages/server/dist directory.
|
|
1557
1635
|
const { createServer } = await loadServerPackage();
|
|
1558
|
-
//
|
|
1559
|
-
//
|
|
1560
|
-
//
|
|
1561
|
-
const
|
|
1636
|
+
// The dashboard is the lean server-rendered UI (DASH-LEAN-DOC), served
|
|
1637
|
+
// in-process by the server itself — no separate SPA build. `--no-web`
|
|
1638
|
+
// still opts out to run API-only.
|
|
1639
|
+
const serveUi = options.web !== false;
|
|
1562
1640
|
// The JSONL ingest watcher now starts in-process inside createServer
|
|
1563
1641
|
// when `ingest: true`. The server owns its lifecycle; CLI just toggles.
|
|
1564
1642
|
const handle = await createServer({
|
|
@@ -1566,7 +1644,8 @@ function main() {
|
|
|
1566
1644
|
host,
|
|
1567
1645
|
port,
|
|
1568
1646
|
ingest: options.ingest !== false,
|
|
1569
|
-
webDir,
|
|
1647
|
+
webDir: null,
|
|
1648
|
+
ssr: serveUi,
|
|
1570
1649
|
verbose: false,
|
|
1571
1650
|
});
|
|
1572
1651
|
console.error(chalk.bold('\nSpecShip Desktop server\n'));
|
|
@@ -1575,15 +1654,11 @@ function main() {
|
|
|
1575
1654
|
console.error(chalk.dim(` project: ${root}`));
|
|
1576
1655
|
}
|
|
1577
1656
|
else {
|
|
1578
|
-
console.error(chalk.yellow((0, glyphs_1.getGlyphs)().warn) + ' no primary project — pick one in the
|
|
1657
|
+
console.error(chalk.yellow((0, glyphs_1.getGlyphs)().warn) + ' no primary project — pick one in the dashboard');
|
|
1579
1658
|
console.error(chalk.dim(' analytics endpoints will return 409 until one is selected'));
|
|
1580
1659
|
}
|
|
1581
|
-
if (
|
|
1582
|
-
console.error(chalk.green((0, glyphs_1.getGlyphs)().ok) + `
|
|
1583
|
-
}
|
|
1584
|
-
else if (options.web !== false) {
|
|
1585
|
-
console.error(chalk.yellow((0, glyphs_1.getGlyphs)().warn) + ' UI not found — running API-only.');
|
|
1586
|
-
console.error(chalk.dim(' Build it first: cd packages/server && npm run build:all'));
|
|
1660
|
+
if (serveUi) {
|
|
1661
|
+
console.error(chalk.green((0, glyphs_1.getGlyphs)().ok) + ` Dashboard: ${handle.url}/ (server-rendered)`);
|
|
1587
1662
|
}
|
|
1588
1663
|
if (options.ingest !== false) {
|
|
1589
1664
|
console.error(chalk.dim(' Claude Code transcript ingest watcher active'));
|