@gobing-ai/spur 0.3.94 → 0.3.95
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/.claude-plugin/marketplace.json +1 -1
- package/README.md +42 -0
- package/board/index.d.ts +38 -0
- package/config/plugin-scripts.json +5 -0
- package/config/workflows/feature-verification.yaml +7 -4
- package/config/workflows/idea-pipeline.yaml +10 -4
- package/config/workflows/task-pipeline.yaml +54 -26
- package/config/workflows/wrapup-pipeline.yaml +92 -17
- package/package.json +15 -11
- package/plugins/sp/commands/dev-verify.md +7 -4
- package/plugins/sp/commands/dev-verifyall.md +4 -3
- package/plugins/sp/commands/dev-wrap.md +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +2 -2
- package/plugins/sp/hooks/context-session-start.ts +2 -1
- package/plugins/sp/hooks/context-session-stop.ts +22 -14
- package/plugins/sp/hooks/pi/guard-extension.ts +109 -150
- package/plugins/sp/lib/idea-handoff.generated.mjs +2 -2
- package/plugins/sp/lib/inline-run.generated.d.mts +2 -2
- package/plugins/sp/lib/inline-run.generated.mjs +10 -10
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
- package/plugins/sp/scripts/feature-sync-bounded.mjs +5 -2
- package/plugins/sp/scripts/feature-sync-bounded.ts +3 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
- package/plugins/sp/scripts/history-anatomy-cache.mjs +3 -3
- package/plugins/sp/scripts/history-anatomy-cache.ts +4 -4
- package/plugins/sp/scripts/inline-run-setup.mjs +23 -13
- package/plugins/sp/scripts/inline-run-setup.ts +60 -27
- package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
- package/plugins/sp/scripts/pr-reviewing.ts +1 -0
- package/plugins/sp/scripts/quality-gate.mjs +4 -2
- package/plugins/sp/scripts/quality-gate.ts +8 -2
- package/plugins/sp/scripts/residual-scan.mjs +3 -3
- package/plugins/sp/scripts/residual-scan.ts +3 -3
- package/plugins/sp/scripts/script-contract-check.ts +175 -46
- package/plugins/sp/scripts/script-root.mjs +125 -0
- package/plugins/sp/scripts/script-root.ts +195 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1 -1
- package/plugins/sp/scripts/task-evidence-precheck.ts +4 -2
- package/plugins/sp/scripts/task-size-precheck.ts +3 -1
- package/plugins/sp/scripts/validate-commands.ts +12 -12
- package/plugins/sp/scripts/validate-flag-contracts.ts +42 -30
- package/plugins/sp/scripts/verify-answer-lint.ts +3 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +5 -2
- package/plugins/sp/scripts/workflow-step-profile.ts +3 -1
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +5 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +4 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +32 -8
- package/plugins/sp/scripts/wrapup-steps.ts +56 -10
- package/plugins/sp/skills/code-verification/SKILL.md +12 -7
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -0
- package/plugins/sp/skills/functional-review/SKILL.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +9 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +30 -10
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +6 -3
- package/plugins/sp/tsconfig.json +8 -0
- package/spur.js +5194 -4457
- package/web/_astro/{BoardApp.FTEs3-N8.js → BoardApp.BxJuwD7I.js} +150 -153
- package/web/_astro/BoardApp.Cpxntzad.js +1 -0
- package/web/_astro/TaskDetail.C5bW4WGV.js +1 -0
- package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
- package/web/_astro/{arc.uuAf51IT.js → arc.CyjRvNMY.js} +1 -1
- package/web/_astro/architectureDiagram-3BPJPVTR.B4lWRzJA.js +36 -0
- package/web/_astro/{blockDiagram-GPEHLZMM.D1mGCq3p.js → blockDiagram-GPEHLZMM.Be38USQ2.js} +1 -1
- package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
- package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
- package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
- package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
- package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
- package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
- package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
- package/web/_astro/{c4Diagram-AAUBKEIU.CMsolcde.js → c4Diagram-AAUBKEIU.DT9Fj5Qx.js} +1 -1
- package/web/_astro/channel.CI6N_tCg.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CrGA3fik.js → chunk-2J33WTMH.SeSBWLg5.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DsLVVla0.js → chunk-4BX2VUAB.DoO4VE14.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.Dxc59Tfi.js → chunk-55IACEB6.DrmTFA53.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CaEVE2Wy.js → chunk-727SXJPM.Bi-TUb_V.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.BiJ4HXeI.js → chunk-AQP2D5EJ.B-xp8Uhi.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.Mxf1fru5.js → chunk-FMBD7UC4.DGWrDnUU.js} +1 -1
- package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
- package/web/_astro/{chunk-ND2GUHAM.pyOWQixH.js → chunk-ND2GUHAM._BagvPDy.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BmEsg4vr.js → chunk-QZHKN3VN.VOboYWQ0.js} +1 -1
- package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
- package/web/_astro/{classDiagram-4FO5ZUOK.BHhhMFTO.js → classDiagram-4FO5ZUOK.D_apfHS7.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.BHhhMFTO.js → classDiagram-v2-Q7XG4LA2.D_apfHS7.js} +1 -1
- package/web/_astro/client.BhluLQqe.js +1 -0
- package/web/_astro/client.DBpSRLnx.js +9 -0
- package/web/_astro/cose-bilkent-S5V4N54A.DNLU_L-x.js +1 -0
- package/web/_astro/{cynefin-OW5HDTMX.C2j1_lKL.js → cynefin-OW5HDTMX.D8borhV-.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.hZ2NCTdT.js → dagre-BM42HDAG.Uc0lvjcV.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.Bfw5_EzK.js → diagram-2AECGRRQ.DDIxtDBo.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bt1V_Tmk.js → diagram-5GNKFQAL.DVoDnD3H.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.DEk-YFwp.js → diagram-KO2AKTUF.CRubTJ-0.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.CDjndtQm.js → diagram-LMA3HP47.m9SeYcXQ.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.cFnUHScG.js → diagram-OG6HWLK6.C6s2ZjV5.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DlhYp7NV.js → erDiagram-TEJ5UH35.nSzr5KTj.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DNTpsxfx.js → flowDiagram-I6XJVG4X.DrZcys16.js} +1 -1
- package/web/_astro/ganttDiagram-6RSMTGT7.Do4M1b-K.js +292 -0
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DkEqNI0P.js → gitGraphDiagram-PVQCEYII.BWc9oJ4l.js} +1 -1
- package/web/_astro/index.5dn-Zm-2.js +1 -0
- package/web/_astro/index.C-IqPpW2.js +1 -0
- package/web/_astro/index.CENnIEqT.css +1 -0
- package/web/_astro/index.Cyc_GA4f.js +1 -0
- package/web/_astro/index.tZfqwSut.js +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CrjioCTG.js → infoDiagram-5YYISTIA.BIdOctFk.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BcK0CN8n.js → ishikawaDiagram-YF4QCWOH.CV5SuG2t.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.p0CbBDX1.js → journeyDiagram-JHISSGLW.CxE-rEJ-.js} +1 -1
- package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
- package/web/_astro/{kanban-definition-UN3LZRKU.puHIFt6J.js → kanban-definition-UN3LZRKU.BPRdKWs_.js} +1 -1
- package/web/_astro/{linear.Bb-3a1d7.js → linear.zRsuuDTE.js} +1 -1
- package/web/_astro/{mermaid.core.CJDgXOJs.js → mermaid.core.jAJTcMKc.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BoAZEI9t.js → mindmap-definition-RKZ34NQL.BffJxfCr.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CJNSdqY7.js → pieDiagram-4H26LBE5.CacwWaE3.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DtTy7_0Z.js → quadrantDiagram-W4KKPZXB.hXELD06-.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BNYV98Xg.js → requirementDiagram-4Y6WPE33.DdEbPcqT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DRq9DGHp.js → sankeyDiagram-5OEKKPKP.DgaDocKv.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B7KdBFCm.js → sequenceDiagram-3UESZ5HK.B0KScnAu.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CD62ZJ2H.js → stateDiagram-AJRCARHV.CV9M_WNc.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CeVk1TAj.js → stateDiagram-v2-BHNVJYJU.BOAo74Et.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C_ZPA5tT.js → timeline-definition-PNZ67QCA.CqqepUMe.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.CrThO_FQ.js → vennDiagram-CIIHVFJN.DDqe4dOG.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DSZCA5nl.js → wardleyDiagram-YWT4CUSO.B3R9OPdO.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.KI7baTuk.js → xychartDiagram-2RQKCTM6.B3SvgXpQ.js} +1 -1
- package/web/board-runtime.json +69 -0
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.C02hAHPO.js +0 -1
- package/web/_astro/TaskDetail.C-GdsS-t.js +0 -1
- package/web/_astro/architectureDiagram-3BPJPVTR.CGe629A1.js +0 -36
- package/web/_astro/channel.fsgl7o5j.js +0 -1
- package/web/_astro/client.yhYJvxCU.js +0 -9
- package/web/_astro/cose-bilkent-S5V4N54A.2fH4YOlp.js +0 -1
- package/web/_astro/ganttDiagram-6RSMTGT7.DC_p36PI.js +0 -292
- package/web/_astro/index.De90oHcH.js +0 -1
- package/web/_astro/index.Hjbr15fG.css +0 -1
|
@@ -172,6 +172,31 @@ function readFileSyncSafe(path) {
|
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
function verdictFromTestingSection(content) {
|
|
176
|
+
const heading = /^#{1,6}\s+Testing\s*$/m.exec(content);
|
|
177
|
+
if (!heading)
|
|
178
|
+
return null;
|
|
179
|
+
const level = heading[0].match(/^#+/)?.[0]?.length ?? 2;
|
|
180
|
+
const rest = content.slice(heading.index + heading[0].length);
|
|
181
|
+
const next = new RegExp(`^#{1,${level}}\\s+\\S`, "m").exec(rest);
|
|
182
|
+
const section = next ? rest.slice(0, next.index) : rest;
|
|
183
|
+
for (const line of section.split(`
|
|
184
|
+
`)) {
|
|
185
|
+
const m = /^(?:-\s*|\*\*)?Verdict:\s*(PASS|PARTIAL|FAIL|UNKNOWN)\b/i.exec(line.trim());
|
|
186
|
+
if (m?.[1] !== undefined)
|
|
187
|
+
return m[1].toUpperCase();
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function verdictOfArtifact(verdictPath) {
|
|
192
|
+
try {
|
|
193
|
+
const raw = jqPick(JSON.parse(readFileSync(verdictPath, "utf8")).verdict, "");
|
|
194
|
+
const text = jqText(raw);
|
|
195
|
+
return text.length > 0 ? text : null;
|
|
196
|
+
} catch {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
175
200
|
function runMetrics(env, options = {}) {
|
|
176
201
|
const cwd = options.cwd;
|
|
177
202
|
const runId = env.__runId ?? "";
|
|
@@ -212,15 +237,13 @@ function runMetrics(env, options = {}) {
|
|
|
212
237
|
const frontmatter = parsed.frontmatter !== null && typeof parsed.frontmatter === "object" ? parsed.frontmatter : {};
|
|
213
238
|
const featureId = String(jqPick(frontmatter.feature_id, parsed.feature_id, ""));
|
|
214
239
|
const status2 = String(jqPick(frontmatter.status, parsed.status, "unknown"));
|
|
215
|
-
let verdict = "UNKNOWN";
|
|
216
240
|
const verdictPath = join(".spur", "run", `${wbs}-verdict.json`);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
} catch {}
|
|
241
|
+
const artifactVerdict = verdictOfArtifact(abs(verdictPath));
|
|
242
|
+
const trackedVerdict = verdictFromTestingSection(typeof parsed.content === "string" ? parsed.content : "");
|
|
243
|
+
const verdict = artifactVerdict ?? trackedVerdict ?? "UNKNOWN";
|
|
244
|
+
if (verdict === "UNKNOWN") {
|
|
245
|
+
process.stderr.write(`metrics-record: task ${wbs} has no certifying verdict \u2014 ${verdictPath}: ${artifactVerdict ?? "missing or carries none"}, tracked Testing: ${trackedVerdict ?? "no Verdict: line"} \u2014 recording UNKNOWN telemetry
|
|
246
|
+
`);
|
|
224
247
|
}
|
|
225
248
|
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
226
249
|
const row = { wbs, feature_id: featureId, status: status2, verdict, timestamp };
|
|
@@ -403,6 +426,7 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
403
426
|
}
|
|
404
427
|
export {
|
|
405
428
|
writeRouteReason,
|
|
429
|
+
verdictFromTestingSection,
|
|
406
430
|
taskStatusOf,
|
|
407
431
|
spurCommand,
|
|
408
432
|
runMetrics,
|
|
@@ -269,6 +269,49 @@ function readFileSyncSafe(path: string): string | null {
|
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Verdict from a task record's tracked `Testing` section — the durable copy that outlives a
|
|
274
|
+
* worktree teardown or clone (F93). Local copy of `parseVerdictLine` and of the `Testing` slice
|
|
275
|
+
* in `extractTestingSection` (`packages/app/src/services/task-record.ts`) because ADR-065 keeps
|
|
276
|
+
* plugin scripts on builtin and relative imports only; keep the three literals in step by hand
|
|
277
|
+
* (the parity guard in `plugins/sp/tests/wrapup-steps.test.ts` reads the canonical literals).
|
|
278
|
+
*
|
|
279
|
+
* The slice rule is the canonical one verbatim — `#{1,6}` heading, end at the next
|
|
280
|
+
* same-or-higher heading — so an `# Testing` or `#### Testing` heading, and an h4 subheading
|
|
281
|
+
* inside a `## Testing` section, read the same here and in the app. One deliberate difference:
|
|
282
|
+
* with no `Testing` heading at all this returns null instead of falling back to the whole
|
|
283
|
+
* document, so a `Verdict:` token in another section cannot be misread as this task's verdict.
|
|
284
|
+
*/
|
|
285
|
+
export function verdictFromTestingSection(content: string): string | null {
|
|
286
|
+
const heading = /^#{1,6}\s+Testing\s*$/m.exec(content);
|
|
287
|
+
if (!heading) return null;
|
|
288
|
+
const level = heading[0].match(/^#+/)?.[0]?.length ?? 2;
|
|
289
|
+
const rest = content.slice(heading.index + heading[0].length);
|
|
290
|
+
const next = new RegExp(`^#{1,${level}}\\s+\\S`, 'm').exec(rest);
|
|
291
|
+
const section = next ? rest.slice(0, next.index) : rest;
|
|
292
|
+
for (const line of section.split('\n')) {
|
|
293
|
+
// Line-anchored (optionally after `- ` bullet or `**` bold) so evidence text
|
|
294
|
+
// containing a mid-line "Verdict:" token cannot be misread as the section verdict.
|
|
295
|
+
const m = /^(?:-\s*|\*\*)?Verdict:\s*(PASS|PARTIAL|FAIL|UNKNOWN)\b/i.exec(line.trim());
|
|
296
|
+
if (m?.[1] !== undefined) return m[1].toUpperCase();
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* `verdict` field of a verdict artifact; null when the file is absent, unreadable, malformed or
|
|
303
|
+
* carries no usable verdict (jq `//` semantics: null, undefined and false all count as missing).
|
|
304
|
+
*/
|
|
305
|
+
function verdictOfArtifact(verdictPath: string): string | null {
|
|
306
|
+
try {
|
|
307
|
+
const raw = jqPick((JSON.parse(readFileSync(verdictPath, 'utf8')) as { verdict?: unknown }).verdict, '');
|
|
308
|
+
const text = jqText(raw);
|
|
309
|
+
return text.length > 0 ? text : null;
|
|
310
|
+
} catch {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
272
315
|
export interface MetricsResult {
|
|
273
316
|
status: 'PASS' | 'FAIL';
|
|
274
317
|
statusFile: string;
|
|
@@ -323,17 +366,20 @@ export function runMetrics(env: WrapupStepsEnv, options: WrapupStepsOptions = {}
|
|
|
323
366
|
const featureId = String(jqPick(frontmatter.feature_id, parsed.feature_id, ''));
|
|
324
367
|
const status = String(jqPick(frontmatter.status, parsed.status, 'unknown'));
|
|
325
368
|
|
|
326
|
-
//
|
|
327
|
-
|
|
369
|
+
// The verdict artifact stays the first source (F93 R2); when it is gone — a worktree run
|
|
370
|
+
// fast-forwards and removes the tree, taking the gitignored artifact with it — the tracked
|
|
371
|
+
// `Testing` line `task record` already wrote into the task file is the durable copy. Honest
|
|
372
|
+
// UNKNOWN is the last resort, and it is telemetry, never proof of completion.
|
|
328
373
|
const verdictPath = join('.spur', 'run', `${wbs}-verdict.json`);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
374
|
+
const artifactVerdict = verdictOfArtifact(abs(verdictPath));
|
|
375
|
+
const trackedVerdict = verdictFromTestingSection(typeof parsed.content === 'string' ? parsed.content : '');
|
|
376
|
+
const verdict = artifactVerdict ?? trackedVerdict ?? 'UNKNOWN';
|
|
377
|
+
if (verdict === 'UNKNOWN') {
|
|
378
|
+
// R3: name the task, the artifact path and the tracked state. An explicit UNKNOWN on
|
|
379
|
+
// either source is still an uncertified row, so it reports the same way as a miss.
|
|
380
|
+
process.stderr.write(
|
|
381
|
+
`metrics-record: task ${wbs} has no certifying verdict — ${verdictPath}: ${artifactVerdict ?? 'missing or carries none'}, tracked Testing: ${trackedVerdict ?? 'no Verdict: line'} — recording UNKNOWN telemetry\n`,
|
|
382
|
+
);
|
|
337
383
|
}
|
|
338
384
|
|
|
339
385
|
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
@@ -259,17 +259,22 @@ the deterministic Testing writer `spur task record` (section authorship never ha
|
|
|
259
259
|
|
|
260
260
|
```bash
|
|
261
261
|
# write .spur/run/<wbs>-verdict.json (shape in references/verdict-schema.md), then:
|
|
262
|
-
|
|
262
|
+
spur task record <wbs> --verdict-file .spur/run/<wbs>-verdict.json # renders ## Testing, flips proven boxes
|
|
263
|
+
# F96 residual sweep (observe-only): scan + fold AFTER record, under every --fix mode (0987).
|
|
263
264
|
RESIDUAL=$(superskill script path sp residual-scan.mjs)
|
|
264
265
|
node "$RESIDUAL" scan <wbs>
|
|
265
266
|
node "$RESIDUAL" fold <wbs>
|
|
266
|
-
|
|
267
|
+
# Downgrade only: re-record so Testing carries the final (folded) verdict.
|
|
268
|
+
[ "$(jq -r .verdict .spur/run/<wbs>-verdict.json)" = PASS ] \
|
|
269
|
+
|| spur task record <wbs> --verdict-file .spur/run/<wbs>-verdict.json
|
|
267
270
|
```
|
|
268
271
|
|
|
269
|
-
> **Residual fold (F96).** `scan` reads `.spur/run/` artifacts and
|
|
270
|
-
> `residual-report.md`; `fold` rewrites the
|
|
271
|
-
> check in place (PASS → PARTIAL when a blocking residual exists),
|
|
272
|
-
>
|
|
272
|
+
> **Residual fold (F96).** `scan` reads `.spur/run/` artifacts and the post-record task file and
|
|
273
|
+
> writes `residuals.json` + `residual-report.md`; `fold` rewrites the verdict artifact's
|
|
274
|
+
> `residual-sweep` check in place (PASS → PARTIAL when a blocking residual exists), and the
|
|
275
|
+
> downgrade-only re-record transcribes it — a manual verify cannot certify what the pipeline would
|
|
276
|
+
> reject. The sweep runs after `record` (same order as the pipeline, 0983/0987): before it, a
|
|
277
|
+
> verdict-proven box is still unticked and a clean PASS would fold to PARTIAL. Resolve the
|
|
273
278
|
> script via `superskill script path sp residual-scan.mjs`; shipped surfaces never reference
|
|
274
279
|
> `plugins/sp/scripts/` directly (script-contract-check rule 4). When the task reaches `done`
|
|
275
280
|
> through `--next`, run `residual-scan settle` (links follow-up tasks; best-effort).
|
|
@@ -322,7 +327,7 @@ Verdict: PASS
|
|
|
322
327
|
### SECUA Review
|
|
323
328
|
| Priority | Dimension | Location | Finding |
|
|
324
329
|
| --- | --- | --- | --- |
|
|
325
|
-
| P4 | — | — | No
|
|
330
|
+
| P4 | — | — | No findings (verify verdict PASS) |
|
|
326
331
|
```
|
|
327
332
|
|
|
328
333
|
The per-requirement traceability table MUST use `| Req | Status | Evidence |` (exactly this header, no `R#`/`R`/`Requirement` variant — `Status` in column 2 — and no extra columns between Req and Status). The Acceptance Criteria table MUST use `| AC | Status | Evidence Type | Evidence |`.
|
|
@@ -118,6 +118,17 @@ fail — invalid status (`MET | PARTIAL | UNMET` for requirements;
|
|
|
118
118
|
manual-review | llm-judge | n/a`, or a `+` compound), and empty evidence. Interrupted runs keep the
|
|
119
119
|
rows that pass the lint and complete only the missing IDs on retry.
|
|
120
120
|
|
|
121
|
+
**Feature-credited AC ids are narrower than the lint's accepted set (task 0966 finding).** A row keyed
|
|
122
|
+
by a bare checklist ordinal (`AC1`) passes `verify-answer-lint` — it resolves to the task's AC
|
|
123
|
+
checklist label — but `feature-check.rowMatchesScenario` credits a linked feature scenario only by the
|
|
124
|
+
scenario's normalized title or its `AC-N` ordinal alias. Such a row therefore carries a PASS verdict
|
|
125
|
+
while crediting no scenario, and the feature's `verifying → done` gate denies with
|
|
126
|
+
`L4.scenario-unverified` / `L4.verdict-rows-match-no-scenario`. When the task has a linked feature,
|
|
127
|
+
key each AC row `AC-N` (1-based scenario ordinal, hyphenated) or by the exact scenario title.
|
|
128
|
+
A scenario-keyed row (`R3 — <title>`) ticks the task AC box whose line aliases that scenario
|
|
129
|
+
(`- [ ] AC1 — R3 — <title>`) during `spur task record` (0996); a scenario key no AC line aliases
|
|
130
|
+
ticks nothing, so alias every graduated scenario on its AC line.
|
|
131
|
+
|
|
121
132
|
**Concrete anchors only (task 0804 R9).** An evidence anchor must be a concrete existing `file:line`
|
|
122
133
|
(or `file:start-end`) path. A glob or directory summary (`src/services/*.ts`, `the retry
|
|
123
134
|
classifiers in task-pipeline.yaml`) is not an anchor: expand it into the specific cited files/ranges
|
|
@@ -253,7 +253,7 @@ shape, e.g. `| Req | Status | Evidence |` alone, is structurally rejected and de
|
|
|
253
253
|
```markdown
|
|
254
254
|
| Priority | Dimension | Location | Finding |
|
|
255
255
|
| --- | --- | --- | --- |
|
|
256
|
-
| P4 | — | — | No
|
|
256
|
+
| P4 | — | — | No findings (functional verdict PASS) |
|
|
257
257
|
|
|
258
258
|
| Req | Status | Evidence |
|
|
259
259
|
| --- | --- | --- |
|
|
@@ -113,7 +113,7 @@ token, deterministic).
|
|
|
113
113
|
| C3 | A5/A6 when Testing empty/N/A **and** verify would fail for missing tests — only if prior implement claims code exists | Coverage/test signal: `bun test` fail attributed to task paths OR explicit "insufficient tests" in prior verify verdict artifact `.spur/run/<wbs>-verdict.json` | test fail / coverage gap | `/sp:dev-unit <wbs> --auto` | continue |
|
|
114
114
|
| C4 | A3/A5/A6 when operator or task tags mention rules, OR `spur rule run` last report dirty in `.spur/` if present | `spur rule run` (default project preset) non-zero with findings | rule findings | **HITL STOP** — print rule summary; suggest `/sp:rule-scan` or `rule-add`/`rule-refine` (do not auto-author rules) | continue |
|
|
115
115
|
| C5 | A6 only | Existing `.spur/run/<wbs>-verdict.json` with FAIL and findings pointing at coverage | verdict artifact | `/sp:dev-unit <wbs>` then re-verify on next invocation (`--once` friendly) | `/sp:dev-verify …` |
|
|
116
|
-
| C6 | A4/A5 | `.spur/run/<wbs>-verdict.json` has a failing (PARTIAL/FAIL) `residual-sweep` check —
|
|
116
|
+
| C6 | A4/A5 | `.spur/run/<wbs>-verdict.json` has a failing (PARTIAL/FAIL) `residual-sweep` check — residual blockers survived the pipeline sweep (F96) | folded verdict artifact | **HITL STOP** — print `.spur/run/<wbs>-residual-report.md` and the recovery command `/sp:dev-run <wbs>`; never auto-dispatch a fix (repeating the loop unattended burns quota without new information) | continue |
|
|
117
117
|
|
|
118
118
|
**C-row precedence note (F96):** C6 outranks C2/C3/C5 for the same task — a residual-sweep failure
|
|
119
119
|
means the workspace holds unfinished task residue, so fixall/unit reruns would either clean it
|
|
@@ -156,7 +156,7 @@ backfill, and a lifecycle transition. Collapses the pipeline's record step to on
|
|
|
156
156
|
| ---- | ------ |
|
|
157
157
|
| `--verdict-file <path>` | Verdict JSON (default `.spur/run/<wbs>-verdict.json`). |
|
|
158
158
|
| `--solution-from-diff` | Backfill `Solution` from `git diff -U0` **only when Solution is bare**. |
|
|
159
|
-
| `--transition <status>` | Optional lifecycle transition after writing. **
|
|
159
|
+
| `--transition <status>` | Optional lifecycle transition after writing. `done` is a **guarded** path: it requires a PASS verdict, auto-walks `wip → testing → done`, and auto-creates the pipeline run-link; a non-PASS verdict errors instead of transitioning. |
|
|
160
160
|
|
|
161
161
|
**Verdict shape** (`.spur/run/<wbs>-verdict.json`):
|
|
162
162
|
|
|
@@ -334,6 +334,14 @@ column to distinguish AC rows from requirement rows:
|
|
|
334
334
|
`Status`: `MET` | `PARTIAL` | `UNMET` | `N/A`. `Evidence Type`: `test` | `command` | `static-ref`
|
|
335
335
|
(or `static`) | `manual-review` (or `manual`) | `llm-judge` (or `judge`) | `n/a` (or `na`).
|
|
336
336
|
|
|
337
|
+
**Graduating tasks — one row keyed to both checks (0993).** A task whose linked feature has
|
|
338
|
+
`ac_altitude: graduating` must satisfy two independent id spaces: the feature scenario alias
|
|
339
|
+
`AC-<i>` (which credits the scenario) and the task checkbox id `AC<i>` (which the box flip needs).
|
|
340
|
+
Key the row **`AC<n> — <scenario title>`**, omitting the scenario's own `R<n>` label — then one row
|
|
341
|
+
does both: `normalizeTitle` strips the `AC<n>` prefix so the row still matches the scenario title,
|
|
342
|
+
and `prefixId` normalizes `AC<n>` so the box flips. A bare `AC<n>` credits no scenario; a bare
|
|
343
|
+
`AC-<i>` flips no box; a title carrying the scenario's `R<n>` label matches neither.
|
|
344
|
+
|
|
337
345
|
**Evidence rule (behavior-bearing AC):** an AC row with `status: MET` on a behavior-bearing id
|
|
338
346
|
(no `[advisory]`/`[non-core]`/`[non-behavior]`/`[docs-only]` marker) MUST carry `test` or
|
|
339
347
|
`command` evidence; any other evidence type downgrades the row to `PARTIAL` and caps the verdict.
|
|
@@ -492,7 +492,8 @@ batch can never destroy its own evidence. Reuse mode retains its operator-owned
|
|
|
492
492
|
persists the Step 5 report under the invoking tree; the reused tree's `.spur/run/` remains the live
|
|
493
493
|
copy while that tree lives on. The per-run provenance — the worktree DB's run/action rows and the
|
|
494
494
|
`.spur/run/<runId>.md` + `.state.json` records — is persisted mechanically by WT-4a's
|
|
495
|
-
`inline-run-setup.ts --persist-out --from <worktree
|
|
495
|
+
`inline-run-setup.ts --persist-out --from <worktree> [--task-file <merged-task>]...` call,
|
|
496
|
+
not by hand.
|
|
496
497
|
|
|
497
498
|
**Stage records are worktree-local too (0948 R9, E7 Finding 5; persisted by 0975 R1).** Each task's
|
|
498
499
|
own per-stage run record (`.spur/run/<runId>.md` + `.state.json`) and the worktree DB's run rows are
|
|
@@ -500,10 +501,25 @@ written inside the worktree and **are removed with it** in create mode — the E
|
|
|
500
501
|
this evidence. Copying them out is no longer a manual audit-time duty: WT-4a (create-mode block
|
|
501
502
|
below) runs `inline-run-setup.ts --persist-out --from "$WT_PATH"` **before** WT-4b holder cleanup,
|
|
502
503
|
which copies the run/action/phase/transition/workflow-state rows and both record files into the
|
|
503
|
-
invoking tree.
|
|
504
|
-
|
|
504
|
+
invoking tree.
|
|
505
|
+
|
|
506
|
+
**Cited evidence rides the same call (0984 R1/R2).** The driver forwards each merged task file with
|
|
507
|
+
repeatable `--task-file <path>` (paths resolved in the invoking tree after the FF merge, e.g.
|
|
508
|
+
`spur task show <wbs> --json` → `.filePath`), and persist-out then copies/verifies every literal
|
|
509
|
+
`.spur/run/<file>` that file cites — WBS verdicts, check receipts, test-gate logs, run-ID records —
|
|
510
|
+
so a merged task file never anchors a path that died with the tree. Abbreviated references
|
|
511
|
+
(`fadca099-…`, `run-*-ac87.log`, `{batch-report.md,…}`) are not literal files and carry no
|
|
512
|
+
obligation; neither do root-qualified paths (`knowledge-kit/.spur/run/…`, `/abs/.spur/run/…`),
|
|
513
|
+
which cite another project's evidence — cite foreign run artifacts that way, never bare. A citation missing in BOTH trees, a divergent cited file (never overwritten — reconcile
|
|
514
|
+
by hand), an unreadable task file, or more than 64 distinct cited files fails the pass → WT-5.
|
|
515
|
+
|
|
516
|
+
The shapes are pinned (task 0975 R1; `record-missing` and citation behavior per 0984): idempotent on re-persist;
|
|
517
|
+
success exits 0 printing
|
|
518
|
+
`{"ok":true,"persisted":<n>,"skipped":[{"id":<run-id>,"reason":"id-exists"|"external-key-conflict"|"record-conflict:<file>"|"record-missing:<file>"|"cited-directory:<name>"|"cited-symlink:<name>"|"cited-non-file:<name>"}]}`
|
|
505
519
|
— an `id-exists` / `external-key-conflict` skip never modifies the pre-existing target rows, a
|
|
506
|
-
`record-conflict:<file>` skip never overwrites a divergent invoking-tree record
|
|
520
|
+
`record-conflict:<file>` skip never overwrites a divergent invoking-tree record, and a
|
|
521
|
+
`record-missing:<file>` skip is a known `task-lifecycle`/`feature-lifecycle` row with no record file
|
|
522
|
+
at all (its inserted DB row still counts in `persisted` — 0984 R5). Any failure
|
|
507
523
|
exits 1 printing `{"ok":false,"error":<message>}` (a worktree DB run id that is not a single safe
|
|
508
524
|
filename component is rejected before any target write). Any persist-out failure
|
|
509
525
|
routes to **WT-5** — worktree and branch retained — the same copy-out-first contract as the
|
|
@@ -828,12 +844,16 @@ git merge --ff-only "$BRANCH" # FF-only: never rebase, merge-commit, or
|
|
|
828
844
|
WT_PATH="$(cd "../<worktree-dir>" && pwd)" # hoisted: needed by WT-4a AND WT-4b below
|
|
829
845
|
# WT-4a provenance persist-out (task 0975 R1): copy the worktree DB's run rows plus
|
|
830
846
|
# the .spur/run/<runId>.md + .state.json records into THIS tree. Run from the main
|
|
831
|
-
# tree (cwd = the invoking tree).
|
|
832
|
-
#
|
|
833
|
-
#
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
847
|
+
# tree (cwd = the invoking tree). --task-file (0984 R2) forwards each merged task
|
|
848
|
+
# file (post-merge path) so the cited .spur/run/<file> evidence is copied/verified
|
|
849
|
+
# too. Resolve the merged path(s) BEFORE this block — an empty value exits 2:
|
|
850
|
+
# TASK_FILE="$(spur task show <wbs> --json | jq -r .filePath)" # per done task;
|
|
851
|
+
# build TASK_FILE_ARGS=(--task-file "$TASK_FILE") # one flag each
|
|
852
|
+
# Idempotent; conflicts are reported, never overwritten. A non-zero exit —
|
|
853
|
+
# including an unresolved or divergent citation, or a half-readable worktree — must
|
|
854
|
+
# NOT proceed to WT-4b removal:
|
|
855
|
+
SETUP_SCRIPT=plugins/sp/scripts/inline-run-setup.ts; [ -f config/plugin-scripts.json -a -f "$SETUP_SCRIPT" ] || SETUP_SCRIPT="$(superskill script path sp inline-run-setup.mjs 2>/dev/null)"
|
|
856
|
+
bun "$SETUP_SCRIPT" --persist-out --from "$WT_PATH" "${TASK_FILE_ARGS[@]}" \
|
|
837
857
|
|| { echo "halt: worktree run-record persist-out failed - worktree retained (WT-5)" >&2; exit 1; }
|
|
838
858
|
#
|
|
839
859
|
# WT-4b — bounded CWD-holder cleanup (task 0720 R1). $WT_PATH above is the EXACT
|
|
@@ -76,11 +76,14 @@ the human/native presentation layer — labels are display addresses only, never
|
|
|
76
76
|
accept the inline run (0785 R3):
|
|
77
77
|
|
|
78
78
|
```bash
|
|
79
|
-
SETUP_SCRIPT=
|
|
80
|
-
|
|
79
|
+
SETUP_SCRIPT=plugins/sp/scripts/inline-run-setup.ts; [ -f config/plugin-scripts.json -a -f "$SETUP_SCRIPT" ] || SETUP_SCRIPT="$(superskill script path sp inline-run-setup.mjs 2>/dev/null)";
|
|
80
|
+
# 0960 R2: record the run's script-resolution identity beside the run row (the same file the
|
|
81
|
+
# pipelines' run-start action writes) so the inline path carries the same provenance.
|
|
82
|
+
RS=plugins/sp/scripts/script-root.ts; [ -f config/plugin-scripts.json -a -f "$RS" ] || RS="$(superskill script path sp script-root.mjs 2>/dev/null)";
|
|
83
|
+
RUNNER=bun; case "$RS" in *.mjs) RUNNER=node ;; esac; [ -f "$RS" ] && "$RUNNER" "$RS" --run-id "$RUN_ID" || echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2
|
|
81
84
|
[ -n "$SETUP_SCRIPT" ] && [ -f "$SETUP_SCRIPT" ] && \
|
|
82
85
|
bun "$SETUP_SCRIPT" --run-id "$RUN_ID" --file <selected-pipeline-yaml> \
|
|
83
|
-
|| { echo "inline run setup failed closed — checker not found; run 'superskill install sp'" >&2; exit 1; }
|
|
86
|
+
|| { echo "inline run setup failed closed — checker not found; run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 1; }
|
|
84
87
|
```
|
|
85
88
|
|
|
86
89
|
The delegate uses the source app service when the SPUR_BIN chain identifies a checkout;
|