@gobing-ai/spur 0.4.3 → 0.4.4
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/verdict-confidence.yaml +68 -0
- package/config/rules/quality/coverage-gate.yaml +13 -0
- package/config/rules/recommended-pre-check.yaml +6 -2
- 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 +15 -9
- package/package.json +10 -10
- package/plugins/sp/agents/super-reviewer.md +5 -2
- 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.d.mts +13 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +51 -21
- 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 +58 -27
- package/plugins/sp/scripts/residual-scan.ts +8 -8
- 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/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/flag-glossary.md +4 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +22 -2
- package/schemas/section-matrix.schema.json +3 -2
- package/spur.js +24403 -32459
- 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
|
@@ -229,10 +229,35 @@ function reviewContractViolations(taskContent) {
|
|
|
229
229
|
}
|
|
230
230
|
return violations;
|
|
231
231
|
}
|
|
232
|
-
function
|
|
232
|
+
function reviewConfidenceViolation(reviewAnswer, ackLowConfidence) {
|
|
233
|
+
if (ackLowConfidence === "true")
|
|
234
|
+
return null;
|
|
235
|
+
const level = extractAnswerConfidence(reviewAnswer.replace(/\*\*/g, ""));
|
|
236
|
+
if (level === "HIGH" || level === "MEDIUM")
|
|
237
|
+
return null;
|
|
238
|
+
return {
|
|
239
|
+
location: "review-answer:Confidence",
|
|
240
|
+
text: level === undefined ? "The review answer carries no valid `**Confidence:** HIGH | MEDIUM | LOW` line — a " + "PASS must be graded, or the operator must set ackLowConfidence" : `The review answer graded its PASS ${level} — HIGH/MEDIUM cross unadorned, ` + "LOW/absent needs the operator’s ackLowConfidence"
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function reviewGradingInput(root, reviewAnswer, ackLowConfidence, fs) {
|
|
244
|
+
if (reviewAnswer === undefined)
|
|
245
|
+
return;
|
|
246
|
+
const path = join(root, reviewAnswer);
|
|
247
|
+
let isFile = false;
|
|
248
|
+
try {
|
|
249
|
+
isFile = fs.statSync(path).isFile();
|
|
250
|
+
} catch {
|
|
251
|
+
isFile = false;
|
|
252
|
+
}
|
|
253
|
+
return { answer: isFile ? fs.readFileSync(path, "utf8") : "", ackLowConfidence };
|
|
254
|
+
}
|
|
255
|
+
function buildReviewGateArtifact(wbs, taskContent, deferrals, review) {
|
|
256
|
+
const confidence = review === undefined ? null : reviewConfidenceViolation(review.answer, review.ackLowConfidence);
|
|
257
|
+
const contractViolations = [...reviewContractViolations(taskContent), ...confidence === null ? [] : [confidence]];
|
|
233
258
|
const items = [
|
|
234
259
|
...blockingReviewFindings(taskContent, deferrals),
|
|
235
|
-
...
|
|
260
|
+
...contractViolations.map((v) => ({
|
|
236
261
|
id: makeItemId("review-contract", v.location, v.text),
|
|
237
262
|
category: "review-contract",
|
|
238
263
|
class: "blocking",
|
|
@@ -365,6 +390,7 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
365
390
|
let spurBin;
|
|
366
391
|
let root = cwd;
|
|
367
392
|
let tmpDir = defaultTmpDir;
|
|
393
|
+
let reviewAnswer;
|
|
368
394
|
for (let i = 0;i < argv.length; i++) {
|
|
369
395
|
const a = argv[i];
|
|
370
396
|
if (a === undefined)
|
|
@@ -375,6 +401,8 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
375
401
|
root = argv[++i] ?? root;
|
|
376
402
|
else if (a === "--tmp-dir")
|
|
377
403
|
tmpDir = argv[++i] ?? tmpDir;
|
|
404
|
+
else if (a === "--review-answer")
|
|
405
|
+
reviewAnswer = argv[++i];
|
|
378
406
|
else if (a === "--help" || a === "-h")
|
|
379
407
|
return null;
|
|
380
408
|
else if (mode === "")
|
|
@@ -382,7 +410,7 @@ function parseScanArgs(argv, cwd, defaultTmpDir) {
|
|
|
382
410
|
else if (wbs === "")
|
|
383
411
|
wbs = a;
|
|
384
412
|
}
|
|
385
|
-
return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir };
|
|
413
|
+
return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir, reviewAnswer };
|
|
386
414
|
}
|
|
387
415
|
|
|
388
416
|
// plugins/sp/lib/spur-bin.ts
|
|
@@ -392,7 +420,7 @@ function spurCommand(spurBin) {
|
|
|
392
420
|
}
|
|
393
421
|
|
|
394
422
|
// plugins/sp/scripts/residual-scan.ts
|
|
395
|
-
var RESIDUAL_SCAN_USAGE = "usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>]";
|
|
423
|
+
var RESIDUAL_SCAN_USAGE = "usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>] [--review-answer <path>]";
|
|
396
424
|
function run(cmd, args, cwd) {
|
|
397
425
|
const result = spawnSync(cmd, args, { cwd, encoding: "utf8" });
|
|
398
426
|
if (result.error !== undefined)
|
|
@@ -487,7 +515,8 @@ function reviewGateMode(opts, env, io) {
|
|
|
487
515
|
const runDir = join2(opts.root, ".spur", "run");
|
|
488
516
|
fs.mkdirSync(runDir, { recursive: true });
|
|
489
517
|
const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
|
|
490
|
-
const
|
|
518
|
+
const grading = reviewGradingInput(opts.root, opts.reviewAnswer, env.ackLowConfidence, fs);
|
|
519
|
+
const gate = buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs), grading);
|
|
491
520
|
fs.writeFileSync(join2(runDir, `${opts.wbs}-residuals.json`), `${JSON.stringify(gate.artifact, null, 2)}
|
|
492
521
|
`);
|
|
493
522
|
io.out(gate.note);
|
|
@@ -622,27 +651,29 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
622
651
|
}
|
|
623
652
|
process.exit(main(process.argv.slice(2)));
|
|
624
653
|
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,
|
|
654
|
+
ALLOW_PRAGMA,
|
|
646
655
|
RESIDUAL_SCAN_USAGE,
|
|
647
|
-
|
|
656
|
+
blockingAnchors,
|
|
657
|
+
blockingReviewFindings,
|
|
658
|
+
buildReviewGateArtifact,
|
|
659
|
+
classify,
|
|
660
|
+
collectAddedLines,
|
|
661
|
+
findUncheckedBoxes,
|
|
662
|
+
foldVerdict,
|
|
663
|
+
listStagingResidue,
|
|
664
|
+
locationOf,
|
|
665
|
+
main,
|
|
666
|
+
makeItemId,
|
|
667
|
+
normalizeAnchor,
|
|
668
|
+
parseDeferralEntries,
|
|
669
|
+
parseDiffMarkers,
|
|
670
|
+
parseReviewFindings,
|
|
671
|
+
parseScanArgs,
|
|
672
|
+
recordedVerdictPath,
|
|
673
|
+
renderReport,
|
|
674
|
+
reviewConfidenceViolation,
|
|
675
|
+
reviewContractViolations,
|
|
676
|
+
reviewGradingInput,
|
|
677
|
+
scanResiduals2 as scanResiduals,
|
|
678
|
+
verdictDisagreementNote
|
|
648
679
|
};
|
|
@@ -10,7 +10,7 @@ import { spurCommand } from '../lib/spur-bin';
|
|
|
10
10
|
|
|
11
11
|
export * from '../lib/residual-scan.generated.mjs';
|
|
12
12
|
export const RESIDUAL_SCAN_USAGE =
|
|
13
|
-
'usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>]';
|
|
13
|
+
'usage: residual-scan.ts <scan|fold|settle|report|review-gate> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>] [--review-answer <path>]';
|
|
14
14
|
export type ScanEnv = { spurBin?: string } & Record<string, string | undefined>;
|
|
15
15
|
export type ScanIo = { out: (line: string) => void; err: (line: string) => void };
|
|
16
16
|
export type ScanOptions = { cwd?: string; io?: ScanIo };
|
|
@@ -104,18 +104,18 @@ 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
|
-
*
|
|
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, or on a review answer graded LOW or left
|
|
110
|
+
* ungraded without `env.ackLowConfidence` (task 1204, graded via `core.reviewGradingInput` — it
|
|
111
|
+
* cannot sit inline in the edge because ADR-115 caps a guard at 5 logical commands).
|
|
113
112
|
*/
|
|
114
113
|
function reviewGateMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
115
114
|
const runDir = join(opts.root, '.spur', 'run');
|
|
116
115
|
fs.mkdirSync(runDir, { recursive: true });
|
|
117
116
|
const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
|
|
118
|
-
const
|
|
117
|
+
const grading = core.reviewGradingInput(opts.root, opts.reviewAnswer, env.ackLowConfidence, fs);
|
|
118
|
+
const gate = core.buildReviewGateArtifact(opts.wbs, content, readDeferrals(runDir, opts.wbs), grading);
|
|
119
119
|
fs.writeFileSync(join(runDir, `${opts.wbs}-residuals.json`), `${JSON.stringify(gate.artifact, null, 2)}\n`);
|
|
120
120
|
io.out(gate.note);
|
|
121
121
|
return gate.artifact.counts.blocking > 0 ? 1 : 0;
|
|
@@ -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
|
};
|
|
@@ -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.
|