@gobing-ai/spur 0.3.93 → 0.3.95
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/README.md +42 -0
- package/board/index.d.ts +38 -0
- package/config/plugin-scripts.json +5 -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/workflows/feature-verification.yaml +7 -4
- package/config/workflows/idea-pipeline.yaml +10 -4
- package/config/workflows/task-pipeline.yaml +71 -52
- package/config/workflows/wrapup-pipeline.yaml +92 -17
- package/package.json +15 -11
- package/plugins/sp/README.md +1 -1
- package/plugins/sp/commands/dev-review-session.md +4 -4
- package/plugins/sp/commands/dev-review.md +16 -5
- package/plugins/sp/commands/dev-verify.md +7 -4
- package/plugins/sp/commands/dev-verifyall.md +4 -3
- package/plugins/sp/commands/dev-wrap.md +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +2 -2
- package/plugins/sp/hooks/context-session-start.ts +2 -1
- package/plugins/sp/hooks/context-session-stop.ts +22 -14
- package/plugins/sp/hooks/pi/guard-extension.ts +112 -172
- package/plugins/sp/lib/idea-handoff.generated.mjs +2 -2
- package/plugins/sp/lib/inline-run.generated.d.mts +4 -2
- package/plugins/sp/lib/inline-run.generated.mjs +35 -14
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
- package/plugins/sp/scripts/feature-sync-bounded.mjs +5 -2
- package/plugins/sp/scripts/feature-sync-bounded.ts +3 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
- package/plugins/sp/scripts/history-anatomy-cache.mjs +3 -3
- package/plugins/sp/scripts/history-anatomy-cache.ts +4 -4
- package/plugins/sp/scripts/inline-run-setup.mjs +60 -12
- package/plugins/sp/scripts/inline-run-setup.ts +122 -15
- package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
- package/plugins/sp/scripts/pr-reviewing.ts +1 -0
- package/plugins/sp/scripts/quality-gate.mjs +15 -6
- package/plugins/sp/scripts/quality-gate.ts +30 -9
- package/plugins/sp/scripts/residual-scan.mjs +4 -4
- package/plugins/sp/scripts/residual-scan.ts +5 -4
- package/plugins/sp/scripts/script-contract-check.ts +175 -46
- package/plugins/sp/scripts/script-root.mjs +125 -0
- package/plugins/sp/scripts/script-root.ts +195 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +1 -1
- package/plugins/sp/scripts/task-evidence-precheck.ts +4 -2
- package/plugins/sp/scripts/task-size-precheck.ts +3 -1
- package/plugins/sp/scripts/validate-commands.ts +12 -12
- package/plugins/sp/scripts/validate-flag-contracts.ts +42 -30
- package/plugins/sp/scripts/verify-answer-lint.ts +3 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +5 -2
- package/plugins/sp/scripts/workflow-step-profile.ts +3 -1
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +5 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +4 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +32 -8
- package/plugins/sp/scripts/wrapup-steps.ts +56 -10
- package/plugins/sp/skills/code-verification/SKILL.md +12 -7
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -0
- package/plugins/sp/skills/functional-review/SKILL.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/session-review/SKILL.md +11 -9
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +9 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +65 -17
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +22 -5
- package/plugins/sp/tsconfig.json +8 -0
- package/spur.js +20934 -21087
- package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.BxJuwD7I.js} +151 -152
- package/web/_astro/BoardApp.Cpxntzad.js +1 -0
- package/web/_astro/TaskDetail.C5bW4WGV.js +1 -0
- package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
- package/web/_astro/arc.CyjRvNMY.js +1 -0
- package/web/_astro/architectureDiagram-3BPJPVTR.B4lWRzJA.js +36 -0
- package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.Be38USQ2.js} +1 -1
- package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
- package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
- package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
- package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
- package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
- package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
- package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
- package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.DT9Fj5Qx.js} +1 -1
- package/web/_astro/channel.CI6N_tCg.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.SeSBWLg5.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DoO4VE14.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.DrmTFA53.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.Bi-TUb_V.js} +4 -4
- package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.B-xp8Uhi.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.DGWrDnUU.js} +1 -1
- package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
- package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM._BagvPDy.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.VOboYWQ0.js} +1 -1
- package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
- package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.D_apfHS7.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.D_apfHS7.js} +1 -1
- package/web/_astro/client.BhluLQqe.js +1 -0
- package/web/_astro/client.DBpSRLnx.js +9 -0
- package/web/_astro/cose-bilkent-S5V4N54A.DNLU_L-x.js +1 -0
- package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.D8borhV-.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.Uc0lvjcV.js} +2 -2
- package/web/_astro/diagram-2AECGRRQ.DDIxtDBo.js +43 -0
- package/web/_astro/diagram-5GNKFQAL.DVoDnD3H.js +10 -0
- package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.CRubTJ-0.js} +3 -3
- package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.m9SeYcXQ.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.C6s2ZjV5.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.nSzr5KTj.js} +5 -5
- package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DrZcys16.js} +4 -4
- package/web/_astro/ganttDiagram-6RSMTGT7.Do4M1b-K.js +292 -0
- package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.BWc9oJ4l.js} +1 -1
- package/web/_astro/index.5dn-Zm-2.js +1 -0
- package/web/_astro/index.C-IqPpW2.js +1 -0
- package/web/_astro/index.CENnIEqT.css +1 -0
- package/web/_astro/index.Cyc_GA4f.js +1 -0
- package/web/_astro/index.tZfqwSut.js +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.BIdOctFk.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.CV5SuG2t.js} +5 -5
- package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.CxE-rEJ-.js} +1 -1
- package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
- package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.BPRdKWs_.js} +7 -7
- package/web/_astro/{linear.C2_IkbZT.js → linear.zRsuuDTE.js} +1 -1
- package/web/_astro/mermaid.core.jAJTcMKc.js +301 -0
- package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BffJxfCr.js} +2 -2
- package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CacwWaE3.js} +3 -3
- package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.hXELD06-.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.DdEbPcqT.js} +3 -3
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DgaDocKv.js} +4 -4
- package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B0KScnAu.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CV9M_WNc.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.BOAo74Et.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.CqqepUMe.js} +3 -3
- package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.DDqe4dOG.js} +5 -5
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.B3R9OPdO.js} +3 -3
- package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.B3SvgXpQ.js} +1 -1
- package/web/board-runtime.json +69 -0
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
- package/web/_astro/TaskDetail.DCqiC-OZ.js +0 -1
- package/web/_astro/arc.CPwg6Rw0.js +0 -1
- package/web/_astro/architectureDiagram-3BPJPVTR.DM_vp_hO.js +0 -36
- package/web/_astro/channel.MYZLKNwy.js +0 -1
- package/web/_astro/client.yhYJvxCU.js +0 -9
- package/web/_astro/cose-bilkent-S5V4N54A.B1wSL-Xb.js +0 -1
- package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
- package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
- package/web/_astro/ganttDiagram-6RSMTGT7.BO81S85v.js +0 -292
- package/web/_astro/index.De90oHcH.js +0 -1
- package/web/_astro/index.Hjbr15fG.css +0 -1
- package/web/_astro/mermaid.core.GAOYeSR0.js +0 -303
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.95",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -44,7 +44,7 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
44
44
|
for (let i = 0;i < args.length; i++) {
|
|
45
45
|
const arg = args[i];
|
|
46
46
|
if (arg === "--date" && i + 1 < args.length) {
|
|
47
|
-
options.date = args[++i];
|
|
47
|
+
options.date = args[++i] ?? options.date;
|
|
48
48
|
} else if (arg === "--dry-run") {
|
|
49
49
|
options.dryRun = true;
|
|
50
50
|
} else if (arg === "--output" && i + 1 < args.length) {
|
|
@@ -270,8 +270,8 @@ async function getGitCommits(date) {
|
|
|
270
270
|
} else if (currentCommit && line.includes("\t")) {
|
|
271
271
|
const parts = line.split("\t");
|
|
272
272
|
if (parts.length >= 3) {
|
|
273
|
-
const insertions = parseInt(parts[0], 10) || 0;
|
|
274
|
-
const deletions = parseInt(parts[1], 10) || 0;
|
|
273
|
+
const insertions = parseInt(parts[0] ?? "", 10) || 0;
|
|
274
|
+
const deletions = parseInt(parts[1] ?? "", 10) || 0;
|
|
275
275
|
currentCommit.filesChanged = (currentCommit.filesChanged ?? 0) + 1;
|
|
276
276
|
currentCommit.insertions = (currentCommit.insertions ?? 0) + insertions;
|
|
277
277
|
currentCommit.deletions = (currentCommit.deletions ?? 0) + deletions;
|
|
@@ -130,7 +130,7 @@ export function parseArgs(argv: string[] = process.argv.slice(2)): CliOptions {
|
|
|
130
130
|
for (let i = 0; i < args.length; i++) {
|
|
131
131
|
const arg = args[i];
|
|
132
132
|
if (arg === '--date' && i + 1 < args.length) {
|
|
133
|
-
options.date = args[++i];
|
|
133
|
+
options.date = args[++i] ?? options.date;
|
|
134
134
|
} else if (arg === '--dry-run') {
|
|
135
135
|
options.dryRun = true;
|
|
136
136
|
} else if (arg === '--output' && i + 1 < args.length) {
|
|
@@ -451,8 +451,8 @@ export async function getGitCommits(date: string): Promise<GitCommit[]> {
|
|
|
451
451
|
// This is a numstat line (files changed)
|
|
452
452
|
const parts = line.split('\t');
|
|
453
453
|
if (parts.length >= 3) {
|
|
454
|
-
const insertions = parseInt(parts[0], 10) || 0;
|
|
455
|
-
const deletions = parseInt(parts[1], 10) || 0;
|
|
454
|
+
const insertions = parseInt(parts[0] ?? '', 10) || 0;
|
|
455
|
+
const deletions = parseInt(parts[1] ?? '', 10) || 0;
|
|
456
456
|
currentCommit.filesChanged = (currentCommit.filesChanged ?? 0) + 1;
|
|
457
457
|
currentCommit.insertions = (currentCommit.insertions ?? 0) + insertions;
|
|
458
458
|
currentCommit.deletions = (currentCommit.deletions ?? 0) + deletions;
|
|
@@ -22,8 +22,8 @@ function countLedgerDataRows(markdown) {
|
|
|
22
22
|
return Math.max(rows.length - 1, 0);
|
|
23
23
|
}
|
|
24
24
|
function declaredExecutedSteps(markdown) {
|
|
25
|
-
const match = markdown.match(/\*\*Steps:\*\*\s*\d+\s+derived,\s*(\d+)\s+executed/);
|
|
26
|
-
return match ? Number.parseInt(match
|
|
25
|
+
const match = markdown.match(/\*\*Steps:\*\*\s*\d+\s+derived,\s*(\d+)\s+executed/)?.[1];
|
|
26
|
+
return match ? Number.parseInt(match, 10) : null;
|
|
27
27
|
}
|
|
28
28
|
var UNKNOWN_CELLS = new Set(["~unknown", "unknown", "—", "-", "?", "n/a", "~n/a"]);
|
|
29
29
|
var CACHE_SHARE_TOLERANCE = 1;
|
|
@@ -46,15 +46,15 @@ function parseTokenCell(cell) {
|
|
|
46
46
|
const t = cell.trim();
|
|
47
47
|
if (UNKNOWN_CELLS.has(t))
|
|
48
48
|
return null;
|
|
49
|
-
const m = t.match(/~?(\d+)/);
|
|
50
|
-
return m ? Number.parseInt(m
|
|
49
|
+
const m = t.match(/~?(\d+)/)?.[1];
|
|
50
|
+
return m ? Number.parseInt(m, 10) : null;
|
|
51
51
|
}
|
|
52
52
|
function parsePctCell(cell) {
|
|
53
53
|
const t = cell.trim();
|
|
54
54
|
if (UNKNOWN_CELLS.has(t))
|
|
55
55
|
return null;
|
|
56
|
-
const m = t.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
57
|
-
return m ? Number.parseFloat(m
|
|
56
|
+
const m = t.match(/(\d+(?:\.\d+)?)\s*%/)?.[1];
|
|
57
|
+
return m ? Number.parseFloat(m) : null;
|
|
58
58
|
}
|
|
59
59
|
function ledgerCostRows(markdown) {
|
|
60
60
|
const body = sectionBody(markdown, /^### 3\. Monitor Ledger\s*$/m);
|
|
@@ -104,7 +104,7 @@ function validateCostEvidence(markdown, errors) {
|
|
|
104
104
|
}
|
|
105
105
|
for (const scope of pctScopes) {
|
|
106
106
|
for (const m of scope.matchAll(/(\d+(?:\.\d+)?)\s*%/g)) {
|
|
107
|
-
const pct = Number.parseFloat(m[1]);
|
|
107
|
+
const pct = Number.parseFloat(m[1] ?? "");
|
|
108
108
|
if (pct < 0 || pct > 100)
|
|
109
109
|
errors.push(`impossible_percentage:${m[0].trim()}`);
|
|
110
110
|
}
|
|
@@ -194,6 +194,8 @@ function parseValidateCliArgs(argv) {
|
|
|
194
194
|
let help = false;
|
|
195
195
|
for (let i = 0;i < argv.length; i++) {
|
|
196
196
|
const a = argv[i];
|
|
197
|
+
if (a === undefined)
|
|
198
|
+
continue;
|
|
197
199
|
if (a === "--help" || a === "-h")
|
|
198
200
|
help = true;
|
|
199
201
|
else if (a === "--json")
|
|
@@ -50,8 +50,8 @@ function countLedgerDataRows(markdown: string): number | null {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function declaredExecutedSteps(markdown: string): number | null {
|
|
53
|
-
const match = markdown.match(/\*\*Steps:\*\*\s*\d+\s+derived,\s*(\d+)\s+executed/);
|
|
54
|
-
return match ? Number.parseInt(match
|
|
53
|
+
const match = markdown.match(/\*\*Steps:\*\*\s*\d+\s+derived,\s*(\d+)\s+executed/)?.[1];
|
|
54
|
+
return match ? Number.parseInt(match, 10) : null;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// ── Cost evidence contract (task 0913, R2) ──────────────────────────────────
|
|
@@ -97,15 +97,15 @@ interface LedgerCostRow {
|
|
|
97
97
|
function parseTokenCell(cell: string): number | null {
|
|
98
98
|
const t = cell.trim();
|
|
99
99
|
if (UNKNOWN_CELLS.has(t)) return null;
|
|
100
|
-
const m = t.match(/~?(\d+)/);
|
|
101
|
-
return m ? Number.parseInt(m
|
|
100
|
+
const m = t.match(/~?(\d+)/)?.[1];
|
|
101
|
+
return m ? Number.parseInt(m, 10) : null;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
function parsePctCell(cell: string): number | null {
|
|
105
105
|
const t = cell.trim();
|
|
106
106
|
if (UNKNOWN_CELLS.has(t)) return null;
|
|
107
|
-
const m = t.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
108
|
-
return m ? Number.parseFloat(m
|
|
107
|
+
const m = t.match(/(\d+(?:\.\d+)?)\s*%/)?.[1];
|
|
108
|
+
return m ? Number.parseFloat(m) : null;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function ledgerCostRows(markdown: string): LedgerCostRow[] {
|
|
@@ -169,7 +169,7 @@ function validateCostEvidence(markdown: string, errors: string[]): void {
|
|
|
169
169
|
}
|
|
170
170
|
for (const scope of pctScopes) {
|
|
171
171
|
for (const m of scope.matchAll(/(\d+(?:\.\d+)?)\s*%/g)) {
|
|
172
|
-
const pct = Number.parseFloat(m[1]);
|
|
172
|
+
const pct = Number.parseFloat(m[1] ?? '');
|
|
173
173
|
if (pct < 0 || pct > 100) errors.push(`impossible_percentage:${m[0].trim()}`);
|
|
174
174
|
}
|
|
175
175
|
}
|
|
@@ -291,6 +291,7 @@ export function parseValidateCliArgs(argv: string[]): ValidateCliArgs {
|
|
|
291
291
|
let help = false;
|
|
292
292
|
for (let i = 0; i < argv.length; i++) {
|
|
293
293
|
const a = argv[i];
|
|
294
|
+
if (a === undefined) continue;
|
|
294
295
|
if (a === '--help' || a === '-h') help = true;
|
|
295
296
|
else if (a === '--json') json = true;
|
|
296
297
|
else if (a === '--file') file = argv[++i] ?? null;
|
|
@@ -103,8 +103,9 @@ through unchanged.
|
|
|
103
103
|
|
|
104
104
|
Exit: 0 = sync handled (applied / no-op / suppressed-blocked / live-blocked).`;
|
|
105
105
|
function defaultSpurBin() {
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
const fromEnv = getEnvVar("SPUR_BIN");
|
|
107
|
+
if (fromEnv)
|
|
108
|
+
return fromEnv;
|
|
108
109
|
const local = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
109
110
|
if (existsSync(local))
|
|
110
111
|
return `bun ${local}`;
|
|
@@ -118,6 +119,8 @@ function parseBoundedSyncCliArgs(argv) {
|
|
|
118
119
|
let help = false;
|
|
119
120
|
for (let i = 0;i < argv.length; i++) {
|
|
120
121
|
const a = argv[i];
|
|
122
|
+
if (a === undefined)
|
|
123
|
+
continue;
|
|
121
124
|
if (a === "--help" || a === "-h")
|
|
122
125
|
help = true;
|
|
123
126
|
else if (a === "--json")
|
|
@@ -251,7 +251,8 @@ Exit: 0 = sync handled (applied / no-op / suppressed-blocked / live-blocked).`;
|
|
|
251
251
|
* keeps ad-hoc invocations from silently hitting a stale PATH install.
|
|
252
252
|
*/
|
|
253
253
|
export function defaultSpurBin(): string {
|
|
254
|
-
|
|
254
|
+
const fromEnv = getEnvVar('SPUR_BIN');
|
|
255
|
+
if (fromEnv) return fromEnv;
|
|
255
256
|
// scripts/ -> plugins/sp/ -> <repo>/apps/cli/src/index.ts (fileURLToPath — raw pathname breaks
|
|
256
257
|
// on %-encoded paths, e.g. spaces in the checkout directory)
|
|
257
258
|
const local = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
@@ -268,6 +269,7 @@ export function parseBoundedSyncCliArgs(argv: string[]): BoundedSyncCliArgs {
|
|
|
268
269
|
|
|
269
270
|
for (let i = 0; i < argv.length; i++) {
|
|
270
271
|
const a = argv[i];
|
|
272
|
+
if (a === undefined) continue;
|
|
271
273
|
if (a === '--help' || a === '-h') help = true;
|
|
272
274
|
else if (a === '--json') json = true;
|
|
273
275
|
else if (a === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
@@ -45,12 +45,12 @@ const SAFE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
|
45
45
|
|
|
46
46
|
/** Shape of the generated inline bundle / repo app source this script consumes. */
|
|
47
47
|
interface VerificationModule {
|
|
48
|
-
startFeatureVerificationReceipt: typeof import('
|
|
49
|
-
completeFeatureVerificationReceipt: typeof import('
|
|
50
|
-
captureFeatureReceiptDigest: typeof import('
|
|
51
|
-
resolveWorkflowDefinition: typeof import('
|
|
52
|
-
splitLaunchCommand: typeof import('
|
|
53
|
-
openInlineRunProjectDb: typeof import('
|
|
48
|
+
startFeatureVerificationReceipt: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['startFeatureVerificationReceipt'];
|
|
49
|
+
completeFeatureVerificationReceipt: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['completeFeatureVerificationReceipt'];
|
|
50
|
+
captureFeatureReceiptDigest: typeof import('../../../packages/app/src/workflow/feature-verification-receipt')['captureFeatureReceiptDigest'];
|
|
51
|
+
resolveWorkflowDefinition: typeof import('../../../packages/app/src/workflow/workflow-resolver')['resolveWorkflowDefinition'];
|
|
52
|
+
splitLaunchCommand: typeof import('../../../packages/app/src/workflow/split-launch-command')['splitLaunchCommand'];
|
|
53
|
+
openInlineRunProjectDb: typeof import('../../../packages/app/src/services/inline-run-setup')['openInlineRunProjectDb'];
|
|
54
54
|
ArtifactDao: new (
|
|
55
55
|
adapter: never,
|
|
56
56
|
) => { record: (input: { runId: string; path: string; kind: string }) => Promise<unknown> };
|
|
@@ -290,9 +290,9 @@ function checkReportStructure(reportMarkdown) {
|
|
|
290
290
|
const knownRows = findingsBody.match(new RegExp(`^\\|\\s*(?:${catAlt}):[^|]+`, "gm")) ?? [];
|
|
291
291
|
const invalidRows = findingsBody.match(/^\|\s*([^|:\s][^|:]*):[^|]+/gm) ?? [];
|
|
292
292
|
for (const row of [...new Set([...knownRows, ...invalidRows])]) {
|
|
293
|
-
const
|
|
294
|
-
if (
|
|
295
|
-
problems.push(`finding-invalid-key-category:${
|
|
293
|
+
const seg = row.match(/^\|\s*([^:|]+):/)?.[1];
|
|
294
|
+
if (seg !== undefined && !FINDING_CATEGORIES.includes(seg.trim())) {
|
|
295
|
+
problems.push(`finding-invalid-key-category:${seg.trim()}`);
|
|
296
296
|
}
|
|
297
297
|
const rowLower = row.toLowerCase();
|
|
298
298
|
for (const field of FINDING_FIELDS) {
|
|
@@ -375,9 +375,9 @@ export function checkReportStructure(reportMarkdown: string): { ok: boolean; pro
|
|
|
375
375
|
for (const row of [...new Set([...knownRows, ...invalidRows])]) {
|
|
376
376
|
// 0686/I9: a pipe row outside the closed first-segment set fails by name instead of
|
|
377
377
|
// being skipped silently by the legacy regex.
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
380
|
-
problems.push(`finding-invalid-key-category:${
|
|
378
|
+
const seg = row.match(/^\|\s*([^:|]+):/)?.[1];
|
|
379
|
+
if (seg !== undefined && !(FINDING_CATEGORIES as readonly string[]).includes(seg.trim())) {
|
|
380
|
+
problems.push(`finding-invalid-key-category:${seg.trim()}`);
|
|
381
381
|
}
|
|
382
382
|
// Case-insensitive on BOTH sides — camelCase triage fields otherwise never match a
|
|
383
383
|
// lowercased row.
|
|
@@ -630,7 +630,7 @@ export function validateSelector(opts: {
|
|
|
630
630
|
output?: string;
|
|
631
631
|
}): SelectorValidation {
|
|
632
632
|
const errors: string[] = [];
|
|
633
|
-
const has = (v?: string) => v !== undefined && v !== '';
|
|
633
|
+
const has = (v?: string): v is string => v !== undefined && v !== '';
|
|
634
634
|
const val = (v?: string) => (has(v) ? v : null);
|
|
635
635
|
const mode = has(opts.mode) ? opts.mode : 'daily';
|
|
636
636
|
if (mode !== 'daily' && mode !== 'ad-hoc') {
|
|
@@ -30,6 +30,8 @@ function usage() {
|
|
|
30
30
|
console.error(" bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]");
|
|
31
31
|
console.error(" bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> " + "--status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]");
|
|
32
32
|
console.error(" bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]");
|
|
33
|
+
console.error(" bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]");
|
|
34
|
+
console.error(" terminal-reason is a closed enum (0937 R2): done, paused-operator, failed-check, failed-agent, " + "failed-timeout, failed-guard, cancelled, interrupted, retry-exhausted");
|
|
33
35
|
console.error(" bun plugins/sp/scripts/inline-run-setup.ts --decide --run-id <id> --node <state> --options-json <file> [--spur-bin <path>]");
|
|
34
36
|
process.exit(2);
|
|
35
37
|
}
|
|
@@ -152,6 +154,9 @@ async function printFingerprint(taskFile, featureFile, spurBin) {
|
|
|
152
154
|
return 0;
|
|
153
155
|
}
|
|
154
156
|
var CLOSE_STATUSES = new Set(["done", "failed", "paused"]);
|
|
157
|
+
function isCloseStatus(status) {
|
|
158
|
+
return CLOSE_STATUSES.has(status);
|
|
159
|
+
}
|
|
155
160
|
var TERMINAL_REASONS = new Set([
|
|
156
161
|
"done",
|
|
157
162
|
"paused-operator",
|
|
@@ -164,6 +169,9 @@ var TERMINAL_REASONS = new Set([
|
|
|
164
169
|
"retry-exhausted"
|
|
165
170
|
]);
|
|
166
171
|
var ACTION_STATUSES = new Set(["done", "failed"]);
|
|
172
|
+
function isActionStatus(status) {
|
|
173
|
+
return ACTION_STATUSES.has(status);
|
|
174
|
+
}
|
|
167
175
|
function runRecordLogPath(runDir, runId) {
|
|
168
176
|
const markdownPath = join(runDir, `${runId}.md`);
|
|
169
177
|
const legacyLogPath = join(runDir, `${runId}.log`);
|
|
@@ -171,7 +179,7 @@ function runRecordLogPath(runDir, runId) {
|
|
|
171
179
|
return legacyLogPath;
|
|
172
180
|
return markdownPath;
|
|
173
181
|
}
|
|
174
|
-
function
|
|
182
|
+
function appendRunLogLine(runId, detail) {
|
|
175
183
|
try {
|
|
176
184
|
const runDir = join(process.cwd(), ".spur", "run");
|
|
177
185
|
if (!existsSync(runDir))
|
|
@@ -185,7 +193,7 @@ function appendTraceFailureLine(runId, detail) {
|
|
|
185
193
|
async function runTraceMode(input) {
|
|
186
194
|
const operation = input.close ? "run.close" : "action.finish";
|
|
187
195
|
const fail = (error) => {
|
|
188
|
-
|
|
196
|
+
appendRunLogLine(input.runId, `trace-emission-failed operation=${operation} run=${input.runId}${input.node === "" ? "" : ` node=${input.node}`}${input.kind === "" ? "" : ` kind=${input.kind}`}: ${error}`);
|
|
189
197
|
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}
|
|
190
198
|
`);
|
|
191
199
|
return input.close ? 1 : 0;
|
|
@@ -197,7 +205,7 @@ async function runTraceMode(input) {
|
|
|
197
205
|
projectDb = await app.openInlineRunProjectDb(process.cwd());
|
|
198
206
|
const writer = app.createWorkflowActionTraceWriter(projectDb.adapter, (failure) => {
|
|
199
207
|
const detail = failure;
|
|
200
|
-
|
|
208
|
+
appendRunLogLine(input.runId, `trace-emission-failed operation=${detail.operation ?? operation} run=${input.runId}: ${detail.error ?? "unknown error"}`);
|
|
201
209
|
});
|
|
202
210
|
const result = input.close ? await writer.closeRun(input.runId, input.status, undefined, input.reason) : await writer.recordAction({
|
|
203
211
|
runId: input.runId,
|
|
@@ -211,13 +219,20 @@ async function runTraceMode(input) {
|
|
|
211
219
|
const failure = result.failure;
|
|
212
220
|
return fail(failure.error ?? "unknown trace emission failure");
|
|
213
221
|
}
|
|
222
|
+
if (input.close && input.status === "done" && result.actionRows === 0) {
|
|
223
|
+
const error = `run ${input.runId} closed done with zero action_runs rows`;
|
|
224
|
+
appendRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${error}`);
|
|
225
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error, code: "NO_ACTION_ROWS", actionRows: 0 })}
|
|
226
|
+
`);
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
214
229
|
process.stdout.write(`${JSON.stringify({ ...result, runId: input.runId })}
|
|
215
230
|
`);
|
|
216
231
|
return 0;
|
|
217
232
|
} catch (error) {
|
|
218
233
|
if (input.close && error.name === "RunRowNotFoundError") {
|
|
219
234
|
const message = error instanceof Error ? error.message : String(error);
|
|
220
|
-
|
|
235
|
+
appendRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${message}`);
|
|
221
236
|
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error: message, code: "RUN_NOT_FOUND" })}
|
|
222
237
|
`);
|
|
223
238
|
return 1;
|
|
@@ -249,8 +264,9 @@ async function runDecideMode(input) {
|
|
|
249
264
|
}
|
|
250
265
|
if (!outcome.ok)
|
|
251
266
|
return decideFailed(outcome.error ?? "decide failed without an error message");
|
|
252
|
-
process.stdout.write(`${JSON.stringify({
|
|
267
|
+
process.stdout.write(`${JSON.stringify({ runId: input.runId, node: input.node, ...outcome, ok: true })}
|
|
253
268
|
`);
|
|
269
|
+
appendRunLogLine(input.runId, `decide node=${input.node} value=${outcome.value ?? ""} source=${outcome.source ?? "default"} reason=${outcome.reason ?? ""}`);
|
|
254
270
|
return await runTraceMode({
|
|
255
271
|
runId: input.runId,
|
|
256
272
|
close: false,
|
|
@@ -262,6 +278,25 @@ async function runDecideMode(input) {
|
|
|
262
278
|
spurBin: input.spurBin
|
|
263
279
|
});
|
|
264
280
|
}
|
|
281
|
+
async function runPersistOutMode(input) {
|
|
282
|
+
try {
|
|
283
|
+
const { entry } = resolveAppEntry(input.spurBin);
|
|
284
|
+
const app = await import(entry);
|
|
285
|
+
const result = await app.persistWorktreeRuns({
|
|
286
|
+
fromWorkdir: input.from,
|
|
287
|
+
toWorkdir: process.cwd(),
|
|
288
|
+
...input.taskFiles.length > 0 ? { taskFiles: input.taskFiles } : {}
|
|
289
|
+
});
|
|
290
|
+
process.stdout.write(`${JSON.stringify({ ok: true, persisted: result.persisted, skipped: result.skipped })}
|
|
291
|
+
`);
|
|
292
|
+
return 0;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
295
|
+
process.stdout.write(`${JSON.stringify({ ok: false, error: message })}
|
|
296
|
+
`);
|
|
297
|
+
return 1;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
265
300
|
async function main() {
|
|
266
301
|
if (!process.versions.bun) {
|
|
267
302
|
const child = spawnSync("bun", [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
|
|
@@ -274,11 +309,13 @@ async function main() {
|
|
|
274
309
|
let runId = "";
|
|
275
310
|
let file = "";
|
|
276
311
|
let fingerprint = false;
|
|
277
|
-
|
|
312
|
+
const taskFiles = [];
|
|
278
313
|
let featureFile = "";
|
|
279
314
|
let action = false;
|
|
280
315
|
let close = false;
|
|
281
316
|
let decide = false;
|
|
317
|
+
let persistOut = false;
|
|
318
|
+
let from = "";
|
|
282
319
|
let optionsJson = "";
|
|
283
320
|
let node = "";
|
|
284
321
|
let kind = "";
|
|
@@ -296,7 +333,7 @@ async function main() {
|
|
|
296
333
|
else if (argv[i] === "--fingerprint")
|
|
297
334
|
fingerprint = true;
|
|
298
335
|
else if (argv[i] === "--task-file")
|
|
299
|
-
|
|
336
|
+
taskFiles.push(argv[++i] ?? "");
|
|
300
337
|
else if (argv[i] === "--feature-file")
|
|
301
338
|
featureFile = argv[++i] ?? "";
|
|
302
339
|
else if (argv[i] === "--action")
|
|
@@ -305,6 +342,10 @@ async function main() {
|
|
|
305
342
|
close = true;
|
|
306
343
|
else if (argv[i] === "--decide")
|
|
307
344
|
decide = true;
|
|
345
|
+
else if (argv[i] === "--persist-out")
|
|
346
|
+
persistOut = true;
|
|
347
|
+
else if (argv[i] === "--from")
|
|
348
|
+
from = argv[++i] ?? "";
|
|
308
349
|
else if (argv[i] === "--options-json")
|
|
309
350
|
optionsJson = argv[++i] ?? "";
|
|
310
351
|
else if (argv[i] === "--node")
|
|
@@ -323,12 +364,12 @@ async function main() {
|
|
|
323
364
|
spurBin = argv[++i] ?? spurBin;
|
|
324
365
|
}
|
|
325
366
|
if (fingerprint) {
|
|
326
|
-
if (runId !== "" || file !== "" ||
|
|
367
|
+
if (runId !== "" || file !== "" || taskFiles.length !== 1 || (taskFiles[0] ?? "").trim() === "")
|
|
327
368
|
usage();
|
|
328
|
-
process.exit(await printFingerprint(
|
|
369
|
+
process.exit(await printFingerprint(taskFiles[0] ?? "", featureFile, spurBin));
|
|
329
370
|
}
|
|
330
371
|
if (decide) {
|
|
331
|
-
if (action || close || fingerprint || file !== "" ||
|
|
372
|
+
if (action || close || fingerprint || file !== "" || taskFiles.length > 0)
|
|
332
373
|
usage();
|
|
333
374
|
if (runId.trim() === "" || node.trim() === "" || optionsJson.trim() === "")
|
|
334
375
|
usage();
|
|
@@ -336,6 +377,13 @@ async function main() {
|
|
|
336
377
|
refuseUnsafeRunId(runId);
|
|
337
378
|
process.exit(await runDecideMode({ runId, node, optionsFile: optionsJson, spurBin }));
|
|
338
379
|
}
|
|
380
|
+
if (persistOut) {
|
|
381
|
+
if (fingerprint || decide || action || close || runId !== "" || file !== "")
|
|
382
|
+
usage();
|
|
383
|
+
if (from.trim() === "" || taskFiles.some((taskFile) => taskFile.trim() === ""))
|
|
384
|
+
usage();
|
|
385
|
+
process.exit(await runPersistOutMode({ from, taskFiles, spurBin }));
|
|
386
|
+
}
|
|
339
387
|
if (action || close) {
|
|
340
388
|
if (action && close)
|
|
341
389
|
usage();
|
|
@@ -344,7 +392,7 @@ async function main() {
|
|
|
344
392
|
if (!SAFE_RUN_ID_RE.test(runId))
|
|
345
393
|
refuseUnsafeRunId(runId);
|
|
346
394
|
if (close) {
|
|
347
|
-
if (!
|
|
395
|
+
if (!isCloseStatus(status))
|
|
348
396
|
usage();
|
|
349
397
|
if (reason.trim() === "") {
|
|
350
398
|
if (status === "failed")
|
|
@@ -366,7 +414,7 @@ async function main() {
|
|
|
366
414
|
}
|
|
367
415
|
if (node.trim() === "" || kind.trim() === "")
|
|
368
416
|
usage();
|
|
369
|
-
if (!
|
|
417
|
+
if (!isActionStatus(status))
|
|
370
418
|
usage();
|
|
371
419
|
if (okRaw !== "true" && okRaw !== "false")
|
|
372
420
|
usage();
|