@gobing-ai/spur 0.3.95 → 0.3.97
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/config/plugin-scripts.json +0 -54
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +6 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
- package/config/rules/typescript/output-boundaries.yaml +15 -2
- package/config/script-placement-baseline.json +51 -0
- package/config/templates/AGENTS.md +3 -3
- package/config/workflows/feature-verification.yaml +1 -1
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +80 -85
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +42 -95
- package/config/workflows/wrapup-pipeline.yaml +91 -38
- package/package.json +2 -2
- package/plugins/sp/README.md +14 -10
- package/plugins/sp/agents/super-reviewer.md +43 -18
- package/plugins/sp/commands/dev-fixgha.md +83 -0
- package/plugins/sp/commands/dev-gitmsg.md +8 -6
- package/plugins/sp/commands/dev-gtd.md +2 -2
- package/plugins/sp/commands/dev-idea.md +22 -12
- package/plugins/sp/commands/dev-job-dump.md +29 -0
- package/plugins/sp/commands/dev-job-resume.md +29 -0
- package/plugins/sp/commands/dev-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verifyall.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
- package/plugins/sp/lib/inline-run.generated.mjs +39 -13
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +445 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +63 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +226 -0
- package/plugins/sp/lib/spur-bin.ts +36 -0
- package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
- package/plugins/sp/lib/step-profile.generated.mjs +174 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +3 -3
- package/plugins/sp/scripts/feature-verification-steps.mjs +14 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +14 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
- package/plugins/sp/scripts/inline-run-setup.ts +104 -667
- package/plugins/sp/scripts/quality-gate.mjs +46 -24
- package/plugins/sp/scripts/quality-gate.ts +13 -659
- package/plugins/sp/scripts/residual-scan.mjs +179 -174
- package/plugins/sp/scripts/residual-scan.ts +125 -517
- package/plugins/sp/scripts/script-root.mjs +5 -1
- package/plugins/sp/scripts/script-root.ts +5 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
- package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +72 -33
- package/plugins/sp/scripts/wrapup-steps.ts +128 -37
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +34 -9
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
- package/plugins/sp/skills/functional-review/SKILL.md +7 -4
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/source-driven-development/SKILL.md +11 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
- package/plugins/sp/skills/spur-cli/references/features.md +17 -6
- package/plugins/sp/skills/spur-cli/references/init.md +17 -16
- package/plugins/sp/skills/spur-cli/references/self.md +3 -2
- package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-cli/references/workflows.md +17 -14
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -10
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +165 -75
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +7 -5
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +84 -39
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -12
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +40 -21
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +72 -15
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/spur.js +4461 -2085
- package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.Bmv5WkJ9.js} +1 -1
- package/web/_astro/BoardApp.vveLOdDq.js +322 -0
- package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.IKc3uDYV.js} +1 -1
- package/web/_astro/{arc.CyjRvNMY.js → arc.C63Ufg35.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.CP8Hyldk.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.Bo_48MRA.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.Ch61sw-O.js} +1 -1
- package/web/_astro/channel.BKCmJpWM.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.CHpzS7xf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.DzMNNWIw.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.BqPrDw_Q.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ZzBHhNw-.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.1_O_LLkx.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.GS4UHg7_.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.BnqotN4g.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.Ghuh8zoJ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.BW9vC3zg.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.BW9vC3zg.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.D6jNC0ti.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.BOGoCs4L.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.8iU5VcWi.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.CVt88HwH.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.C9HxUUMl.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.4ywkQN3t.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.C48A6gpz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.DxFieQW4.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.CtjtQqYs.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.C5zAiahU.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.D_L6zsny.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.Ignnrk3V.js} +1 -1
- package/web/_astro/index.CbNz17Sx.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DLv8-P5u.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.p0OTpwFO.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.CIaBt-DD.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.DGySIntq.js} +1 -1
- package/web/_astro/{linear.zRsuuDTE.js → linear.BATV4RXu.js} +1 -1
- package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DzkwM3VX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.3piBRRzA.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DUNueLub.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.9bihVbrt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.CkjsC-YT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.Dbq4vaoQ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.BivrH99P.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.CqLQ1Vzz.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DvvoLD3b.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.B-KR78gv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.Ck0Eieb5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRfdd_do.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.Ck_Jxk9C.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
- package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
- package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
- package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
- package/plugins/sp/scripts/record-feature-sync.ts +0 -84
- package/plugins/sp/scripts/script-contract-check.ts +0 -506
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
- package/plugins/sp/scripts/task-size-precheck.ts +0 -212
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
- package/web/_astro/BoardApp.BxJuwD7I.js +0 -191
- package/web/_astro/channel.CI6N_tCg.js +0 -1
- package/web/_astro/index.CENnIEqT.css +0 -1
|
@@ -11,6 +11,12 @@ function getEnvVars() {
|
|
|
11
11
|
return process.env;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// plugins/sp/lib/spur-bin.ts
|
|
15
|
+
function spurCommand(spurBin) {
|
|
16
|
+
const parts = (spurBin ?? "spur").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
17
|
+
return { cmd: parts[0] ?? "spur", prefix: parts.slice(1) };
|
|
18
|
+
}
|
|
19
|
+
|
|
14
20
|
// plugins/sp/scripts/wrapup-steps.ts
|
|
15
21
|
function jqPick(...values) {
|
|
16
22
|
for (const value of values) {
|
|
@@ -23,10 +29,6 @@ function jqText(value) {
|
|
|
23
29
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
24
30
|
}
|
|
25
31
|
var WBS_PATTERN = /^[0-9]{4}$/;
|
|
26
|
-
function spurCommand(spurBin) {
|
|
27
|
-
const parts = (spurBin ?? "spur").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
28
|
-
return { cmd: parts[0] ?? "spur", prefix: parts.slice(1) };
|
|
29
|
-
}
|
|
30
32
|
function spur(env, args, options = {}) {
|
|
31
33
|
const { cmd, prefix } = spurCommand(env.spurBin);
|
|
32
34
|
const result = spawnSync(cmd, [...prefix, ...args], {
|
|
@@ -112,10 +114,68 @@ function resolveTasks(env, options = {}) {
|
|
|
112
114
|
if (unresolved) {
|
|
113
115
|
return writeFail(`failed:unresolved or non-completed task (see ${relTasksFile})`);
|
|
114
116
|
}
|
|
117
|
+
if ((env.feature ?? "") !== "") {
|
|
118
|
+
const pf = preflightFeature(env, cwd);
|
|
119
|
+
if (pf)
|
|
120
|
+
return writeFail(pf);
|
|
121
|
+
}
|
|
115
122
|
writeFileSync(abs(relStatusFile), `PASS
|
|
116
123
|
`);
|
|
117
124
|
return { status: "PASS", statusFile: relStatusFile, tasksFile: relTasksFile, exitCode: 0 };
|
|
118
125
|
}
|
|
126
|
+
function isFindingList(value) {
|
|
127
|
+
return Array.isArray(value) && value.every((f) => f !== null && typeof f === "object" && typeof f.severity === "string" && typeof f.code === "string" && f.code.length > 0);
|
|
128
|
+
}
|
|
129
|
+
function sortedUniqueCodes(findings) {
|
|
130
|
+
const codes = findings.filter((f) => f.severity === "error").map((f) => String(f.code ?? "")).filter((c) => c.length > 0);
|
|
131
|
+
return [...new Set(codes)].sort();
|
|
132
|
+
}
|
|
133
|
+
function preflightFeature(env, cwd) {
|
|
134
|
+
const feature = env.feature ?? "";
|
|
135
|
+
const abs = (p) => cwd ? join(cwd, p) : p;
|
|
136
|
+
const syncOut = spur(env, ["feature", "sync", feature, "--dry-run", "--json"], { cwd });
|
|
137
|
+
let sync;
|
|
138
|
+
if (syncOut.status === 0) {
|
|
139
|
+
try {
|
|
140
|
+
const parsed = JSON.parse(syncOut.stdout);
|
|
141
|
+
if (parsed !== null && typeof parsed === "object")
|
|
142
|
+
sync = parsed;
|
|
143
|
+
} catch {}
|
|
144
|
+
}
|
|
145
|
+
if (sync === undefined)
|
|
146
|
+
return "failed:preflight:sync-unreadable";
|
|
147
|
+
const proposal = sync.proposal;
|
|
148
|
+
if (proposal === null || typeof proposal !== "object" || Array.isArray(proposal) || proposal.gateBlocked !== undefined && typeof proposal.gateBlocked !== "boolean" || proposal.gateFindings !== undefined && !isFindingList(proposal.gateFindings) || proposal.hops !== undefined && (!Array.isArray(proposal.hops) || !proposal.hops.every((hop) => typeof hop === "string")) || proposal.gateBlocked !== true && (typeof proposal.from !== "string" || typeof proposal.to !== "string")) {
|
|
149
|
+
return "failed:preflight:sync-unreadable";
|
|
150
|
+
}
|
|
151
|
+
if (proposal.gateBlocked === true) {
|
|
152
|
+
return `failed:preflight:gate-blocked ${sortedUniqueCodes(proposal.gateFindings ?? []).join(",")}`;
|
|
153
|
+
}
|
|
154
|
+
const reachesDone = proposal.to === "done" || (proposal.hops ?? []).includes("done");
|
|
155
|
+
if (!reachesDone)
|
|
156
|
+
return null;
|
|
157
|
+
const checkOut = spur(env, ["feature", "check", feature, "--strict", "--as", "done", "--json"], { cwd });
|
|
158
|
+
let check;
|
|
159
|
+
try {
|
|
160
|
+
check = JSON.parse(checkOut.stdout);
|
|
161
|
+
} catch {
|
|
162
|
+
return "failed:preflight:check-unreadable";
|
|
163
|
+
}
|
|
164
|
+
const payload = Array.isArray(check) && check.length === 1 ? check[0] : check;
|
|
165
|
+
const findings = payload !== null && typeof payload === "object" ? payload.findings : undefined;
|
|
166
|
+
if (!isFindingList(findings) || checkOut.status !== 0 && (checkOut.status !== 1 || !findings.some((f) => f.severity === "error"))) {
|
|
167
|
+
return "failed:preflight:check-unreadable";
|
|
168
|
+
}
|
|
169
|
+
const eligible = proposal.from === "verifying" ? findings : findings.filter((f) => !String(f.code ?? "").startsWith("L4.feature-receipt-"));
|
|
170
|
+
const runId = env.__runId ?? "";
|
|
171
|
+
if (runId.length > 0) {
|
|
172
|
+
mkdirSync(abs(join(".spur", "run")), { recursive: true });
|
|
173
|
+
writeFileSync(abs(join(".spur", "run", `${runId}-wrapup-preflight.json`)), `${JSON.stringify(check)}
|
|
174
|
+
`);
|
|
175
|
+
}
|
|
176
|
+
const codes = sortedUniqueCodes(eligible);
|
|
177
|
+
return codes.length > 0 ? `failed:preflight:done-gate ${codes.join(",")}` : null;
|
|
178
|
+
}
|
|
119
179
|
var ROUTE_REASON_TABLE = {
|
|
120
180
|
fast: "fast:evidence complete+consistent",
|
|
121
181
|
"": "safety:missing evidence (mode empty)",
|
|
@@ -237,8 +297,10 @@ function runMetrics(env, options = {}) {
|
|
|
237
297
|
const frontmatter = parsed.frontmatter !== null && typeof parsed.frontmatter === "object" ? parsed.frontmatter : {};
|
|
238
298
|
const featureId = String(jqPick(frontmatter.feature_id, parsed.feature_id, ""));
|
|
239
299
|
const status2 = String(jqPick(frontmatter.status, parsed.status, "unknown"));
|
|
240
|
-
const
|
|
241
|
-
const
|
|
300
|
+
const durable = join(".spur", "memory", "evidence", `${wbs}-verdict.json`);
|
|
301
|
+
const hasDurable = existsSync(abs(durable));
|
|
302
|
+
const verdictPath = hasDurable ? durable : join(".spur", "run", `${wbs}-verdict.json`);
|
|
303
|
+
const artifactVerdict = verdictOfArtifact(abs(verdictPath)) ?? (hasDurable ? "UNKNOWN" : null);
|
|
242
304
|
const trackedVerdict = verdictFromTestingSection(typeof parsed.content === "string" ? parsed.content : "");
|
|
243
305
|
const verdict = artifactVerdict ?? trackedVerdict ?? "UNKNOWN";
|
|
244
306
|
if (verdict === "UNKNOWN") {
|
|
@@ -323,32 +385,9 @@ function runFeatureTransition(env, options = {}) {
|
|
|
323
385
|
}
|
|
324
386
|
let syncOutput = "";
|
|
325
387
|
let syncRc = 1;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const result = spawnSync("bun", [boundedTs, ...boundedArgs], { cwd, encoding: "utf8" });
|
|
330
|
-
syncOutput = result.stdout ?? "";
|
|
331
|
-
syncRc = result.status ?? 1;
|
|
332
|
-
if (result.stderr !== null && result.stderr.length > 0)
|
|
333
|
-
process.stderr.write(result.stderr);
|
|
334
|
-
} else {
|
|
335
|
-
const probe = spawnSync("superskill", ["script", "path", "sp", "feature-sync-bounded.mjs"], {
|
|
336
|
-
cwd,
|
|
337
|
-
encoding: "utf8"
|
|
338
|
-
});
|
|
339
|
-
const twin = probe.status === 0 ? (probe.stdout ?? "").trim() : "";
|
|
340
|
-
if (twin.length > 0 && existsSync(twin)) {
|
|
341
|
-
const result = spawnSync("node", [twin, ...boundedArgs], { cwd, encoding: "utf8" });
|
|
342
|
-
syncOutput = result.stdout ?? "";
|
|
343
|
-
syncRc = result.status ?? 1;
|
|
344
|
-
if (result.stderr !== null && result.stderr.length > 0)
|
|
345
|
-
process.stderr.write(result.stderr);
|
|
346
|
-
} else {
|
|
347
|
-
const result = spur(env, ["feature", "sync", feature, "--json"], { cwd, stderr: "inherit" });
|
|
348
|
-
syncOutput = result.stdout;
|
|
349
|
-
syncRc = result.status;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
388
|
+
const sync = spur(env, ["feature", "sync", feature, "--json"], { cwd, stderr: "inherit" });
|
|
389
|
+
syncOutput = sync.stdout;
|
|
390
|
+
syncRc = sync.status;
|
|
352
391
|
process.stdout.write(`${syncOutput}
|
|
353
392
|
`);
|
|
354
393
|
const shown = spur(env, ["feature", "show", feature, "--json"], { cwd });
|
|
@@ -428,10 +467,10 @@ export {
|
|
|
428
467
|
writeRouteReason,
|
|
429
468
|
verdictFromTestingSection,
|
|
430
469
|
taskStatusOf,
|
|
431
|
-
spurCommand,
|
|
432
470
|
runMetrics,
|
|
433
471
|
runFeatureTransition,
|
|
434
472
|
resolveTasks,
|
|
473
|
+
preflightFeature,
|
|
435
474
|
main,
|
|
436
475
|
jqPick,
|
|
437
476
|
classifySync,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* the shell-program caps while writing the same `.spur/run` artifacts:
|
|
10
10
|
* - `<runId>-wrapup-tasks.json` normalized, deduplicated WBS capture (resolve)
|
|
11
11
|
* - `<runId>-wrapup-resolve.status` `PASS`/`FAIL` (resolve)
|
|
12
|
+
* - `<runId>-wrapup-preflight.json` full feature-check JSON (resolve pre-flight, when `feature` is set)
|
|
12
13
|
* - `<runId>-route-reason.txt` route reason (route-reason subcommand)
|
|
13
14
|
* - `.spur/memory/wrapup-metrics.jsonl` one row per task (metrics)
|
|
14
15
|
* - `<runId>-wrapup-metrics.status` `PASS`/`FAIL` (metrics)
|
|
@@ -33,6 +34,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
33
34
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
34
35
|
import { join } from 'node:path';
|
|
35
36
|
import { getEnvVars } from '../lib/env';
|
|
37
|
+
import { spurCommand } from '../lib/spur-bin';
|
|
36
38
|
|
|
37
39
|
export interface WrapupStepsEnv {
|
|
38
40
|
__runId?: string;
|
|
@@ -64,15 +66,6 @@ function jqText(value: unknown): string {
|
|
|
64
66
|
/** Canonical four-digit WBS string: whitespace is rejected, not trimmed (0783 R1). */
|
|
65
67
|
export const WBS_PATTERN = /^[0-9]{4}$/;
|
|
66
68
|
|
|
67
|
-
/** `spurBin` splits on whitespace into a command plus prefix args (so `bun x.ts` works). */
|
|
68
|
-
export function spurCommand(spurBin: string | undefined): { cmd: string; prefix: string[] } {
|
|
69
|
-
const parts = (spurBin ?? 'spur')
|
|
70
|
-
.trim()
|
|
71
|
-
.split(/\s+/)
|
|
72
|
-
.filter((p) => p.length > 0);
|
|
73
|
-
return { cmd: parts[0] ?? 'spur', prefix: parts.slice(1) };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
69
|
function spur(
|
|
77
70
|
env: WrapupStepsEnv,
|
|
78
71
|
args: string[],
|
|
@@ -182,10 +175,126 @@ export function resolveTasks(env: WrapupStepsEnv, options: WrapupStepsOptions =
|
|
|
182
175
|
if (unresolved) {
|
|
183
176
|
return writeFail(`failed:unresolved or non-completed task (see ${relTasksFile})`);
|
|
184
177
|
}
|
|
178
|
+
if ((env.feature ?? '') !== '') {
|
|
179
|
+
const pf = preflightFeature(env, cwd);
|
|
180
|
+
if (pf) return writeFail(pf);
|
|
181
|
+
}
|
|
185
182
|
writeFileSync(abs(relStatusFile), 'PASS\n');
|
|
186
183
|
return { status: 'PASS', statusFile: relStatusFile, tasksFile: relTasksFile, exitCode: 0 };
|
|
187
184
|
}
|
|
188
185
|
|
|
186
|
+
interface PreflightFinding {
|
|
187
|
+
severity: string;
|
|
188
|
+
code: string;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isFindingList(value: unknown): value is PreflightFinding[] {
|
|
192
|
+
return (
|
|
193
|
+
Array.isArray(value) &&
|
|
194
|
+
value.every(
|
|
195
|
+
(f) =>
|
|
196
|
+
f !== null &&
|
|
197
|
+
typeof f === 'object' &&
|
|
198
|
+
typeof f.severity === 'string' &&
|
|
199
|
+
typeof f.code === 'string' &&
|
|
200
|
+
f.code.length > 0,
|
|
201
|
+
)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface PreflightSyncPayload {
|
|
206
|
+
proposal?: {
|
|
207
|
+
gateBlocked?: boolean;
|
|
208
|
+
from?: string;
|
|
209
|
+
to?: string;
|
|
210
|
+
hops?: string[];
|
|
211
|
+
gateFindings?: PreflightFinding[];
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function sortedUniqueCodes(findings: PreflightFinding[]): string[] {
|
|
216
|
+
const codes = findings
|
|
217
|
+
.filter((f) => f.severity === 'error')
|
|
218
|
+
.map((f) => String(f.code ?? ''))
|
|
219
|
+
.filter((c) => c.length > 0);
|
|
220
|
+
return [...new Set(codes)].sort();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 1033 R2 feature pre-flight for wrapup resolve — runs after the task list resolves and
|
|
225
|
+
* before any corpus-mutating wrapup state. Returns a `failed:preflight:*` reason on the
|
|
226
|
+
* first blocking condition, or null when wrapup may proceed. Never writes PASS, never
|
|
227
|
+
* relaxes the later feature-transition / feature-verify gates, and uses the same `spurBin`
|
|
228
|
+
* env as `runFeatureTransition`.
|
|
229
|
+
*/
|
|
230
|
+
export function preflightFeature(env: WrapupStepsEnv, cwd?: string): string | null {
|
|
231
|
+
const feature = env.feature ?? '';
|
|
232
|
+
const abs = (p: string): string => (cwd ? join(cwd, p) : p);
|
|
233
|
+
|
|
234
|
+
const syncOut = spur(env, ['feature', 'sync', feature, '--dry-run', '--json'], { cwd });
|
|
235
|
+
let sync: PreflightSyncPayload | undefined;
|
|
236
|
+
if (syncOut.status === 0) {
|
|
237
|
+
try {
|
|
238
|
+
const parsed: unknown = JSON.parse(syncOut.stdout);
|
|
239
|
+
if (parsed !== null && typeof parsed === 'object') sync = parsed as PreflightSyncPayload;
|
|
240
|
+
} catch {
|
|
241
|
+
// fall through to the unreadable branch below
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (sync === undefined) return 'failed:preflight:sync-unreadable';
|
|
245
|
+
const proposal = sync.proposal;
|
|
246
|
+
if (
|
|
247
|
+
proposal === null ||
|
|
248
|
+
typeof proposal !== 'object' ||
|
|
249
|
+
Array.isArray(proposal) ||
|
|
250
|
+
(proposal.gateBlocked !== undefined && typeof proposal.gateBlocked !== 'boolean') ||
|
|
251
|
+
(proposal.gateFindings !== undefined && !isFindingList(proposal.gateFindings)) ||
|
|
252
|
+
(proposal.hops !== undefined &&
|
|
253
|
+
(!Array.isArray(proposal.hops) || !proposal.hops.every((hop) => typeof hop === 'string'))) ||
|
|
254
|
+
(proposal.gateBlocked !== true && (typeof proposal.from !== 'string' || typeof proposal.to !== 'string'))
|
|
255
|
+
) {
|
|
256
|
+
return 'failed:preflight:sync-unreadable';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (proposal.gateBlocked === true) {
|
|
260
|
+
return `failed:preflight:gate-blocked ${sortedUniqueCodes(proposal.gateFindings ?? []).join(',')}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const reachesDone = proposal.to === 'done' || (proposal.hops ?? []).includes('done');
|
|
264
|
+
if (!reachesDone) return null;
|
|
265
|
+
|
|
266
|
+
const checkOut = spur(env, ['feature', 'check', feature, '--strict', '--as', 'done', '--json'], { cwd });
|
|
267
|
+
let check: unknown;
|
|
268
|
+
try {
|
|
269
|
+
check = JSON.parse(checkOut.stdout);
|
|
270
|
+
} catch {
|
|
271
|
+
return 'failed:preflight:check-unreadable';
|
|
272
|
+
}
|
|
273
|
+
const payload = Array.isArray(check) && check.length === 1 ? check[0] : check;
|
|
274
|
+
const findings =
|
|
275
|
+
payload !== null && typeof payload === 'object' ? (payload as { findings?: unknown }).findings : undefined;
|
|
276
|
+
if (
|
|
277
|
+
!isFindingList(findings) ||
|
|
278
|
+
(checkOut.status !== 0 && (checkOut.status !== 1 || !findings.some((f) => f.severity === 'error')))
|
|
279
|
+
) {
|
|
280
|
+
return 'failed:preflight:check-unreadable';
|
|
281
|
+
}
|
|
282
|
+
// The verifying onEnter will produce the receipt, so receipt-only errors on a feature
|
|
283
|
+
// that is not yet verifying would fail every wrapup forever.
|
|
284
|
+
const eligible =
|
|
285
|
+
proposal.from === 'verifying'
|
|
286
|
+
? findings
|
|
287
|
+
: findings.filter((f) => !String(f.code ?? '').startsWith('L4.feature-receipt-'));
|
|
288
|
+
|
|
289
|
+
const runId = env.__runId ?? '';
|
|
290
|
+
if (runId.length > 0) {
|
|
291
|
+
mkdirSync(abs(join('.spur', 'run')), { recursive: true });
|
|
292
|
+
writeFileSync(abs(join('.spur', 'run', `${runId}-wrapup-preflight.json`)), `${JSON.stringify(check)}\n`);
|
|
293
|
+
}
|
|
294
|
+
const codes = sortedUniqueCodes(eligible);
|
|
295
|
+
return codes.length > 0 ? `failed:preflight:done-gate ${codes.join(',')}` : null;
|
|
296
|
+
}
|
|
297
|
+
|
|
189
298
|
/**
|
|
190
299
|
* 0944 R3 route-reason map — mirrors the former inline jq object one-for-one, plus the
|
|
191
300
|
* `safety` entry (operator-forced doc-sync). `fast:drift-probe-clean` is NOT in the map:
|
|
@@ -370,8 +479,10 @@ export function runMetrics(env: WrapupStepsEnv, options: WrapupStepsOptions = {}
|
|
|
370
479
|
// fast-forwards and removes the tree, taking the gitignored artifact with it — the tracked
|
|
371
480
|
// `Testing` line `task record` already wrote into the task file is the durable copy. Honest
|
|
372
481
|
// UNKNOWN is the last resort, and it is telemetry, never proof of completion.
|
|
373
|
-
const
|
|
374
|
-
const
|
|
482
|
+
const durable = join('.spur', 'memory', 'evidence', `${wbs}-verdict.json`);
|
|
483
|
+
const hasDurable = existsSync(abs(durable));
|
|
484
|
+
const verdictPath = hasDurable ? durable : join('.spur', 'run', `${wbs}-verdict.json`);
|
|
485
|
+
const artifactVerdict = verdictOfArtifact(abs(verdictPath)) ?? (hasDurable ? 'UNKNOWN' : null);
|
|
375
486
|
const trackedVerdict = verdictFromTestingSection(typeof parsed.content === 'string' ? parsed.content : '');
|
|
376
487
|
const verdict = artifactVerdict ?? trackedVerdict ?? 'UNKNOWN';
|
|
377
488
|
if (verdict === 'UNKNOWN') {
|
|
@@ -484,34 +595,14 @@ export function runFeatureTransition(env: WrapupStepsEnv, options: WrapupStepsOp
|
|
|
484
595
|
return { status: 'FAIL', statusFile: relStatusFile, exitCode: 1 };
|
|
485
596
|
}
|
|
486
597
|
|
|
487
|
-
//
|
|
598
|
+
// 1004 R5: one direct service sync (stderr streams through, stdout is the JSON payload).
|
|
599
|
+
// Repeated-BLOCKED suppression (0411) lives in the feature sync service itself (1004 R3),
|
|
600
|
+
// so the bounded-wrapper resolution branches are gone.
|
|
488
601
|
let syncOutput = '';
|
|
489
602
|
let syncRc = 1;
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const result = spawnSync('bun', [boundedTs, ...boundedArgs], { cwd, encoding: 'utf8' });
|
|
494
|
-
syncOutput = result.stdout ?? '';
|
|
495
|
-
syncRc = result.status ?? 1;
|
|
496
|
-
if (result.stderr !== null && result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
497
|
-
} else {
|
|
498
|
-
const probe = spawnSync('superskill', ['script', 'path', 'sp', 'feature-sync-bounded.mjs'], {
|
|
499
|
-
cwd,
|
|
500
|
-
encoding: 'utf8',
|
|
501
|
-
});
|
|
502
|
-
const twin = probe.status === 0 ? (probe.stdout ?? '').trim() : '';
|
|
503
|
-
if (twin.length > 0 && existsSync(twin)) {
|
|
504
|
-
const result = spawnSync('node', [twin, ...boundedArgs], { cwd, encoding: 'utf8' });
|
|
505
|
-
syncOutput = result.stdout ?? '';
|
|
506
|
-
syncRc = result.status ?? 1;
|
|
507
|
-
if (result.stderr !== null && result.stderr.length > 0) process.stderr.write(result.stderr);
|
|
508
|
-
} else {
|
|
509
|
-
// Last branch: stderr streams through (visible), stdout is the JSON payload.
|
|
510
|
-
const result = spur(env, ['feature', 'sync', feature, '--json'], { cwd, stderr: 'inherit' });
|
|
511
|
-
syncOutput = result.stdout;
|
|
512
|
-
syncRc = result.status;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
603
|
+
const sync = spur(env, ['feature', 'sync', feature, '--json'], { cwd, stderr: 'inherit' });
|
|
604
|
+
syncOutput = sync.stdout;
|
|
605
|
+
syncRc = sync.status;
|
|
515
606
|
process.stdout.write(`${syncOutput}\n`);
|
|
516
607
|
|
|
517
608
|
const shown = spur(env, ['feature', 'show', feature, '--json'], { cwd });
|
|
@@ -105,9 +105,9 @@ spur task show <wbs> --json # for a pipeline run
|
|
|
105
105
|
# scope = 'src/api/' | 'packages/domain/' | 'plugins/sp/'
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
For a pipeline run, derive the diff scope the same way `sp:code-verification` Step 3 does (the
|
|
109
|
-
task
|
|
110
|
-
scope.
|
|
108
|
+
For a pipeline run, derive the diff scope the same way `sp:code-verification` Step 3 does (the SSOT
|
|
109
|
+
recipe: tagged-commit task diff — defined there, not restated here). For standalone, the `path`
|
|
110
|
+
argument is the scope.
|
|
111
111
|
|
|
112
112
|
### Step 2 — Explore (read the map)
|
|
113
113
|
|
|
@@ -188,7 +188,8 @@ When invoked as the `--focus architecture` dimension of `/sp:dev-review`:
|
|
|
188
188
|
- `blocker`/`major` candidates block the `approve(HITL)` gate alongside any SECUA blockers from
|
|
189
189
|
`sp:code-verification`.
|
|
190
190
|
- The candidate list is returned as a **review fragment**; the review coordinator
|
|
191
|
-
(`sp:super-reviewer` under `/sp:dev-review`) merges it into the combined
|
|
191
|
+
(`sp:super-reviewer` — the invoking session under `/sp:dev-review`) merges it into the combined
|
|
192
|
+
`## Review` section —
|
|
192
193
|
it is never written by `record`, which backfills `## Review` only when the section is bare
|
|
193
194
|
(fallback-only, F92 0593 R1).
|
|
194
195
|
- This skill does **not** write to the task file directly — the coordinator (or the operator) does.
|
|
@@ -36,7 +36,7 @@ It backs two commands:
|
|
|
36
36
|
| Command | Mode | Input | Output |
|
|
37
37
|
|---------|------|-------|--------|
|
|
38
38
|
| `/sp:dev-verify <wbs>` | **verify** | a task WBS | `.spur/run/<wbs>-verdict.json`; `record` transcribes `## Testing` |
|
|
39
|
-
| `/sp:dev-review <wbs>` | **review** (coordinator) | a task WBS (diff scope) | merged three-dimensional findings → `## Review` |
|
|
39
|
+
| `/sp:dev-review --tasks <wbs>` | **review** (coordinator) | a task WBS (diff scope) | merged three-dimensional findings → `## Review` |
|
|
40
40
|
|
|
41
41
|
The verify mode is the **completion gate's evidence source**: it emits a machine verdict the
|
|
42
42
|
`task-pipeline.yaml` workflow reads before allowing `record → done`. A `PASS` clears the gate; a
|
|
@@ -96,16 +96,39 @@ no-op there; `--force` matters for re-auditing completed tasks.)
|
|
|
96
96
|
|
|
97
97
|
### Step 3 — Establish the change scope
|
|
98
98
|
|
|
99
|
-
Determine which files the task changed
|
|
99
|
+
Determine which files the task changed. This step is the SSOT scope recipe:
|
|
100
|
+
`sp:functional-review` Step 3, `sp:code-improvement` Step 1, and `sp:super-reviewer` "Establish
|
|
101
|
+
scope first" defer here and must not restate it.
|
|
102
|
+
|
|
103
|
+
WBS mode — the union of files changed by the task's implementation commits, identified by the
|
|
104
|
+
`(<wbs>)` subject tag, excluding the task file itself:
|
|
100
105
|
|
|
101
106
|
```bash
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
git
|
|
105
|
-
|
|
107
|
+
ROOT=$(git rev-parse --show-toplevel)
|
|
108
|
+
TASK_FILE=$(cd "$ROOT" && spur task show <wbs> --json | jq -r .filePath)
|
|
109
|
+
COMMITS=$(git log --format='%H %s' | grep -F "(<wbs>)" | cut -d' ' -f1)
|
|
110
|
+
for c in $COMMITS; do git diff-tree --no-commit-id --name-only -r "$c"; done \
|
|
111
|
+
| sort -u | grep -vxF "${TASK_FILE#$ROOT/}"
|
|
112
|
+
# Empty scope → working-tree diff.
|
|
106
113
|
git status --porcelain
|
|
107
114
|
```
|
|
108
115
|
|
|
116
|
+
- No file-extension filter — `.md`/`.yaml` are first-class harness surfaces. Generated/lock files
|
|
117
|
+
(`*.generated.mjs`, `bun.lock`) may be excluded by name, stated in the Scope line.
|
|
118
|
+
- Empty scope (no tagged commit, or tagged commits touch only the task file) degrades visibly:
|
|
119
|
+
state `working tree` and the reason in the Scope line.
|
|
120
|
+
|
|
121
|
+
### Step 3p — Path scope (review mode only)
|
|
122
|
+
|
|
123
|
+
When the review target is a path, not a WBS, Step 3 does not apply — derive the scope from the
|
|
124
|
+
path: the tracked files under it, with the file count reported in the Scope line.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
git ls-files -- <path>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Large scopes are fanned out by the review coordinator, not here.
|
|
131
|
+
|
|
109
132
|
### Step 4 — Requirements traceability gate (Phase 8)
|
|
110
133
|
|
|
111
134
|
For each `R{n}` in `## Requirements`, find implementation evidence in the changed files / tests and
|
|
@@ -338,7 +361,8 @@ canonical.
|
|
|
338
361
|
Under the pipeline, the verifier owns the answer file `.spur/run/<wbs>-verify-answer.txt` (0726
|
|
339
362
|
R3): `Verdict: PARTIAL` first, append one row at a time, replace the verdict line only when all
|
|
340
363
|
rows are certified — interruptions leave lintable partial rows; retries fill only missing IDs.
|
|
341
|
-
Host: `
|
|
364
|
+
Host: `spur task verdict --from-answer` lints the answer file, then derives the verdict +
|
|
365
|
+
`spur task check` (R9). Vocabularies,
|
|
342
366
|
rejection classes, and the AC identity rule: `references/verdict-schema.md`. Sections follow the
|
|
343
367
|
Step 10 contract.
|
|
344
368
|
|
|
@@ -481,10 +505,11 @@ re-audit is never misread as a successful `testing -> done` (dev-verify.md `--ne
|
|
|
481
505
|
## Mode: review (`/sp:dev-review`)
|
|
482
506
|
|
|
483
507
|
The source-oriented path: SECUA review of a task's diff without the full traceability verdict. Runs
|
|
484
|
-
|
|
508
|
+
Step 3 (WBS target) or Step 3p (path target — a path target never derives scope from Step 3) plus
|
|
509
|
+
Step 7, and returns a **review fragment** — no verdict artifact, no section write, no `done`
|
|
485
510
|
gate (F92 0593 R1); the coordinator (`sp:super-reviewer`) merges fragments into `## Review`.
|
|
486
511
|
|
|
487
|
-
Flags: `--agent <inline|auto|name>` (execution surface — inline default, with named escalation triggers taking precedence), `--auto` (no confirmations),
|
|
512
|
+
Flags: `--agent <inline|auto|name>` (execution surface — inline default, with named escalation triggers taking precedence), `--auto` (no confirmations), and `--focus <all|functional|security|efficiency|correctness|usability|architecture>` (review dimensions — **the single review `--focus` vocabulary is declared here (SSOT)**: `functional` routes to `sp:functional-review`, `architecture` to `sp:code-improvement` + SECUA-A, the SECUA lenses to Step 7; `dev-review.md`, `dev-operations.md` §2 and `flag-glossary.md#flag-focus` link here instead of restating a second list). Apply the [central contract](../spur-dev/references/cross-cutting.md#inline-default-execution-surface) before starting the review.
|
|
488
513
|
|
|
489
514
|
---
|
|
490
515
|
|
|
@@ -108,8 +108,8 @@ For answer files, emit a matching parseable table:
|
|
|
108
108
|
|
|
109
109
|
**Authoring contract under the pipeline (0726 R3).** The verifier owns the answer file: write
|
|
110
110
|
`Verdict: PARTIAL` first, append one complete row at a time, and replace the first verdict line only
|
|
111
|
-
after every row is certified. `
|
|
112
|
-
|
|
111
|
+
after every row is certified. `spur task verdict --from-answer` lints the file before deriving the
|
|
112
|
+
verdict and rejects, with row-level diagnostics: missing/duplicate/unknown requirement IDs,
|
|
113
113
|
AC ids that do not resolve to one accepted identity — a task AC checklist label or its declared
|
|
114
114
|
`AC-N`/checklist-token alias, or a linked feature scenario title, in the ac-style-guide forms
|
|
115
115
|
(exact/bare title, `Scenario:` prefix, bracket tags, `AC-N`); paraphrases and ambiguous aliases
|
|
@@ -119,7 +119,7 @@ manual-review | llm-judge | n/a`, or a `+` compound), and empty evidence. Interr
|
|
|
119
119
|
rows that pass the lint and complete only the missing IDs on retry.
|
|
120
120
|
|
|
121
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 `
|
|
122
|
+
by a bare checklist ordinal (`AC1`) passes the `spur task verdict` answer lint — it resolves to the task's AC
|
|
123
123
|
checklist label — but `feature-check.rowMatchesScenario` credits a linked feature scenario only by the
|
|
124
124
|
scenario's normalized title or its `AC-N` ordinal alias. Such a row therefore carries a PASS verdict
|
|
125
125
|
while crediting no scenario, and the feature's `verifying → done` gate denies with
|
|
@@ -11,6 +11,7 @@ metadata:
|
|
|
11
11
|
- pipeline
|
|
12
12
|
modes:
|
|
13
13
|
- verify
|
|
14
|
+
- review
|
|
14
15
|
verdicts:
|
|
15
16
|
- PASS
|
|
16
17
|
- PARTIAL
|
|
@@ -214,8 +215,8 @@ return `verdict = PASS` with "No requirements to verify" (zero-requirements case
|
|
|
214
215
|
### Step 3 — Establish the change scope
|
|
215
216
|
|
|
216
217
|
If `--source-paths` is given, use it. Otherwise derive the diff scope exactly as
|
|
217
|
-
`sp:code-verification` Step 3 does (the
|
|
218
|
-
|
|
218
|
+
`sp:code-verification` Step 3 does (the SSOT recipe: the `(<wbs>)`-tagged implementation commits'
|
|
219
|
+
changed files, fallback to the working-tree diff — defined there, not restated here).
|
|
219
220
|
|
|
220
221
|
### Step 4 — Track A: BDD mapping (if `--bdd-report`)
|
|
221
222
|
|
|
@@ -268,7 +269,8 @@ The priority table leads; the traceability table follows for per-requirement det
|
|
|
268
269
|
**Fragment-only discipline (F92 0593 R1).** In coordinated mode (dispatched by `/sp:dev-review` /
|
|
269
270
|
`sp:super-reviewer`), do **not** write `## Review` — return the fragment to the coordinator, which
|
|
270
271
|
merges the functional + SECUA + architecture fragments into the combined `## Review` section. Only
|
|
271
|
-
`sp:super-reviewer` (the
|
|
272
|
+
`sp:super-reviewer` (the invoking session under `/sp:dev-review` acting as review coordinator)
|
|
273
|
+
writes `## Review`; direct component-skill use is
|
|
272
274
|
advisory output. `spur task record` backfills a **bare** `## Review` from the verdict artifact as a
|
|
273
275
|
standalone compatibility fallback only and never overwrites authored Review (F92 0593 R1).
|
|
274
276
|
|
|
@@ -291,7 +293,8 @@ Include the per-requirement traceability table in the report:
|
|
|
291
293
|
```
|
|
292
294
|
|
|
293
295
|
**Under the pipeline**, `sp:functional-review` is a component of `/sp:dev-review`: it returns its
|
|
294
|
-
fragment to the coordinator (`sp:super-reviewer`
|
|
296
|
+
fragment to the coordinator (`sp:super-reviewer` — the invoking session under `/sp:dev-review`),
|
|
297
|
+
which writes the combined `## Review`. The
|
|
295
298
|
`record` step transcribes only `## Testing` from the verdict artifact and backfills `## Review`
|
|
296
299
|
only when the section is bare (`sectionIsBare` guard, `task-service.ts`); it never overwrites the
|
|
297
300
|
coordinator's authored Review. Keep the priority-table lead stable in the fragment so the L3 gate
|
|
@@ -27,7 +27,7 @@ interface FunctionalVerdict {
|
|
|
27
27
|
bddReportPath: string | null;
|
|
28
28
|
/**
|
|
29
29
|
* Explicit source scope (if --source-paths given) or derived diff scope
|
|
30
|
-
* (
|
|
30
|
+
* (tagged-commit recipe; SSOT = code-verification SKILL Step 3/3p).
|
|
31
31
|
*/
|
|
32
32
|
sourcePaths: string[];
|
|
33
33
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The skill resolves exactly two modes. Everything else fails loud. This matrix is the enforcement
|
|
4
4
|
surface the workflow (0660) and the skill share; keep the vocabulary frozen. Execution note
|
|
5
5
|
(0920): argument validation runs deterministically in the `history-anatomy-cache` helper `paths`
|
|
6
|
-
command
|
|
6
|
+
command (logic core: `packages/app/src/services/history-anatomy.ts`, task 1005) before the
|
|
7
|
+
workflow starts; the model hop no longer performs it.
|
|
7
8
|
|
|
8
9
|
## Mode vocabulary (frozen)
|
|
9
10
|
|
|
@@ -78,7 +78,7 @@ survivors would make its primary case unreachable. Offer the rank-1 ranked candi
|
|
|
78
78
|
**Why this is not a Principle #5 taste auto-click.** The ranking already produced the recommendation;
|
|
79
79
|
`--auto` only skips the *proceed-with-offer* pause. Overriding the ranking (picking a non-offered
|
|
80
80
|
candidate) still requires the interactive confirm path. Architecture / design-approval taste gates
|
|
81
|
-
inside dispatched children remain governed by their own contracts (`--
|
|
81
|
+
inside dispatched children remain governed by their own contracts (`--auto` where applicable).
|
|
82
82
|
|
|
83
83
|
### What `--task` does not change
|
|
84
84
|
|
|
@@ -131,7 +131,7 @@ call; no git dirtiness as a route (optional advisory print only).
|
|
|
131
131
|
| C2 and C3 both true | lint vs tests both red | (1) fixall (2) unit (3) abort |
|
|
132
132
|
| Feature B3 pick ambiguous because two `todo` same rank — **should not happen** after WBS sort | — | N/A — WBS tie-break is total |
|
|
133
133
|
| Task `todo` but also feature-level AC invalid when invoked via feature id | feature health vs task progress | (1) fix feature AC (2) proceed with task A3 |
|
|
134
|
-
| `testing` with open P1 in Review section | verify vs review-fix | (1) `/sp:dev-review <wbs> --
|
|
134
|
+
| `testing` with open P1 in Review section | verify vs review-fix | (1) `/sp:dev-review --tasks <wbs> --triage` (2) verify anyway |
|
|
135
135
|
| `wip` with both checkpoint and dirty Solution L3 | resume vs re-implement | (1) `--continue` (2) implement `--next` |
|
|
136
136
|
|
|
137
137
|
When HITL STOP fires: print decision-brief (question, stakes, recommended option, alternatives).
|
|
@@ -91,6 +91,16 @@ Generate the code and **cite the source** (doc URL + date, or `pkg@version` + th
|
|
|
91
91
|
point of use or in the task's notes. A claim about external behavior without a citation is
|
|
92
92
|
unverified — flag it as such rather than presenting it as fact.
|
|
93
93
|
|
|
94
|
+
### Step 5 — Close with a verification verdict
|
|
95
|
+
|
|
96
|
+
Before finishing, verify the citations for the API/library claims you emitted and close with one
|
|
97
|
+
line: `Verification: HIGH|MEDIUM|LOW — <first failing check and deviation, or 'all checks passed'>`.
|
|
98
|
+
|
|
99
|
+
- **Citation coverage** — every API/library claim in the output carries a source citation (doc URL + date, or `pkg@version` + file/symbol); no uncited claim is asserted as fact.
|
|
100
|
+
- **Source authority** — each citation points at the pinned version's primary source (installed signature/source, or official docs for the lockfile version), not the latest docs or memory.
|
|
101
|
+
- **Honest confidence** — the stated HIGH/MEDIUM/LOW matches the evidence: HIGH requires a check against the pinned source this session; MEDIUM/LOW items are labeled, and LOW is never presented as fact.
|
|
102
|
+
- **HIGH** — all three checks pass. **MEDIUM** — coverage intact but some citations are off-version (latest docs for a pinned dependency) or secondary-source only. **LOW** — an API/library claim lacks any citation, or a LOW-confidence claim was asserted as fact.
|
|
103
|
+
|
|
94
104
|
## Common Rationalizations
|
|
95
105
|
|
|
96
106
|
| Rationalization | Reality |
|
|
@@ -116,6 +126,7 @@ unverified — flag it as such rather than presenting it as fact.
|
|
|
116
126
|
- [ ] Both questions answered — the API exists **and** is called within its contract.
|
|
117
127
|
- [ ] Claims about external behavior carry a citation (source + date, or `pkg@version` + symbol); unverifiable ones are flagged, not asserted.
|
|
118
128
|
- [ ] Confidence is stated honestly (HIGH/MEDIUM/LOW); nothing at LOW was presented as fact.
|
|
129
|
+
- [ ] Run closes with the Step 5 verdict line: `Verification: HIGH|MEDIUM|LOW — <first failing check and deviation, or 'all checks passed'>`.
|
|
119
130
|
|
|
120
131
|
## See also
|
|
121
132
|
|
|
@@ -46,7 +46,7 @@ Pick the noun, read its reference. Each Tier A and Tier B reference owns that no
|
|
|
46
46
|
| **Tier A** | **builder** | Release plumbing: bump a package (or the `workspace:`-pinned set) with `bump-ver`, delete release tags with `drop-tags`, commit + tag + optional push | [references/builder.md](references/builder.md) |
|
|
47
47
|
| **Tier B** | **agent** | Coding-agent execution surface: run prompts via detected/named agents, list agent specs, start/stop supervised processes, readiness check | [references/agent.md](references/agent.md) |
|
|
48
48
|
| **Tier B** | **message** | Durable inter-agent messaging: send, inbox, reply, watch | [references/message.md](references/message.md) |
|
|
49
|
-
| **Tier B** | **self** | Self-management verbs: scaffold (`init`), schema migrations (`migrate`), local web server (`serve`), status overview (`status`); `self init` runs post-scaffold validation probes & layout classification | [references/self.md](references/self.md) |
|
|
49
|
+
| **Tier B** | **self** | Self-management verbs: scaffold (`init`), database maintenance (`maintain`), schema migrations (`migrate`), local web server (`serve`), status overview (`status`); `self init` runs post-scaffold validation probes & layout classification | [references/self.md](references/self.md) |
|
|
50
50
|
| **Tier B** | **history** | Import agent histories, aggregate forensic artifacts, render reports, and run the checkpoint-resumed daily pipeline | [references/history.md](references/history.md) |
|
|
51
51
|
| **Tier B** | **projects** | Manage the local multi-project registry and start/stop project servers | [references/projects.md](references/projects.md) |
|
|
52
52
|
| **Tier C** | **help** | Commander-generated help command; not a Spur noun | Generated `--help` |
|
|
@@ -141,8 +141,8 @@ and spreading it; full contract in `docs/04_DESIGN.md` §1.0.1.
|
|
|
141
141
|
[dispatch-surface rule](../parallel-execution/references/dispatch-surface.md).
|
|
142
142
|
- **[references/message.md](references/message.md)** - durable inter-agent messaging (`send`,
|
|
143
143
|
`inbox`, `reply`, `watch`).
|
|
144
|
-
- **[references/self.md](references/self.md)** - `spur self init|migrate|serve|status` CLI verbs
|
|
145
|
-
(the
|
|
144
|
+
- **[references/self.md](references/self.md)** - `spur self init|maintain|migrate|serve|status` CLI verbs
|
|
145
|
+
(the five legacy top-level nouns remain hidden aliases). `self init` runs post-scaffold init
|
|
146
146
|
validation (Phase 1.5/1.6 probes).
|
|
147
147
|
- **[references/history.md](references/history.md)** - history import, forensic artifact analysis,
|
|
148
148
|
pure report rendering, and the daily pipeline.
|