@gobing-ai/spur 0.3.92 → 0.3.94

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.
Files changed (119) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -7
  3. package/config/plugin-scripts.json +10 -0
  4. package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
  5. package/config/rules/strict/runtime-boundaries.yaml +2 -1
  6. package/config/rules/structure/protected-files.yaml +4 -0
  7. package/config/templates/AGENTS.md +4 -0
  8. package/config/templates/docs/02_ROADMAP.md +2 -0
  9. package/config/templates/docs/03_ARCHITECTURE.md +3 -1
  10. package/config/templates/docs/04_DESIGN.md +2 -0
  11. package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
  12. package/config/workflow-candidates.json +72 -1
  13. package/config/workflows/feature-verification.yaml +1 -0
  14. package/config/workflows/history-anatomy.yaml +2 -0
  15. package/config/workflows/idea-pipeline.yaml +67 -18
  16. package/config/workflows/pr-review.yaml +8 -0
  17. package/config/workflows/task-pipeline.yaml +171 -39
  18. package/config/workflows/wayfinder-resolution.yaml +5 -0
  19. package/config/workflows/wrapup-pipeline.yaml +55 -14
  20. package/package.json +9 -9
  21. package/plugins/sp/README.md +8 -3
  22. package/plugins/sp/commands/dev-review-session.md +4 -4
  23. package/plugins/sp/commands/dev-review.md +16 -5
  24. package/plugins/sp/commands/dev-run.md +2 -2
  25. package/plugins/sp/commands/dev-runall.md +2 -2
  26. package/plugins/sp/hooks/pi/guard-extension.ts +17 -36
  27. package/plugins/sp/lib/idea-handoff.generated.mjs +8 -4
  28. package/plugins/sp/lib/inline-run.generated.d.mts +3 -0
  29. package/plugins/sp/lib/inline-run.generated.mjs +48 -19
  30. package/plugins/sp/plugin.json +1 -1
  31. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
  32. package/plugins/sp/scripts/inline-run-setup.mjs +117 -7
  33. package/plugins/sp/scripts/inline-run-setup.ts +211 -7
  34. package/plugins/sp/scripts/quality-gate.mjs +255 -6
  35. package/plugins/sp/scripts/quality-gate.ts +425 -9
  36. package/plugins/sp/scripts/residual-scan.mjs +13 -5
  37. package/plugins/sp/scripts/residual-scan.ts +34 -7
  38. package/plugins/sp/scripts/task-diffstat.mjs +156 -0
  39. package/plugins/sp/scripts/task-diffstat.ts +229 -0
  40. package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
  41. package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
  42. package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
  43. package/plugins/sp/scripts/wrapup-steps.ts +89 -4
  44. package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
  45. package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
  46. package/plugins/sp/skills/code-verification/SKILL.md +2 -2
  47. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
  48. package/plugins/sp/skills/session-review/SKILL.md +11 -9
  49. package/plugins/sp/skills/spur-check/SKILL.md +112 -0
  50. package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
  51. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -2
  52. package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
  53. package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
  54. package/plugins/sp/skills/spur-dev/references/execution-batch.md +46 -18
  55. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +24 -3
  56. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
  57. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +34 -2
  58. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -5
  59. package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
  60. package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
  61. package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
  62. package/schemas/state-machine-workflow.schema.json +4 -0
  63. package/spur.js +19577 -19822
  64. package/web/_astro/BoardApp.C02hAHPO.js +1 -0
  65. package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.FTEs3-N8.js} +107 -105
  66. package/web/_astro/{TaskDetail.DCqiC-OZ.js → TaskDetail.C-GdsS-t.js} +1 -1
  67. package/web/_astro/arc.uuAf51IT.js +1 -0
  68. package/web/_astro/{architectureDiagram-3BPJPVTR.DM_vp_hO.js → architectureDiagram-3BPJPVTR.CGe629A1.js} +1 -1
  69. package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.D1mGCq3p.js} +1 -1
  70. package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.CMsolcde.js} +1 -1
  71. package/web/_astro/channel.fsgl7o5j.js +1 -0
  72. package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.CrGA3fik.js} +1 -1
  73. package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DsLVVla0.js} +1 -1
  74. package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.Dxc59Tfi.js} +1 -1
  75. package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.CaEVE2Wy.js} +4 -4
  76. package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.BiJ4HXeI.js} +1 -1
  77. package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.Mxf1fru5.js} +1 -1
  78. package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM.pyOWQixH.js} +1 -1
  79. package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.BmEsg4vr.js} +1 -1
  80. package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.BHhhMFTO.js} +1 -1
  81. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.BHhhMFTO.js} +1 -1
  82. package/web/_astro/{cose-bilkent-S5V4N54A.B1wSL-Xb.js → cose-bilkent-S5V4N54A.2fH4YOlp.js} +1 -1
  83. package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.C2j1_lKL.js} +1 -1
  84. package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.hZ2NCTdT.js} +2 -2
  85. package/web/_astro/diagram-2AECGRRQ.Bfw5_EzK.js +43 -0
  86. package/web/_astro/diagram-5GNKFQAL.Bt1V_Tmk.js +10 -0
  87. package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.DEk-YFwp.js} +3 -3
  88. package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.CDjndtQm.js} +1 -1
  89. package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.cFnUHScG.js} +1 -1
  90. package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.DlhYp7NV.js} +5 -5
  91. package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DNTpsxfx.js} +4 -4
  92. package/web/_astro/{ganttDiagram-6RSMTGT7.BO81S85v.js → ganttDiagram-6RSMTGT7.DC_p36PI.js} +1 -1
  93. package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.DkEqNI0P.js} +1 -1
  94. package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.CrjioCTG.js} +1 -1
  95. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.BcK0CN8n.js} +5 -5
  96. package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.p0CbBDX1.js} +1 -1
  97. package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.puHIFt6J.js} +7 -7
  98. package/web/_astro/{linear.C2_IkbZT.js → linear.Bb-3a1d7.js} +1 -1
  99. package/web/_astro/mermaid.core.CJDgXOJs.js +301 -0
  100. package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BoAZEI9t.js} +2 -2
  101. package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CJNSdqY7.js} +3 -3
  102. package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.DtTy7_0Z.js} +1 -1
  103. package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.BNYV98Xg.js} +3 -3
  104. package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DRq9DGHp.js} +4 -4
  105. package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B7KdBFCm.js} +1 -1
  106. package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CD62ZJ2H.js} +1 -1
  107. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.CeVk1TAj.js} +1 -1
  108. package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.C_ZPA5tT.js} +3 -3
  109. package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.CrThO_FQ.js} +5 -5
  110. package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.DSZCA5nl.js} +3 -3
  111. package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.KI7baTuk.js} +1 -1
  112. package/web/index.html +1 -1
  113. package/config/workflows/decision-routing-example.yaml +0 -134
  114. package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
  115. package/web/_astro/arc.CPwg6Rw0.js +0 -1
  116. package/web/_astro/channel.MYZLKNwy.js +0 -1
  117. package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
  118. package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
  119. package/web/_astro/mermaid.core.GAOYeSR0.js +0 -303
@@ -86,6 +86,196 @@ function scanCoverageShortfalls(logText, threshold) {
86
86
  }
87
87
  return [...shortfalls.values()];
88
88
  }
89
+ var RECEIPT_SCHEMA_VERSION = "check-receipt/v1";
90
+ function buildReceipt(input) {
91
+ return {
92
+ schemaVersion: RECEIPT_SCHEMA_VERSION,
93
+ wbs: input.wbs,
94
+ runId: input.runId,
95
+ tier: input.tier,
96
+ inputDigest: input.inputDigest,
97
+ checks: input.checks,
98
+ status: input.checks.every((row) => row.status === "PASS") ? "PASS" : "FAIL",
99
+ completedAt: input.completedAt
100
+ };
101
+ }
102
+ function readReceipt(receiptPath) {
103
+ try {
104
+ return JSON.parse(readFileSync(receiptPath, "utf8"));
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+ function receiptFailsAtDigest(receipt, currentDigest) {
110
+ return receipt !== null && receipt.schemaVersion === RECEIPT_SCHEMA_VERSION && receipt.tier === "full" && receipt.status === "FAIL" && currentDigest.length > 0 && receipt.inputDigest === currentDigest;
111
+ }
112
+ function readReceiptStatus(receiptPath, currentDigest) {
113
+ const receipt = readReceipt(receiptPath);
114
+ if (receipt === null || receipt.schemaVersion !== RECEIPT_SCHEMA_VERSION) {
115
+ return { reuse: false, reason: "missing" };
116
+ }
117
+ if (receipt.status !== "PASS")
118
+ return { reuse: false, reason: "failed" };
119
+ if (currentDigest.length === 0 || receipt.inputDigest !== currentDigest) {
120
+ return { reuse: false, reason: "stale" };
121
+ }
122
+ if (receipt.tier !== "full")
123
+ return { reuse: false, reason: "light-only" };
124
+ return { reuse: true, reason: "ok" };
125
+ }
126
+ var TEST_FILE_PATTERN = /\.test\.tsx?$/;
127
+ function workspaceOf(file, exists) {
128
+ const segments = file.split("/");
129
+ for (let depth = 1;depth < segments.length; depth++) {
130
+ const prefix = segments.slice(0, depth).join("/");
131
+ if (exists(`${prefix}/package.json`))
132
+ return prefix;
133
+ }
134
+ return null;
135
+ }
136
+ function lightScope(changedFiles, exists = existsSync) {
137
+ const files = [];
138
+ const workspaces = new Set;
139
+ const tests = new Set;
140
+ for (const file of changedFiles) {
141
+ files.push(file);
142
+ const workspace = workspaceOf(file, exists);
143
+ if (workspace === null)
144
+ continue;
145
+ workspaces.add(workspace);
146
+ const rest = file.slice(workspace.length + 1);
147
+ if (rest.startsWith("src/")) {
148
+ const candidate = `${workspace}/tests/${rest.slice("src/".length).replace(/\.tsx?$/, (ext) => `.test${ext}`)}`;
149
+ if (exists(candidate))
150
+ tests.add(candidate);
151
+ } else if (rest.startsWith("tests/") && TEST_FILE_PATTERN.test(rest)) {
152
+ tests.add(file);
153
+ }
154
+ }
155
+ return { files, workspaces: [...workspaces].sort(), tests: [...tests].sort() };
156
+ }
157
+ function shQuote(arg) {
158
+ return /^[\w./@+-]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
159
+ }
160
+ function workspaceHasTypecheck(workspace) {
161
+ try {
162
+ const pkg = JSON.parse(readFileSync(join(workspace, "package.json"), "utf8"));
163
+ return typeof pkg.scripts?.typecheck === "string";
164
+ } catch {
165
+ return false;
166
+ }
167
+ }
168
+ function planLightChecks(scope, hasTypecheck = workspaceHasTypecheck) {
169
+ const plans = [];
170
+ if (scope.files.length > 0) {
171
+ plans.push({ id: "format-lint:changed", cmd: `bunx biome check ${scope.files.map(shQuote).join(" ")}` });
172
+ }
173
+ for (const workspace of scope.workspaces) {
174
+ if (hasTypecheck(workspace)) {
175
+ plans.push({ id: `typecheck:${workspace}`, cmd: `cd ${shQuote(workspace)} && bun run typecheck` });
176
+ }
177
+ }
178
+ const testsByWorkspace = new Map;
179
+ for (const test of scope.tests) {
180
+ const workspace = test.slice(0, test.indexOf("/tests/"));
181
+ const paths = testsByWorkspace.get(workspace) ?? [];
182
+ paths.push(test.slice(workspace.length + 1));
183
+ testsByWorkspace.set(workspace, paths);
184
+ }
185
+ for (const [workspace, paths] of testsByWorkspace) {
186
+ plans.push({
187
+ id: `test:${workspace}`,
188
+ cmd: `cd ${shQuote(workspace)} && bun test ${paths.map(shQuote).join(" ")}`
189
+ });
190
+ }
191
+ return plans;
192
+ }
193
+ function gitChangedFiles(cwd) {
194
+ const abs = (p) => cwd ? join(cwd, p) : p;
195
+ const changed = new Set;
196
+ for (const cmd of ["git diff --name-only HEAD", "git ls-files --others --exclude-standard"]) {
197
+ const result = runShellCommand(cmd, cwd);
198
+ for (const line of result.output.split(`
199
+ `)) {
200
+ const file = line.trim();
201
+ if (file.length > 0 && existsSync(abs(file)))
202
+ changed.add(file);
203
+ }
204
+ }
205
+ return [...changed].sort();
206
+ }
207
+ function receiptRunId(env) {
208
+ return (env.runId ?? "").length > 0 ? env.runId : `pipeline-${env.wbs}`;
209
+ }
210
+ function runLightGate(env, options = {}) {
211
+ const cwd = options.cwd;
212
+ const abs = (p) => cwd ? join(cwd, p) : p;
213
+ const runDir = join(".spur", "run");
214
+ mkdirSync(abs(runDir), { recursive: true });
215
+ const logFile = join(runDir, `${env.wbs}-light-gate.log`);
216
+ const receiptFile = join(runDir, `${env.wbs}-check-receipt.json`);
217
+ writeFileSync(abs(logFile), "");
218
+ const digest = env.proofDigest ?? "";
219
+ const scope = lightScope(gitChangedFiles(cwd), (p) => existsSync(abs(p)));
220
+ const plans = planLightChecks(scope, (workspace) => workspaceHasTypecheck(abs(workspace)));
221
+ const reusable = new Map;
222
+ let preserveFullReceipt = false;
223
+ if (digest.length > 0) {
224
+ try {
225
+ const prior = JSON.parse(readFileSync(abs(receiptFile), "utf8"));
226
+ if (prior?.schemaVersion === RECEIPT_SCHEMA_VERSION && prior.inputDigest === digest) {
227
+ if (prior.tier === "full") {
228
+ preserveFullReceipt = true;
229
+ } else {
230
+ for (const row of prior.checks ?? [])
231
+ if (row.status === "PASS")
232
+ reusable.set(row.id, row);
233
+ }
234
+ }
235
+ } catch {}
236
+ }
237
+ const checks = [];
238
+ let skipped = 0;
239
+ for (const plan of plans) {
240
+ const priorRow = reusable.get(plan.id);
241
+ if (priorRow !== undefined) {
242
+ checks.push(priorRow);
243
+ skipped++;
244
+ const line = `--- light ${plan.id}: check.reused \u2014 skipped (PASS at the same input digest)
245
+ `;
246
+ process.stdout.write(line);
247
+ appendFileSync(abs(logFile), line);
248
+ continue;
249
+ }
250
+ const startedAtMs = Date.now();
251
+ const result = runShellCommand(plan.cmd, cwd);
252
+ const durationMs = Date.now() - startedAtMs;
253
+ const status = result.code === 0 ? "PASS" : "FAIL";
254
+ appendFileSync(abs(logFile), `--- light ${plan.id}: ${status} (${durationMs}ms)
255
+ ${result.output}`);
256
+ checks.push({ id: plan.id, cmd: plan.cmd, status, durationMs, logPath: logFile });
257
+ }
258
+ const receipt = buildReceipt({
259
+ wbs: env.wbs,
260
+ runId: receiptRunId(env),
261
+ tier: "light",
262
+ inputDigest: digest,
263
+ checks,
264
+ completedAt: new Date().toISOString()
265
+ });
266
+ if (preserveFullReceipt) {
267
+ appendFileSync(abs(logFile), `--- light receipt: not written \u2014 the full-tier receipt at the same input digest is preserved
268
+ `);
269
+ process.stdout.write(`light gate ${receipt.status} (${scope.files.length} changed files; checks: ${checks.length},` + ` skipped: ${skipped}; receipt: ${receiptFile} preserved (full tier); log: ${logFile})
270
+ `);
271
+ } else {
272
+ writeFileSync(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
273
+ `);
274
+ process.stdout.write(`light gate ${receipt.status} (${scope.files.length} changed files; checks: ${checks.length},` + ` skipped: ${skipped}; receipt: ${receiptFile}; log: ${logFile})
275
+ `);
276
+ }
277
+ return { status: receipt.status, scope, checks, logFile, receiptFile };
278
+ }
89
279
  function retryDelayMs(env) {
90
280
  const raw = Number.parseInt(env[RETRY_DELAY_MS_ENV] ?? "", 10);
91
281
  return Number.isFinite(raw) && raw >= 0 ? raw : RETRY_DELAY_MS_DEFAULT;
@@ -124,9 +314,18 @@ function runQualityGate(mode, env, options = {}) {
124
314
  if (mode === "run")
125
315
  writeFileSync(abs(attemptFile), `0
126
316
  `);
317
+ const gateStartedAtMs = Date.now();
127
318
  let gateRc = 0;
128
319
  let gateAttempt = 0;
129
- if (mode === "recheck" && (env.gateProbeCmd ?? "").length > 0) {
320
+ const noProgressSkip = mode === "recheck" && receiptFailsAtDigest(readReceipt(abs(rel("-check-receipt.json"))), env.proofDigest ?? "");
321
+ if (noProgressSkip) {
322
+ const line = `check.skipped-no-progress \u2014 full-tier FAIL receipt at input digest ${env.proofDigest ?? ""}; recheck skipped
323
+ `;
324
+ process.stdout.write(line);
325
+ appendFileSync(abs(logFile), line);
326
+ gateRc = 1;
327
+ }
328
+ if (mode === "recheck" && !noProgressSkip && (env.gateProbeCmd ?? "").length > 0) {
130
329
  const probe = runShellCommand(env.gateProbeCmd ?? "", cwd);
131
330
  writeFileSync(abs(`${logFile}.probe`), probe.output);
132
331
  gateRc = probe.code;
@@ -182,12 +381,38 @@ function runQualityGate(mode, env, options = {}) {
182
381
  `);
183
382
  appendFileSync(abs(logFile), `proof-digest: ${env.proofDigest ?? ""}
184
383
  `);
185
- return { status, attempts: gateAttempt, logFile, findingsFile, statusFile, attemptFile };
384
+ let receiptFile;
385
+ if (!noProgressSkip) {
386
+ if ((env.proofDigest ?? "").length > 0) {
387
+ receiptFile = join(runDir, `${env.wbs}-check-receipt.json`);
388
+ const receipt = buildReceipt({
389
+ wbs: env.wbs,
390
+ runId: receiptRunId(env),
391
+ tier: "full",
392
+ inputDigest: env.proofDigest ?? "",
393
+ checks: [
394
+ {
395
+ id: "test",
396
+ cmd: env.qualityGateCmd ?? "",
397
+ status,
398
+ durationMs: Date.now() - gateStartedAtMs,
399
+ logPath: logFile
400
+ }
401
+ ],
402
+ completedAt: new Date().toISOString()
403
+ });
404
+ writeFileSync(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
405
+ `);
406
+ } else {
407
+ appendFileSync(abs(logFile), "check-receipt: not written \u2014 env `proofDigest` is not set\n");
408
+ }
409
+ }
410
+ return { status, attempts: gateAttempt, logFile, findingsFile, statusFile, attemptFile, receiptFile };
186
411
  }
187
- var QUALITY_GATE_USAGE = "usage: quality-gate.ts <run|recheck> (env: wbs, qualityGateCmd, gateProbeCmd, proofDigest)";
188
- function main(argv, env = getEnvVars()) {
412
+ var QUALITY_GATE_USAGE = "usage: quality-gate.ts <run|recheck|light|status> (env: wbs, qualityGateCmd, gateProbeCmd, proofDigest, runId)";
413
+ function main(argv, env = getEnvVars(), options = {}) {
189
414
  const mode = argv[0];
190
- if (mode !== "run" && mode !== "recheck") {
415
+ if (mode !== "run" && mode !== "recheck" && mode !== "light" && mode !== "status") {
191
416
  process.stderr.write(`${QUALITY_GATE_USAGE}
192
417
  `);
193
418
  return 2;
@@ -196,24 +421,48 @@ function main(argv, env = getEnvVars()) {
196
421
  process.stderr.write("quality-gate: env `wbs` is required\n");
197
422
  return 2;
198
423
  }
199
- runQualityGate(mode, env);
424
+ if (mode === "light") {
425
+ runLightGate(env);
426
+ } else if (mode === "status") {
427
+ const runDir = join(options.cwd ?? ".", ".spur", "run");
428
+ const verdict = readReceiptStatus(join(runDir, `${env.wbs}-check-receipt.json`), env.proofDigest ?? "");
429
+ if (verdict.reuse) {
430
+ const line = `check.reused \u2014 full-tier receipt reused for input digest ${env.proofDigest ?? ""}
431
+ `;
432
+ process.stdout.write(line);
433
+ appendFileSync(join(runDir, `${env.wbs}-test-gate.log`), line);
434
+ }
435
+ process.stdout.write(`${JSON.stringify(verdict)}
436
+ `);
437
+ } else {
438
+ runQualityGate(mode, env);
439
+ }
200
440
  return 0;
201
441
  }
202
442
  {
203
443
  process.exit(main(process.argv.slice(2)));
204
444
  }
205
445
  export {
446
+ workspaceHasTypecheck,
206
447
  tailLines,
448
+ shQuote,
207
449
  scanCoverageShortfalls,
208
450
  runShellCommand,
209
451
  runQualityGate,
452
+ runLightGate,
210
453
  retryMessage,
454
+ receiptFailsAtDigest,
455
+ readReceiptStatus,
456
+ planLightChecks,
211
457
  parseCoverageThreshold,
212
458
  main,
459
+ lightScope,
213
460
  isTransientLock,
214
461
  extractFindings,
462
+ buildReceipt,
215
463
  RETRY_DELAY_MS_ENV,
216
464
  RETRY_DELAY_MS_DEFAULT,
465
+ RECEIPT_SCHEMA_VERSION,
217
466
  QUALITY_GATE_USAGE,
218
467
  MAX_GATE_ATTEMPTS,
219
468
  MAX_FINDINGS,