@gobing-ai/spur 0.4.3 → 0.4.5
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/rules/docs/sourced-api-claims.yaml +3 -2
- package/config/rules/quality/coverage-gate.yaml +13 -0
- package/config/rules/recommended-pre-check.yaml +11 -5
- package/config/rules/strict/runtime-boundaries.yaml +3 -0
- package/config/rules/surface/check-cli-surface.yaml +1 -3
- package/config/templates/AGENTS.md +7 -0
- package/config/workflows/feature-verification.yaml +9 -1
- package/config/workflows/task-lifecycle.yaml +50 -0
- package/config/workflows/task-pipeline.yaml +31 -65
- package/package.json +10 -10
- package/plugins/sp/agents/super-reviewer.md +11 -24
- package/plugins/sp/commands/dev-changelog.md +1 -1
- package/plugins/sp/commands/dev-gitmsg.md +6 -2
- package/plugins/sp/commands/dev-handover.md +1 -0
- package/plugins/sp/commands/dev-job-dump.md +2 -1
- package/plugins/sp/commands/dev-refine.md +14 -2
- package/plugins/sp/commands/dev-refineall.md +7 -1
- package/plugins/sp/commands/dev-review.md +1 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +18 -18
- package/plugins/sp/lib/idea-handoff.generated.mjs +91 -91
- package/plugins/sp/lib/inline-run.generated.mjs +233 -277
- package/plugins/sp/lib/plan-projection.generated.mjs +62 -38
- package/plugins/sp/lib/quality-gate.generated.d.mts +1 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +13793 -13966
- package/plugins/sp/lib/residual-scan.generated.mjs +20 -34
- package/plugins/sp/lib/step-profile.generated.mjs +8 -8
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +62 -40
- package/plugins/sp/scripts/batch-preflight.mjs +34 -12
- package/plugins/sp/scripts/commit-guard.mjs +6 -6
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +26 -28
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +11 -11
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +4 -4
- package/plugins/sp/scripts/feature-verification-steps.mjs +15 -4
- package/plugins/sp/scripts/feature-verification-steps.ts +29 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +19 -19
- package/plugins/sp/scripts/idea-handoff.mjs +2 -2
- package/plugins/sp/scripts/inline-run-dispatch.mjs +2 -2
- package/plugins/sp/scripts/inline-run-setup.mjs +20 -20
- package/plugins/sp/scripts/persist-out-check.mjs +5 -5
- package/plugins/sp/scripts/pr-reviewing.mjs +13 -13
- package/plugins/sp/scripts/quality-gate.mjs +13715 -13884
- package/plugins/sp/scripts/residual-scan.mjs +24 -38
- package/plugins/sp/scripts/residual-scan.ts +4 -6
- package/plugins/sp/scripts/run-summary.mjs +15 -15
- package/plugins/sp/scripts/script-root.mjs +6 -6
- package/plugins/sp/scripts/session-timeline.mjs +16 -16
- package/plugins/sp/scripts/task-diffstat.mjs +8 -8
- package/plugins/sp/scripts/workflow-step-profile.mjs +12 -12
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +9 -9
- package/plugins/sp/scripts/wrapup-steps.mjs +25 -25
- package/plugins/sp/skills/code-verification/SKILL.md +1 -2
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -32
- package/plugins/sp/skills/next-router/SKILL.md +3 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +9 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +24 -4
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +8 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -11
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +6 -3
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +7 -11
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +4 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -8
- package/schemas/section-matrix.schema.json +3 -2
- package/spur.js +24597 -32769
- package/web/_astro/{BoardApp.5IIl2O-z.js → BoardApp.Niubpe6z.js} +1 -1
- package/web/_astro/{BoardApp.Drnx-KeM.js → BoardApp.hF0RAIrg.js} +112 -112
- package/web/_astro/TaskDetail.DJpapt38.js +1 -0
- package/web/_astro/{arc.j2EVkYVd.js → arc.BgKcK0i8.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.DZWSFqEG.js → architectureDiagram-3BPJPVTR.ChP-Ig0V.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DE6GpXCN.js → blockDiagram-GPEHLZMM.BVaCBoeN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DoVR1G6B.js → c4Diagram-AAUBKEIU.CHSDpEO5.js} +1 -1
- package/web/_astro/channel.DkpvN2tD.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.Do6-Gbyf.js → chunk-2J33WTMH.MYRcYVRa.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BYV1HGHx.js → chunk-4BX2VUAB.S5j22Zqa.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.CvI1zkrc.js → chunk-55IACEB6.gciRPvmZ.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DNGSCRlX.js → chunk-727SXJPM.CdqrSQkX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DXhhgT5z.js → chunk-AQP2D5EJ.CyokdHT3.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.dzkRKP2u.js → chunk-FMBD7UC4.Bg9KJhjW.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.DujGLeEf.js → chunk-ND2GUHAM.1gxxI-vD.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BWtIT3ZU.js → chunk-QZHKN3VN.DDEYtCKU.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.DyApg0Nn.js → classDiagram-4FO5ZUOK.Ch3zi37T.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.DyApg0Nn.js → classDiagram-v2-Q7XG4LA2.Ch3zi37T.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DJQB-u_l.js → cose-bilkent-S5V4N54A.D2q5AINI.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.bi3D_siV.js → cynefin-OW5HDTMX.BA9kC2YT.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.C-f7TrRg.js → dagre-BM42HDAG.CHO08nRp.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DrPfjHQB.js → diagram-2AECGRRQ.BfFQt0LW.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.C9S4WUHO.js → diagram-5GNKFQAL.CHMwMTe4.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Dzzye8QH.js → diagram-KO2AKTUF.BJ-7gK-w.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.DX-LRiof.js → diagram-LMA3HP47.C5xqcc0t.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.Z-kAKwIz.js → diagram-OG6HWLK6.C2pQjfrC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.D6dyClSv.js → erDiagram-TEJ5UH35._FoNyxgC.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.Cx3q2cxD.js → flowDiagram-I6XJVG4X.B_ENkT2I.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DU3gH6CT.js → ganttDiagram-6RSMTGT7.B3q8h77C.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.ISmCnJ87.js → gitGraphDiagram-PVQCEYII.QWZFDboZ.js} +1 -1
- package/web/_astro/index.BFyW13JJ.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DR6_iNOK.js → infoDiagram-5YYISTIA.Cs6eEc92.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js → ishikawaDiagram-YF4QCWOH.BU9DJMHA.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DkP9-WK8.js → journeyDiagram-JHISSGLW.D35k6Jpe.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.wtgY98M2.js → kanban-definition-UN3LZRKU.DBqcAT4m.js} +1 -1
- package/web/_astro/{linear.CI6djNi7.js → linear.D-LSNK8W.js} +1 -1
- package/web/_astro/{mermaid.core.CuQPxS8b.js → mermaid.core.CSruWVOa.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.5HNIEG7R.js → mindmap-definition-RKZ34NQL.BiFaUsfj.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CchhDFjE.js → pieDiagram-4H26LBE5.D1KTKqb9.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.PYXXJPSJ.js → quadrantDiagram-W4KKPZXB.DBHflsMG.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.cUQuNAKU.js → requirementDiagram-4Y6WPE33.hwX9CNdk.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BO7MnRIA.js → sankeyDiagram-5OEKKPKP.DkI8mJiE.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.D9bElyD6.js → sequenceDiagram-3UESZ5HK.DMz7jBDx.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DhGdRz7I.js → stateDiagram-AJRCARHV.B6Ef2xJ1.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.5GvLeAoL.js → stateDiagram-v2-BHNVJYJU.CgoaufB9.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.LtAT9haM.js → timeline-definition-PNZ67QCA.B3c8h1t-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DRth1yLA.js → vennDiagram-CIIHVFJN.BdHPAxAb.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.h9MlZfcr.js → wardleyDiagram-YWT4CUSO.BrJXPPAj.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DHqy9DYA.js → xychartDiagram-2RQKCTM6.CMH5yFsV.js} +1 -1
- package/web/index.html +2 -2
- package/web/spur_logo.svg +1 -1
- package/web/_astro/TaskDetail.Ci4LNn9S.js +0 -1
- package/web/_astro/channel.DmdDxGsq.js +0 -1
- package/web/_astro/index.Do2lLdgo.css +0 -1
|
@@ -33,14 +33,6 @@ function hasClaimMarker(evidence) {
|
|
|
33
33
|
function hasCitationForm(evidence) {
|
|
34
34
|
return CITATION_FORMS.some((re) => re.test(evidence));
|
|
35
35
|
}
|
|
36
|
-
var CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
37
|
-
function extractAnswerConfidence(text) {
|
|
38
|
-
const matches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
|
|
39
|
-
if (matches.length !== 1)
|
|
40
|
-
return;
|
|
41
|
-
const value = (matches[0]?.[1] ?? "").toUpperCase();
|
|
42
|
-
return CONFIDENCE_LEVELS.includes(value) ? value : undefined;
|
|
43
|
-
}
|
|
44
36
|
var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
|
|
45
37
|
var PRIORITY_PATTERN = /^P[1-4]/;
|
|
46
38
|
var RANGE_PRIORITY = /^P[1-4]\s*[\u2013\u2014-]\s*P?[1-4]/;
|
|
@@ -212,13 +204,6 @@ function blockingReviewFindings(taskContent, deferrals) {
|
|
|
212
204
|
}
|
|
213
205
|
function reviewContractViolations(taskContent) {
|
|
214
206
|
const violations = [];
|
|
215
|
-
const body = reviewBody(taskContent);
|
|
216
|
-
if (body !== null && extractAnswerConfidence(body.replace(/\*\*/g, "")) === undefined) {
|
|
217
|
-
violations.push({
|
|
218
|
-
location: "task-file:Review",
|
|
219
|
-
text: "Review report carries no single valid `**Confidence:** HIGH | MEDIUM | LOW` line — " + "state the level the report can defend (HIGH: every requirement row MET, every finding " + "dispositioned, evidence cited; MEDIUM/LOW: name the gap)."
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
207
|
for (const row of parseReviewFindings(taskContent)) {
|
|
223
208
|
if (hasClaimMarker(row.text) && !hasCitationForm(row.text)) {
|
|
224
209
|
violations.push({
|
|
@@ -230,9 +215,10 @@ function reviewContractViolations(taskContent) {
|
|
|
230
215
|
return violations;
|
|
231
216
|
}
|
|
232
217
|
function buildReviewGateArtifact(wbs, taskContent, deferrals) {
|
|
218
|
+
const contractViolations = reviewContractViolations(taskContent);
|
|
233
219
|
const items = [
|
|
234
220
|
...blockingReviewFindings(taskContent, deferrals),
|
|
235
|
-
...
|
|
221
|
+
...contractViolations.map((v) => ({
|
|
236
222
|
id: makeItemId("review-contract", v.location, v.text),
|
|
237
223
|
category: "review-contract",
|
|
238
224
|
class: "blocking",
|
|
@@ -622,27 +608,27 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
622
608
|
}
|
|
623
609
|
process.exit(main(process.argv.slice(2)));
|
|
624
610
|
export {
|
|
625
|
-
|
|
626
|
-
scanResiduals2 as scanResiduals,
|
|
627
|
-
reviewContractViolations,
|
|
628
|
-
renderReport,
|
|
629
|
-
recordedVerdictPath,
|
|
630
|
-
parseScanArgs,
|
|
631
|
-
parseReviewFindings,
|
|
632
|
-
parseDiffMarkers,
|
|
633
|
-
parseDeferralEntries,
|
|
634
|
-
normalizeAnchor,
|
|
635
|
-
makeItemId,
|
|
636
|
-
main,
|
|
637
|
-
locationOf,
|
|
638
|
-
listStagingResidue,
|
|
639
|
-
foldVerdict,
|
|
640
|
-
findUncheckedBoxes,
|
|
641
|
-
collectAddedLines,
|
|
642
|
-
classify,
|
|
643
|
-
buildReviewGateArtifact,
|
|
644
|
-
blockingReviewFindings,
|
|
645
|
-
blockingAnchors,
|
|
611
|
+
ALLOW_PRAGMA,
|
|
646
612
|
RESIDUAL_SCAN_USAGE,
|
|
647
|
-
|
|
613
|
+
blockingAnchors,
|
|
614
|
+
blockingReviewFindings,
|
|
615
|
+
buildReviewGateArtifact,
|
|
616
|
+
classify,
|
|
617
|
+
collectAddedLines,
|
|
618
|
+
findUncheckedBoxes,
|
|
619
|
+
foldVerdict,
|
|
620
|
+
listStagingResidue,
|
|
621
|
+
locationOf,
|
|
622
|
+
main,
|
|
623
|
+
makeItemId,
|
|
624
|
+
normalizeAnchor,
|
|
625
|
+
parseDeferralEntries,
|
|
626
|
+
parseDiffMarkers,
|
|
627
|
+
parseReviewFindings,
|
|
628
|
+
parseScanArgs,
|
|
629
|
+
recordedVerdictPath,
|
|
630
|
+
renderReport,
|
|
631
|
+
reviewContractViolations,
|
|
632
|
+
scanResiduals2 as scanResiduals,
|
|
633
|
+
verdictDisagreementNote
|
|
648
634
|
};
|
|
@@ -104,12 +104,10 @@ function scanMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
|
104
104
|
return 0;
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* Task 1122 R2: the review-time gate — the record sweep's review-finding slice
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
* authority and overwrites it. Exit 1 on an open P1-P3 finding fails the review PASS edge closed
|
|
112
|
-
* into review-fail-triage.
|
|
107
|
+
* Task 1122 R2: the review-time gate — the record sweep's review-finding slice
|
|
108
|
+
* (core.buildReviewGateArtifact) at the review PASS edges; writes the same `<wbs>-residuals.json`
|
|
109
|
+
* the test-fix hop reads. Exit 1 on an open P1-P3 finding. The answer-grade rule (1204) was
|
|
110
|
+
* retired with the asserted confidence machinery (F511); findings stay the gate.
|
|
113
111
|
*/
|
|
114
112
|
function reviewGateMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
115
113
|
const runDir = join(opts.root, '.spur', 'run');
|
|
@@ -114,21 +114,21 @@ function accumulate(acc, row, ts) {
|
|
|
114
114
|
if (row.type === "message") {
|
|
115
115
|
const m = row.message;
|
|
116
116
|
if (m?.role === "assistant") {
|
|
117
|
-
const
|
|
118
|
-
for (const b of
|
|
117
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
118
|
+
for (const b of blocks) {
|
|
119
119
|
if (b.type === "toolCall" && b.id) {
|
|
120
120
|
acc.toolIds.add(b.id);
|
|
121
121
|
if (b.name && OPERATOR_TOOLS.has(b.name))
|
|
122
122
|
acc.asks.set(b.id, ts);
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
125
|
+
const u = m.usage;
|
|
126
|
+
if (u && row.id)
|
|
127
127
|
acc.messages.set(row.id, {
|
|
128
|
-
input:
|
|
129
|
-
cacheCreate:
|
|
130
|
-
cacheRead:
|
|
131
|
-
output:
|
|
128
|
+
input: u.input ?? 0,
|
|
129
|
+
cacheCreate: u.cacheWrite ?? 0,
|
|
130
|
+
cacheRead: u.cacheRead ?? 0,
|
|
131
|
+
output: u.output ?? 0
|
|
132
132
|
});
|
|
133
133
|
} else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
|
|
134
134
|
acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
|
|
@@ -747,12 +747,12 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
747
747
|
}
|
|
748
748
|
process.exit(main(process.argv.slice(2)));
|
|
749
749
|
export {
|
|
750
|
-
|
|
751
|
-
renderSummaryMarkdown,
|
|
752
|
-
renderRollupMarkdown,
|
|
753
|
-
main,
|
|
754
|
-
gateFromReceipt,
|
|
755
|
-
buildRunSummary,
|
|
750
|
+
RUN_SUMMARY_USAGE,
|
|
756
751
|
actorSplit,
|
|
757
|
-
|
|
752
|
+
buildRunSummary,
|
|
753
|
+
gateFromReceipt,
|
|
754
|
+
main,
|
|
755
|
+
renderRollupMarkdown,
|
|
756
|
+
renderSummaryMarkdown,
|
|
757
|
+
writeCloseSummary
|
|
758
758
|
};
|
|
@@ -119,11 +119,11 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
119
119
|
process.exit(main(process.argv.slice(2)));
|
|
120
120
|
}
|
|
121
121
|
export {
|
|
122
|
-
|
|
123
|
-
main,
|
|
124
|
-
digestScriptSet,
|
|
125
|
-
SOURCE_REPO_MARKER,
|
|
126
|
-
SCRIPT_ROOT_USAGE,
|
|
122
|
+
PROJECT_SCRIPTS_DIR,
|
|
127
123
|
SCRIPT_ROOT_TWIN_REL,
|
|
128
|
-
|
|
124
|
+
SCRIPT_ROOT_USAGE,
|
|
125
|
+
SOURCE_REPO_MARKER,
|
|
126
|
+
digestScriptSet,
|
|
127
|
+
main,
|
|
128
|
+
runScriptRoot
|
|
129
129
|
};
|
|
@@ -107,21 +107,21 @@ function accumulate(acc, row, ts) {
|
|
|
107
107
|
if (row.type === "message") {
|
|
108
108
|
const m = row.message;
|
|
109
109
|
if (m?.role === "assistant") {
|
|
110
|
-
const
|
|
111
|
-
for (const b of
|
|
110
|
+
const blocks = Array.isArray(m.content) ? m.content : [];
|
|
111
|
+
for (const b of blocks) {
|
|
112
112
|
if (b.type === "toolCall" && b.id) {
|
|
113
113
|
acc.toolIds.add(b.id);
|
|
114
114
|
if (b.name && OPERATOR_TOOLS.has(b.name))
|
|
115
115
|
acc.asks.set(b.id, ts);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
118
|
+
const u = m.usage;
|
|
119
|
+
if (u && row.id)
|
|
120
120
|
acc.messages.set(row.id, {
|
|
121
|
-
input:
|
|
122
|
-
cacheCreate:
|
|
123
|
-
cacheRead:
|
|
124
|
-
output:
|
|
121
|
+
input: u.input ?? 0,
|
|
122
|
+
cacheCreate: u.cacheWrite ?? 0,
|
|
123
|
+
cacheRead: u.cacheRead ?? 0,
|
|
124
|
+
output: u.output ?? 0
|
|
125
125
|
});
|
|
126
126
|
} else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
|
|
127
127
|
acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
|
|
@@ -347,13 +347,13 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
347
347
|
}
|
|
348
348
|
process.exit(main(process.argv.slice(2)));
|
|
349
349
|
export {
|
|
350
|
-
|
|
351
|
-
resolveTranscript,
|
|
352
|
-
parseGroups,
|
|
353
|
-
main,
|
|
354
|
-
formatTokens,
|
|
355
|
-
formatTokenSplit,
|
|
356
|
-
formatDuration,
|
|
350
|
+
SESSION_TIMELINE_USAGE,
|
|
357
351
|
buildTimeline,
|
|
358
|
-
|
|
352
|
+
formatDuration,
|
|
353
|
+
formatTokenSplit,
|
|
354
|
+
formatTokens,
|
|
355
|
+
main,
|
|
356
|
+
parseGroups,
|
|
357
|
+
resolveTranscript,
|
|
358
|
+
zeroSegmentReason
|
|
359
359
|
};
|
|
@@ -84,9 +84,9 @@ function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
|
|
|
84
84
|
const failSafe = (reason) => {
|
|
85
85
|
process.stderr.write(`task-diffstat: ${reason} \u2014 failing safe (sensitive)
|
|
86
86
|
`);
|
|
87
|
-
const
|
|
88
|
-
writeArtifact(abs(relResult),
|
|
89
|
-
return { ...
|
|
87
|
+
const row = { files: 0, insertions: 0, deletions: 0, paths: [], sensitive: true };
|
|
88
|
+
writeArtifact(abs(relResult), row);
|
|
89
|
+
return { ...row, resultFile: relResult, exitCode: 0 };
|
|
90
90
|
};
|
|
91
91
|
let stdoutIsArtifact = false;
|
|
92
92
|
try {
|
|
@@ -172,10 +172,10 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
172
172
|
process.exit(main(process.argv.slice(2)));
|
|
173
173
|
}
|
|
174
174
|
export {
|
|
175
|
-
|
|
176
|
-
runDiffstat,
|
|
177
|
-
main,
|
|
178
|
-
TASK_DIFFSTAT_USAGE,
|
|
175
|
+
SENSITIVE_GLOBS,
|
|
179
176
|
SENSITIVE_PREFIXES,
|
|
180
|
-
|
|
177
|
+
TASK_DIFFSTAT_USAGE,
|
|
178
|
+
main,
|
|
179
|
+
runDiffstat,
|
|
180
|
+
sensitiveReasonForPath
|
|
181
181
|
};
|
|
@@ -322,17 +322,17 @@ function main(argv) {
|
|
|
322
322
|
process.exit(main(process.argv.slice(2)));
|
|
323
323
|
}
|
|
324
324
|
export {
|
|
325
|
-
|
|
326
|
-
rowFlags,
|
|
327
|
-
parseStepProfileCliArgs,
|
|
328
|
-
nonDryRuns,
|
|
329
|
-
nearestRankP50,
|
|
330
|
-
main,
|
|
331
|
-
formatStepProfileHuman,
|
|
332
|
-
extractExecutions,
|
|
333
|
-
buildStepProfile,
|
|
334
|
-
buildRows,
|
|
335
|
-
STEP_PROFILE_USAGE,
|
|
325
|
+
DEFAULT_LAST,
|
|
336
326
|
DEFAULT_WINDOW_SEC,
|
|
337
|
-
|
|
327
|
+
STEP_PROFILE_USAGE,
|
|
328
|
+
buildRows,
|
|
329
|
+
buildStepProfile,
|
|
330
|
+
extractExecutions,
|
|
331
|
+
formatStepProfileHuman,
|
|
332
|
+
main,
|
|
333
|
+
nearestRankP50,
|
|
334
|
+
nonDryRuns,
|
|
335
|
+
parseStepProfileCliArgs,
|
|
336
|
+
rowFlags,
|
|
337
|
+
runStepProfileCli
|
|
338
338
|
};
|
|
@@ -114,13 +114,13 @@ function runDriftProbe(env, options = {}, spur = defaultSpurRunner(env, options.
|
|
|
114
114
|
mkdirSync(abs(join(".spur", "run")), { recursive: true });
|
|
115
115
|
writeFileSync(abs(relModeFile), `
|
|
116
116
|
`);
|
|
117
|
-
const finish = (clean,
|
|
118
|
-
writeFileSync(abs(relProbeFile), `${JSON.stringify({ clean, reasons
|
|
117
|
+
const finish = (clean, reasons, paths) => {
|
|
118
|
+
writeFileSync(abs(relProbeFile), `${JSON.stringify({ clean, reasons, paths })}
|
|
119
119
|
`);
|
|
120
120
|
writeFileSync(abs(relModeFile), clean ? `fast
|
|
121
121
|
` : `
|
|
122
122
|
`);
|
|
123
|
-
return { clean, reasons
|
|
123
|
+
return { clean, reasons, paths, probeFile: relProbeFile, modeFile: relModeFile, exitCode: 0 };
|
|
124
124
|
};
|
|
125
125
|
let tasks;
|
|
126
126
|
try {
|
|
@@ -180,11 +180,11 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
180
180
|
process.exit(main(process.argv.slice(2)));
|
|
181
181
|
}
|
|
182
182
|
export {
|
|
183
|
-
|
|
184
|
-
runDriftProbe,
|
|
185
|
-
main,
|
|
186
|
-
driftReasonForPath,
|
|
187
|
-
changedPathsOf,
|
|
183
|
+
DOC_OWNED_SURFACES,
|
|
188
184
|
WRAPUP_DRIFT_PROBE_USAGE,
|
|
189
|
-
|
|
185
|
+
changedPathsOf,
|
|
186
|
+
driftReasonForPath,
|
|
187
|
+
main,
|
|
188
|
+
runDriftProbe,
|
|
189
|
+
solutionSectionOf
|
|
190
190
|
};
|
|
@@ -296,7 +296,7 @@ function runMetrics(env, options = {}) {
|
|
|
296
296
|
const parsed = JSON.parse(shown.stdout);
|
|
297
297
|
const frontmatter = parsed.frontmatter !== null && typeof parsed.frontmatter === "object" ? parsed.frontmatter : {};
|
|
298
298
|
const featureId = String(jqPick(frontmatter.feature_id, parsed.feature_id, ""));
|
|
299
|
-
const
|
|
299
|
+
const status = String(jqPick(frontmatter.status, parsed.status, "unknown"));
|
|
300
300
|
const durable = join(".spur", "memory", "evidence", `${wbs}-verdict.json`);
|
|
301
301
|
const hasDurable = existsSync(abs(durable));
|
|
302
302
|
const verdictPath = hasDurable ? durable : join(".spur", "run", `${wbs}-verdict.json`);
|
|
@@ -308,7 +308,7 @@ function runMetrics(env, options = {}) {
|
|
|
308
308
|
`);
|
|
309
309
|
}
|
|
310
310
|
const timestamp = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
311
|
-
const row = { wbs, feature_id: featureId, status
|
|
311
|
+
const row = { wbs, feature_id: featureId, status, verdict, timestamp };
|
|
312
312
|
try {
|
|
313
313
|
appendFileSync(abs(relMetricsFile), `${JSON.stringify(row)}
|
|
314
314
|
`);
|
|
@@ -600,28 +600,28 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
600
600
|
process.exit(main(process.argv.slice(2)));
|
|
601
601
|
}
|
|
602
602
|
export {
|
|
603
|
-
|
|
604
|
-
verdictFromTestingSection,
|
|
605
|
-
taskStatusOf,
|
|
606
|
-
runMetrics,
|
|
607
|
-
runFeatureTransition,
|
|
608
|
-
runDocSyncSnapshot,
|
|
609
|
-
runDocSyncScope,
|
|
610
|
-
runDocSupersessionCheck,
|
|
611
|
-
resolveTasks,
|
|
612
|
-
preflightFeature,
|
|
613
|
-
porcelainPath,
|
|
614
|
-
main,
|
|
615
|
-
jqPick,
|
|
616
|
-
isDocSyncAllowedPath,
|
|
617
|
-
docSyncScopeViolations,
|
|
618
|
-
classifySync,
|
|
619
|
-
WRAPUP_STEPS_USAGE,
|
|
620
|
-
WBS_PATTERN,
|
|
621
|
-
SUPERSESSION_PIN,
|
|
622
|
-
SNAPSHOT_MISSING,
|
|
623
|
-
GIT_UNAVAILABLE_MARKER,
|
|
624
|
-
DOC_SYNC_SUPERSESSION_STATUS_SUFFIX,
|
|
603
|
+
DOC_SYNC_SCOPE_STATUS_SUFFIX,
|
|
625
604
|
DOC_SYNC_SNAPSHOT_SUFFIX,
|
|
626
|
-
|
|
605
|
+
DOC_SYNC_SUPERSESSION_STATUS_SUFFIX,
|
|
606
|
+
GIT_UNAVAILABLE_MARKER,
|
|
607
|
+
SNAPSHOT_MISSING,
|
|
608
|
+
SUPERSESSION_PIN,
|
|
609
|
+
WBS_PATTERN,
|
|
610
|
+
WRAPUP_STEPS_USAGE,
|
|
611
|
+
classifySync,
|
|
612
|
+
docSyncScopeViolations,
|
|
613
|
+
isDocSyncAllowedPath,
|
|
614
|
+
jqPick,
|
|
615
|
+
main,
|
|
616
|
+
porcelainPath,
|
|
617
|
+
preflightFeature,
|
|
618
|
+
resolveTasks,
|
|
619
|
+
runDocSupersessionCheck,
|
|
620
|
+
runDocSyncScope,
|
|
621
|
+
runDocSyncSnapshot,
|
|
622
|
+
runFeatureTransition,
|
|
623
|
+
runMetrics,
|
|
624
|
+
taskStatusOf,
|
|
625
|
+
verdictFromTestingSection,
|
|
626
|
+
writeRouteReason
|
|
627
627
|
};
|
|
@@ -341,7 +341,6 @@ Verdict: PASS (or PARTIAL / FAIL)
|
|
|
341
341
|
|
|
342
342
|
```markdown
|
|
343
343
|
Verdict: PASS
|
|
344
|
-
Confidence: HIGH
|
|
345
344
|
|
|
346
345
|
### Per-Requirement Traceability
|
|
347
346
|
| Req | Status | Evidence |
|
|
@@ -360,7 +359,7 @@ Confidence: HIGH
|
|
|
360
359
|
```
|
|
361
360
|
|
|
362
361
|
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 |`.
|
|
363
|
-
The answer
|
|
362
|
+
The answer carries no `Confidence:` line (F511, task 1207): model-asserted confidence is not verification — a verifier that can write a PASS can write `HIGH`. What the old level compressed now lives in the evidence cells themselves: cite fresh executable evidence and name residual gaps explicitly. Legacy answers that still carry a confidence line parse (the value is ignored); no lint code reads it and the artifact and recorded `## Testing` section carry no confidence value.
|
|
364
363
|
The answer MUST cite a source for external API/library claims in Requirement/AC evidence cells: a cell asserting a scoped-package (`@scope/name`), `node_modules/`, semver-version (`x.y.z`), or backticked function-call (`` `name(args)` ``) claim is rejected with `evidence-citation` unless the SAME cell carries a citation — a repo `path:line(-end)` (backticked or bare), the external named-origin form (@origin `path` line N), or a URL. Cross-cell references ("receipt above") do not count; marker-free receipts ("bun test 398 pass / 0 fail") need no citation.
|
|
365
364
|
**MUST NOT:** place a `Severity` column between `Req` and `Status`. The parser tolerates variants
|
|
366
365
|
as defense-in-depth; this contract is canonical.
|
|
@@ -35,42 +35,21 @@ interface VerifyVerdict {
|
|
|
35
35
|
status: 'pass' | 'fail' | 'warn';
|
|
36
36
|
evidence: string;
|
|
37
37
|
}>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
confidence?: 'HIGH' | 'MEDIUM' | 'LOW';
|
|
38
|
+
// F511 (task 1207): no asserted `confidence` field. Legacy artifacts that still carry one
|
|
39
|
+
// parse with the value ignored — unknown legacy keys are tolerated on read.
|
|
41
40
|
}
|
|
42
41
|
```
|
|
43
42
|
|
|
44
|
-
##
|
|
43
|
+
## No asserted confidence (F511, task 1207)
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
Presence and vocabulary are not enough — the level has to match the rows in the same answer, so the
|
|
56
|
-
lint rejects a level that contradicts its own evidence. Two rejection classes, both addressed to the
|
|
57
|
-
`Confidence:` line and both capped by the shared `ANSWER_LINT_MAX_FINDINGS` budget:
|
|
58
|
-
|
|
59
|
-
- `confidence-unwarranted` — `HIGH` while any requirement row normalizes to `PARTIAL`/`UNMET`, any
|
|
60
|
-
acceptance-criteria row does not normalize to `MET`, or any `MET` row's evidence is hedged.
|
|
61
|
-
A `HIGH` claim says the evidence needs no re-check; a non-MET or hedged row says the opposite.
|
|
62
|
-
- `confidence-understated` — `LOW` while at least one requirement row exists and every requirement
|
|
63
|
-
and acceptance-criteria row normalizes to `MET`. The evidence already did the work, so `LOW`
|
|
64
|
-
misreports the state as unproven.
|
|
65
|
-
|
|
66
|
-
`MEDIUM` carries no coherence rule in either direction: it is the level a verifier uses for a caveat
|
|
67
|
-
with no row to point at, and closing it would force either an overclaimed `HIGH` or an unusable
|
|
68
|
-
`LOW`. The level is normalized to uppercase before the decision, so `high` is judged as `HIGH`.
|
|
69
|
-
|
|
70
|
-
Set the level from the rows, not the mood: all rows `MET` with fresh evidence is `HIGH`; a
|
|
71
|
-
`PARTIAL`/`UNMET` row or a hedge means `MEDIUM`, not `HIGH`. An unwarranted level does not merely
|
|
72
|
-
warn — the lint rejects the answer and no verdict artifact is written, so the level is worth
|
|
73
|
-
choosing deliberately before the verdict line is finalized.
|
|
45
|
+
Verdict artifacts carry no `confidence` field and verify answers no `Confidence:` line. The
|
|
46
|
+
1068/1133 machinery that required, linted and coherence-checked a model-asserted
|
|
47
|
+
`HIGH | MEDIUM | LOW` was retired: a verifier that can write a PASS can write `HIGH`, so the grade
|
|
48
|
+
verifies nothing. What the level compressed belongs in the evidence cells — cite fresh executable
|
|
49
|
+
evidence and name residual gaps explicitly. The done gate no longer denies a confidence-less PASS:
|
|
50
|
+
it checks the proof digests, which are the part a hand-edited artifact cannot fake. The DERIVED
|
|
51
|
+
confidence family is a different thing and stays: feature-verification receipts (ADR-131, 1196),
|
|
52
|
+
the decision subsystem threshold, history-anatomy reading.
|
|
74
53
|
|
|
75
54
|
## Compatibility alias: `scenario` row key
|
|
76
55
|
|
|
@@ -142,7 +142,8 @@ but redundant. See the glossary entry for the disambiguation in full.
|
|
|
142
142
|
|
|
143
143
|
Never: reimplement the pipeline inside the router; default `todo` to `--mode full`; infer targets
|
|
144
144
|
from git/chat; auto-`--merge` on wrap; auto-author rules; use `dev-runall` as the feature default;
|
|
145
|
-
bypass lifecycle guards (`--no-lifecycle`) to force progress
|
|
145
|
+
bypass lifecycle guards (`--no-lifecycle`) to force progress; select a `deprecated` task for any
|
|
146
|
+
lifecycle hop (TABLE A A10 — frozen work has no forward step; un-deprecate or cancel it first).
|
|
146
147
|
|
|
147
148
|
## Common Rationalizations
|
|
148
149
|
|
|
@@ -158,6 +159,7 @@ bypass lifecycle guards (`--no-lifecycle`) to force progress.
|
|
|
158
159
|
- Dispatching more than one primary command in a single invocation.
|
|
159
160
|
- `--auto` used to choose among HITL candidates.
|
|
160
161
|
- A route that is not in TABLE A/B/C (invented dispatch targets).
|
|
162
|
+
- A `deprecated` WBS routed into any lifecycle command instead of stopping (A10/ADR-137).
|
|
161
163
|
- `--once` honored by merely skipping the re-invoke while leaving `--next` in the child argv.
|
|
162
164
|
- Omitting the `dev-next:` prefix on stop/plan messages.
|
|
163
165
|
|
|
@@ -54,7 +54,7 @@ it or when chaining (chain links already use `--auto` per existing refine→run
|
|
|
54
54
|
|
|
55
55
|
## 1. TABLE A — Task primary routes (corpus status)
|
|
56
56
|
|
|
57
|
-
Statuses from `TASK_STATUSES`: backlog | todo | wip | testing | blocked | done | cancelled.
|
|
57
|
+
Statuses from `TASK_STATUSES`: backlog | todo | wip | testing | blocked | done | cancelled | deprecated.
|
|
58
58
|
|
|
59
59
|
| # | Precondition | Primary signal | Dispatch | probe | Chain on success? | Stop / notes |
|
|
60
60
|
| --- | --- | --- | --- | --- | --- | --- |
|
|
@@ -67,8 +67,9 @@ Statuses from `TASK_STATUSES`: backlog | todo | wip | testing | blocked | done |
|
|
|
67
67
|
| A7 | `status == blocked` | `task.show` + Notes/History for blocker text | `/sp:dev-handover "<blocker summary from Notes or status>"` | no | no | **STOP** after handover doc; human unblocks |
|
|
68
68
|
| A8 | `status == done` | `task.show.status` | `/sp:dev-wrap <wbs>` (`--agent <value>` preserved when the originating command supplied it; omission remains omission) | no | **no** (wrap's `--merge` is irreversible HITL; never auto) | Operator may re-invoke with `--merge` explicitly later |
|
|
69
69
|
| A9 | `status == cancelled` | `task.show.status` | *(none)* | no | no | **STOP** — no-op; print "cancelled — nothing to advance" |
|
|
70
|
+
| A10 | `status == deprecated` | `task.show.status` | *(none)* | no | no | **STOP** — frozen and retained (ADR-137); nothing to advance. Print "deprecated — frozen; un-deprecate with `spur task update <wbs> todo`, or cancel it once removed". Never falls through to another row |
|
|
70
71
|
|
|
71
|
-
**Priority when multiple TABLE A rows could match:** lowest row number wins (A1–
|
|
72
|
+
**Priority when multiple TABLE A rows could match:** lowest row number wins (A1–A10 are mutually
|
|
72
73
|
exclusive by status except A2/A3 which split `todo`).
|
|
73
74
|
|
|
74
75
|
## 2. TABLE B — Feature-ID path (rollup)
|
|
@@ -84,7 +85,7 @@ When input is a feature id:
|
|
|
84
85
|
| B4 | No frontier tasks AND `feature.status == backlog` AND AC placeholder/invalid | `feature check` / AC body | `/sp:dev-plan` continuation is not WBS-shaped — **STOP** with: run `/sp:dev-plan --feature <id>` or fix AC then `spur feature check <id>` | no | no | Planning-half entry needs a description; do not invent idea text |
|
|
85
86
|
| B5 | No frontier tasks AND feature has valid AC but zero tasks | feature + empty task roster | **STOP** with: `/sp:dev-plan --feature <id>` (decompose/batch-create) | no | no | Avoid auto-running plan without operator description confirmation in v1 |
|
|
86
87
|
| B6 | No frontier tasks AND all child tasks `done` AND feature in `active`\|`verifying` | task list all done | `/sp:dev-wrapall --feature <id>` (`--agent <value>` preserved when the originating command supplied it; omission remains omission) | no | no | Advances feature via wrapall guards; `--merge` never auto |
|
|
87
|
-
| B7 | No frontier tasks AND mixed cancelled
|
|
88
|
+
| B7 | No frontier tasks AND mixed terminal only (any combination drawn from `cancelled`, `done`, `deprecated`) | statuses | **STOP** — print summary; suggest feature status update manually | no | no | |
|
|
88
89
|
| B8 | `feature.status == blocked` | feature status | **STOP** — print feature blocked; do not pick tasks | no | no | |
|
|
89
90
|
|
|
90
91
|
**Frontier selection algorithm (B3) — precise:**
|
|
@@ -101,6 +102,11 @@ if pick is null → TABLE B rows B4–B7
|
|
|
101
102
|
else → TABLE A(pick)
|
|
102
103
|
```
|
|
103
104
|
|
|
105
|
+
The candidate set is exactly the open statuses: `done`, `cancelled` **and `deprecated`** are never
|
|
106
|
+
frontier candidates (ADR-137 — a frozen task is retained, not resumable), and a `deprecated`
|
|
107
|
+
dependency is not a satisfied one, so it keeps its dependents out of the frontier exactly like any
|
|
108
|
+
unfinished dep.
|
|
109
|
+
|
|
104
110
|
## 3. TABLE C — Light-gate short-circuit (probe=yes rows only)
|
|
105
111
|
|
|
106
112
|
**Order is sequential short-circuit** (first match wins). Never parallel in v1 (simpler, lower
|
|
@@ -126,12 +126,32 @@ mutually exclusive.
|
|
|
126
126
|
spur task update 0040 wip
|
|
127
127
|
```
|
|
128
128
|
|
|
129
|
-
Valid statuses
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
Valid statuses are the canonical `TaskStatus` vocabulary — **`TASK_STATUSES` in
|
|
130
|
+
`packages/domain/src/planning/schema.ts` is the only list**, and the lifecycle engine enforces legal
|
|
131
|
+
transitions against the shipped `task-lifecycle` graph (the engine's bundled workflow, resolved per
|
|
132
|
+
project).
|
|
133
|
+
Operator-facing semantics (the freeze, its guards, the exempt write set) live in the design
|
|
134
|
+
satellite [`task-deprecated-status.md`](../../../../../docs/design/task-deprecated-status.md) — read it
|
|
135
|
+
there rather than restating the vocabulary. Two transitions are **guarded by `check`**:
|
|
136
|
+
`wip→testing` runs `spur task check <wbs> --as testing`, and `testing→done` runs
|
|
137
|
+
`spur task check <wbs> --as done` (F92 R3 — target-aware guards) — a failing
|
|
133
138
|
gate blocks the transition (§7.5).
|
|
134
139
|
|
|
140
|
+
**`deprecated` — frozen but retained (ADR-137).** Non-terminal: the record stays readable and
|
|
141
|
+
countable, but nothing new attaches to it and it never advances. Legal edges (from the shipped
|
|
142
|
+
graph): `backlog`, `todo`, `wip`, `testing`, `blocked` and `done` may move to `deprecated`, and it leaves only to `todo`
|
|
143
|
+
(un-deprecate, mandatory `History` entry) or `cancelled` (removed). Any other status write is
|
|
144
|
+
refused by the graph, and the refusal names the un-deprecate command.
|
|
145
|
+
|
|
146
|
+
Two guards carry the freeze past the graph: a **new** dependency edge onto a `deprecated` task is
|
|
147
|
+
refused (ready-set resolver and pipeline preflight), and a live dependent is reported by the
|
|
148
|
+
`L4.task-deprecated` warning. The freeze is prospective — an edge recorded before the deprecation is
|
|
149
|
+
reported, never rewritten.
|
|
150
|
+
|
|
151
|
+
Writes that stay **permitted** on a `deprecated` task (each edits the record rather than declaring
|
|
152
|
+
the work resumable): `spur task record`, `migrate-anchors` and verdict writes, and
|
|
153
|
+
`spur task update <wbs> --section <any>` — the section write lands and returns a freeze warning.
|
|
154
|
+
|
|
135
155
|
**`--no-lifecycle`** suppresses lifecycle workflow *run record* creation (use during pipeline-driven
|
|
136
156
|
transitions so nested lifecycle runs are not orphaned). **It is not a guard bypass** — the
|
|
137
157
|
`wip→testing` / `testing→done` `check` gates still run, evaluated inline when the FSM guard does not.
|
|
@@ -572,7 +572,14 @@ does not earn another full-gate run:
|
|
|
572
572
|
4. Give the test an explicit per-test timeout and a WHY comment (see
|
|
573
573
|
`plugins/sp/tests/inline-run-close-reason.test.ts:100-102`) as soon as EITHER (a) the same test
|
|
574
574
|
load-flakes in two separate runs, OR (b) it times out once **outside the change scope** and then
|
|
575
|
-
passes green alone on three consecutive isolated reruns.
|
|
575
|
+
passes green alone on three consecutive isolated reruns. **Prefer making the test cheap or
|
|
576
|
+
hermetic first** — a test that hashes the live checkout transitively, or writes a probe file into
|
|
577
|
+
it, should build a `mkdtemp` fixture repo instead of buying a bigger budget
|
|
578
|
+
(`packages/app/tests/workflow/actions/proof-fingerprint.test.ts` is the pattern, 2026-10-13).
|
|
579
|
+
Only then hand the remaining repeat flakers an explicit budget: the suite-wide `--timeout` is
|
|
580
|
+
not the lever, and raising it was measured NOT to help (a 60s suite default still failed 7
|
|
581
|
+
spawn-heavy tests under a host shared with two sibling agent suites, while every one passed
|
|
582
|
+
alone in 0.15-0.3s — 2026-10-13). Waiting for a second gate run to license
|
|
576
583
|
the fix cost 21 minutes on 2026-10-08 (task 1128, the 1058 two-tree canary: 5145 ms then 5967 ms
|
|
577
584
|
against bun's 5000 ms default, green 3/3 alone). Never raise the global timeout, skip the test,
|
|
578
585
|
or loop full-gate reruns.
|