@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +0 -7
- package/config/plugin-scripts.json +10 -0
- package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
- package/config/rules/strict/runtime-boundaries.yaml +2 -1
- package/config/rules/structure/protected-files.yaml +4 -0
- package/config/templates/AGENTS.md +4 -0
- package/config/templates/docs/02_ROADMAP.md +2 -0
- package/config/templates/docs/03_ARCHITECTURE.md +3 -1
- package/config/templates/docs/04_DESIGN.md +2 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
- package/config/workflow-candidates.json +72 -1
- package/config/workflows/feature-verification.yaml +1 -0
- package/config/workflows/history-anatomy.yaml +2 -0
- package/config/workflows/idea-pipeline.yaml +67 -18
- package/config/workflows/pr-review.yaml +8 -0
- package/config/workflows/task-pipeline.yaml +171 -39
- package/config/workflows/wayfinder-resolution.yaml +5 -0
- package/config/workflows/wrapup-pipeline.yaml +55 -14
- package/package.json +9 -9
- package/plugins/sp/README.md +8 -3
- package/plugins/sp/commands/dev-review-session.md +4 -4
- package/plugins/sp/commands/dev-review.md +16 -5
- package/plugins/sp/commands/dev-run.md +2 -2
- package/plugins/sp/commands/dev-runall.md +2 -2
- package/plugins/sp/hooks/pi/guard-extension.ts +17 -36
- package/plugins/sp/lib/idea-handoff.generated.mjs +8 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -0
- package/plugins/sp/lib/inline-run.generated.mjs +48 -19
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.mjs +117 -7
- package/plugins/sp/scripts/inline-run-setup.ts +211 -7
- package/plugins/sp/scripts/quality-gate.mjs +255 -6
- package/plugins/sp/scripts/quality-gate.ts +425 -9
- package/plugins/sp/scripts/residual-scan.mjs +13 -5
- package/plugins/sp/scripts/residual-scan.ts +34 -7
- package/plugins/sp/scripts/task-diffstat.mjs +156 -0
- package/plugins/sp/scripts/task-diffstat.ts +229 -0
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
- package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
- package/plugins/sp/scripts/wrapup-steps.ts +89 -4
- package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
- package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
- package/plugins/sp/skills/code-verification/SKILL.md +2 -2
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
- package/plugins/sp/skills/session-review/SKILL.md +11 -9
- package/plugins/sp/skills/spur-check/SKILL.md +112 -0
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -2
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
- package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +46 -18
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +24 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +34 -2
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -5
- package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
- package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
- package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
- package/schemas/state-machine-workflow.schema.json +4 -0
- package/spur.js +19577 -19822
- package/web/_astro/BoardApp.C02hAHPO.js +1 -0
- package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.FTEs3-N8.js} +107 -105
- package/web/_astro/{TaskDetail.DCqiC-OZ.js → TaskDetail.C-GdsS-t.js} +1 -1
- package/web/_astro/arc.uuAf51IT.js +1 -0
- package/web/_astro/{architectureDiagram-3BPJPVTR.DM_vp_hO.js → architectureDiagram-3BPJPVTR.CGe629A1.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.D1mGCq3p.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.CMsolcde.js} +1 -1
- package/web/_astro/channel.fsgl7o5j.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.CrGA3fik.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DsLVVla0.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.Dxc59Tfi.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.CaEVE2Wy.js} +4 -4
- package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.BiJ4HXeI.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.Mxf1fru5.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM.pyOWQixH.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.BmEsg4vr.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.BHhhMFTO.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.BHhhMFTO.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.B1wSL-Xb.js → cose-bilkent-S5V4N54A.2fH4YOlp.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.C2j1_lKL.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.hZ2NCTdT.js} +2 -2
- package/web/_astro/diagram-2AECGRRQ.Bfw5_EzK.js +43 -0
- package/web/_astro/diagram-5GNKFQAL.Bt1V_Tmk.js +10 -0
- package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.DEk-YFwp.js} +3 -3
- package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.CDjndtQm.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.cFnUHScG.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.DlhYp7NV.js} +5 -5
- package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DNTpsxfx.js} +4 -4
- package/web/_astro/{ganttDiagram-6RSMTGT7.BO81S85v.js → ganttDiagram-6RSMTGT7.DC_p36PI.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.DkEqNI0P.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.CrjioCTG.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.BcK0CN8n.js} +5 -5
- package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.p0CbBDX1.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.puHIFt6J.js} +7 -7
- package/web/_astro/{linear.C2_IkbZT.js → linear.Bb-3a1d7.js} +1 -1
- package/web/_astro/mermaid.core.CJDgXOJs.js +301 -0
- package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BoAZEI9t.js} +2 -2
- package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CJNSdqY7.js} +3 -3
- package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.DtTy7_0Z.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.BNYV98Xg.js} +3 -3
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DRq9DGHp.js} +4 -4
- package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B7KdBFCm.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CD62ZJ2H.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.CeVk1TAj.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.C_ZPA5tT.js} +3 -3
- package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.CrThO_FQ.js} +5 -5
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.DSZCA5nl.js} +3 -3
- package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.KI7baTuk.js} +1 -1
- package/web/index.html +1 -1
- package/config/workflows/decision-routing-example.yaml +0 -134
- package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
- package/web/_astro/arc.CPwg6Rw0.js +0 -1
- package/web/_astro/channel.MYZLKNwy.js +0 -1
- package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
- package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|