@gobing-ai/spur 0.3.99 → 0.4.1
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/config.global.yaml +17 -0
- package/config/decisions/gates.yaml +36 -0
- package/config/decisions/history-anatomy.yaml +35 -0
- package/config/decisions/idea-pipeline.yaml +38 -0
- package/config/decisions/task-pipeline.yaml +55 -0
- package/config/plugin-scripts.json +25 -0
- package/config/rules/boundary/sp-runtime-path.yaml +2 -0
- package/config/rules/docs/sourced-api-claims.yaml +60 -0
- package/config/rules/quality/coverage-gate.yaml +5 -3
- package/config/rules/recommended-pre-check.yaml +8 -3
- package/config/rules/strict/runtime-boundaries.yaml +11 -0
- package/config/rules/structure/protected-files.yaml +3 -0
- package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
- package/config/script-placement-baseline.json +2 -2
- package/config/workflows/feature-lifecycle.yaml +9 -2
- package/config/workflows/history-anatomy.yaml +22 -0
- package/config/workflows/idea-pipeline.yaml +75 -0
- package/config/workflows/task-pipeline.yaml +368 -49
- package/config/workflows/wrapup-pipeline.yaml +106 -13
- package/package.json +9 -9
- package/plugins/sp/README.md +25 -2
- package/plugins/sp/agents/super-planner.md +11 -0
- package/plugins/sp/agents/super-reviewer.md +39 -11
- package/plugins/sp/bunfig.toml +8 -0
- package/plugins/sp/commands/dev-brainstorm.md +29 -16
- package/plugins/sp/commands/dev-changelog.md +5 -4
- package/plugins/sp/commands/dev-find-way.md +80 -0
- package/plugins/sp/commands/dev-idea.md +6 -0
- package/plugins/sp/commands/dev-parallel.md +8 -2
- package/plugins/sp/commands/dev-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +14 -7
- package/plugins/sp/commands/dev-run.md +17 -4
- package/plugins/sp/commands/dev-runall.md +21 -10
- package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
- package/plugins/sp/hooks/context-session-start.ts +1 -1
- package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
- package/plugins/sp/hooks/hooks.json +45 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +72 -11
- package/plugins/sp/lib/inline-run-app.ts +28 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +6 -0
- package/plugins/sp/lib/inline-run.generated.mjs +364 -46
- package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
- package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
- package/plugins/sp/lib/run-summary-core.ts +561 -0
- package/plugins/sp/lib/transcript.ts +275 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +335 -0
- package/plugins/sp/scripts/batch-plan.ts +100 -0
- package/plugins/sp/scripts/batch-preflight.mjs +51 -3
- package/plugins/sp/scripts/batch-preflight.ts +72 -1
- package/plugins/sp/scripts/commit-guard.mjs +216 -0
- package/plugins/sp/scripts/commit-guard.ts +249 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
- package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
- package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
- package/plugins/sp/scripts/inline-run-setup.mjs +563 -91
- package/plugins/sp/scripts/inline-run-setup.ts +65 -97
- package/plugins/sp/scripts/persist-out-check.mjs +90 -41
- package/plugins/sp/scripts/persist-out-check.ts +112 -57
- package/plugins/sp/scripts/quality-gate.mjs +30407 -73
- package/plugins/sp/scripts/quality-gate.ts +21 -5
- package/plugins/sp/scripts/residual-scan.mjs +88 -38
- package/plugins/sp/scripts/residual-scan.ts +35 -38
- package/plugins/sp/scripts/run-summary.mjs +693 -0
- package/plugins/sp/scripts/run-summary.ts +105 -0
- package/plugins/sp/scripts/session-timeline.mjs +331 -0
- package/plugins/sp/scripts/session-timeline.ts +204 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
- package/plugins/sp/scripts/wrapup-steps.ts +203 -1
- package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
- package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
- package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
- package/plugins/sp/skills/code-verification/SKILL.md +8 -4
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
- package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
- package/plugins/sp/skills/session-review/SKILL.md +77 -24
- package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
- package/plugins/sp/skills/spur-cli/SKILL.md +6 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +124 -17
- package/plugins/sp/skills/spur-cli/references/decision.md +98 -0
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +41 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
- package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
- package/plugins/sp/skills/spur-composer/SKILL.md +28 -2
- package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +51 -18
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -963
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +20 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +51 -9
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +361 -156
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
- package/schemas/decision-catalog.schema.json +226 -0
- package/schemas/spur-config.schema.json +25 -0
- package/schemas/state-machine-workflow.schema.json +23 -0
- package/spur.js +28165 -19051
- package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.D66k_s_T.js} +1 -1
- package/web/_astro/BoardApp.D6sjAJr_.js +324 -0
- package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.BynLnMNc.js} +1 -1
- package/web/_astro/{arc.Bu5XpBEz.js → arc._BUadrdH.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
- package/web/_astro/channel.DZVfto-r.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
- package/web/_astro/index.COh_kBxN.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
- package/web/_astro/{linear.Dxw00fGQ.js → linear.MzmRrNER.js} +1 -1
- package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.fK-WNswS.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
- package/web/board-runtime.json +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
- package/web/_astro/channel.6IFIKBhu.js +0 -1
- package/web/_astro/index.CbNz17Sx.css +0 -1
|
@@ -1,21 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* persist-out-check — WT-4 pre-removal assertion for E71 persist-out (task 1067 R1).
|
|
3
|
+
* persist-out-check — WT-4 pre-removal assertion for E71 persist-out (task 1067 R1, 1148 R1).
|
|
4
4
|
*
|
|
5
|
-
* Before `git worktree remove`, verify the worktree's durable evidence would not be
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* never overwrites — reconcile by hand per execution-batch.md). No new public CLI verb:
|
|
11
|
-
* this is a driver-side assertion step, not a corpus tool.
|
|
12
|
-
*
|
|
13
|
-
* ponytail: file-level comparison only — DB run rows and nested-run deep walks stay
|
|
14
|
-
* persist-out's job (`inline-run-setup --persist-out`); this checks the planes E71
|
|
15
|
-
* names as files. Listing caps (64/prefix, 256 evidence) refuse runaways by name.
|
|
16
|
-
*
|
|
17
|
-
* Node-builtin imports only; pure helpers are exported for tests. Exit 0 ok, 1
|
|
18
|
-
* blocked/divergent, 2 usage error. Normal output is the finding lines themselves.
|
|
5
|
+
* Before `git worktree remove`, verify the worktree's OWNED durable evidence would not be abandoned:
|
|
6
|
+
* every `.spur/run/` and `.spur/memory/evidence/` file starting with a forwarded `<wbs>-`/`<runId>-`
|
|
7
|
+
* prefix must exist in the invoking tree with identical bytes. Unowned evidence is counted
|
|
8
|
+
* (`unowned: N`) and does not block (1148 R1). Node builtins only; helpers are exported for tests.
|
|
9
|
+
* Exit 0 ok, 1 blocked/divergent, 2 usage error. Normal output is the finding lines themselves.
|
|
19
10
|
*/
|
|
20
11
|
|
|
21
12
|
import { spawnSync } from 'node:child_process';
|
|
@@ -26,7 +17,6 @@ const EVIDENCE_DIR = join('.spur', 'memory', 'evidence');
|
|
|
26
17
|
const RUN_DIR = join('.spur', 'run');
|
|
27
18
|
const NAMED_CAP = 32;
|
|
28
19
|
const PREFIX_CAP = 64;
|
|
29
|
-
const EVIDENCE_CAP = 256;
|
|
30
20
|
|
|
31
21
|
/** Task files own their `<wbs>-*` evidence: leading digits before `_` in the basename. */
|
|
32
22
|
export function wbsOfTaskFile(path: string): string | null {
|
|
@@ -34,49 +24,64 @@ export function wbsOfTaskFile(path: string): string | null {
|
|
|
34
24
|
return m?.[1] ?? null;
|
|
35
25
|
}
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
export interface ListObligationsResult {
|
|
28
|
+
files: string[];
|
|
29
|
+
unowned: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Recursively list files under `dir`, relative to `root`. */
|
|
33
|
+
function walkFiles(dir: string, root: string): string[] {
|
|
39
34
|
if (!existsSync(dir)) return [];
|
|
40
35
|
const out: string[] = [];
|
|
41
|
-
const walk = (cur: string):
|
|
42
|
-
for (const
|
|
43
|
-
const p = join(cur,
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
} else {
|
|
47
|
-
out.push(relative(root, p));
|
|
48
|
-
if (out.length > cap) return false;
|
|
49
|
-
}
|
|
36
|
+
const walk = (cur: string): void => {
|
|
37
|
+
for (const e of readdirSync(cur, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
38
|
+
const p = join(cur, e.name);
|
|
39
|
+
if (e.isDirectory()) walk(p);
|
|
40
|
+
else if (e.isFile()) out.push(relative(root, p));
|
|
50
41
|
}
|
|
51
|
-
return true;
|
|
52
42
|
};
|
|
53
|
-
|
|
43
|
+
walk(dir);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Owned evidence (prefix-matched) under one plane; null once a prefix exceeds PREFIX_CAP. */
|
|
48
|
+
function collectOwned(rels: string[], prefixes: string[], counts: Map<string, number>, out: string[]): boolean {
|
|
49
|
+
for (const rel of rels) {
|
|
50
|
+
const prefix = prefixes.find((p) => basename(rel).startsWith(`${p}-`));
|
|
51
|
+
if (prefix === undefined) continue;
|
|
52
|
+
const count = (counts.get(prefix) ?? 0) + 1;
|
|
53
|
+
if (count > PREFIX_CAP) return false;
|
|
54
|
+
counts.set(prefix, count);
|
|
55
|
+
out.push(rel);
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/**
|
|
57
|
-
* Evidence
|
|
58
|
-
*
|
|
59
|
-
*
|
|
61
|
+
* Evidence the worktree owns: `.spur/memory/evidence/` (recursive) and `.spur/run/` (flat) files
|
|
62
|
+
* whose basename starts with a forwarded `<wbs>-`/`<runId>-` prefix. Evidence matching no prefix is
|
|
63
|
+
* counted as `unowned` (1148 R1). Null when no prefix is forwarded or a prefix cap is exceeded.
|
|
60
64
|
*/
|
|
61
|
-
export function listObligations(wtRoot: string, wbsList: string[], runIds: string[]):
|
|
62
|
-
const
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
65
|
+
export function listObligations(wtRoot: string, wbsList: string[], runIds: string[]): ListObligationsResult | null {
|
|
66
|
+
const prefixes = [...new Set([...wbsList, ...runIds].filter(Boolean))];
|
|
67
|
+
if (prefixes.length === 0) return null;
|
|
68
|
+
|
|
69
|
+
const evDir = join(wtRoot, EVIDENCE_DIR);
|
|
70
|
+
const runDir = join(wtRoot, RUN_DIR);
|
|
71
|
+
const evFiles = walkFiles(evDir, wtRoot);
|
|
72
|
+
const runFiles = existsSync(runDir)
|
|
73
|
+
? readdirSync(runDir, { withFileTypes: true })
|
|
74
|
+
.filter((e) => e.isFile())
|
|
75
|
+
.map((e) => join(RUN_DIR, e.name))
|
|
76
|
+
.sort()
|
|
77
|
+
: [];
|
|
78
|
+
|
|
79
|
+
const files: string[] = [];
|
|
80
|
+
const counts = new Map<string, number>();
|
|
81
|
+
if (!collectOwned(evFiles, prefixes, counts, files)) return null;
|
|
82
|
+
if (!collectOwned(runFiles, prefixes, counts, files)) return null;
|
|
83
|
+
const unowned = evFiles.length - files.filter((f) => f.startsWith(EVIDENCE_DIR)).length;
|
|
84
|
+
return { files: [...new Set(files)].sort(), unowned };
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
/** Classify obligations against the invoking tree: missing, divergent (byte-differs), or ok. */
|
|
@@ -84,11 +89,15 @@ export function compareTrees(
|
|
|
84
89
|
wtRoot: string,
|
|
85
90
|
invokeRoot: string,
|
|
86
91
|
files: string[],
|
|
92
|
+
foreignDivergent = new Set<string>(),
|
|
87
93
|
): { missing: string[]; divergent: string[]; ok: number } {
|
|
88
94
|
const missing: string[] = [];
|
|
89
95
|
const divergent: string[] = [];
|
|
90
96
|
let ok = 0;
|
|
91
97
|
for (const rel of files) {
|
|
98
|
+
if (foreignDivergent.has(rel) || foreignDivergent.has(basename(rel))) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
92
101
|
const wtPath = join(wtRoot, rel);
|
|
93
102
|
const invPath = join(invokeRoot, rel);
|
|
94
103
|
if (!existsSync(invPath)) {
|
|
@@ -107,7 +116,7 @@ export function compareTrees(
|
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
export function persistOutCheckUsage(): string {
|
|
110
|
-
return 'usage: persist-out-check --from <worktree> [--task-file <path>]… [--run-id <id>]… [--root <invoke-tree>]';
|
|
119
|
+
return 'usage: persist-out-check --from <worktree> [--task-file <path>]… [--run-id <id>]… [--root <invoke-tree>] [--success-json <path>]';
|
|
111
120
|
}
|
|
112
121
|
|
|
113
122
|
/**
|
|
@@ -129,6 +138,7 @@ export function defaultInvokeRoot(cwd: string): string {
|
|
|
129
138
|
|
|
130
139
|
export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd())): number {
|
|
131
140
|
let wtRoot = '';
|
|
141
|
+
let successJsonPath = '';
|
|
132
142
|
const wbsList: string[] = [];
|
|
133
143
|
const runIds: string[] = [];
|
|
134
144
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -145,25 +155,64 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
|
|
|
145
155
|
} else if (arg === '--run-id') {
|
|
146
156
|
const id = argv[++i] ?? '';
|
|
147
157
|
if (id) runIds.push(id);
|
|
158
|
+
} else if (arg === '--success-json') {
|
|
159
|
+
successJsonPath = argv[++i] ?? '';
|
|
148
160
|
} else {
|
|
149
161
|
process.stderr.write(`${persistOutCheckUsage()}\n`);
|
|
150
162
|
return 2;
|
|
151
163
|
}
|
|
152
164
|
}
|
|
165
|
+
if (wbsList.length === 0 && runIds.length === 0) {
|
|
166
|
+
process.stderr.write(
|
|
167
|
+
`${persistOutCheckUsage()}\npersist-out-check: at least one --task-file or --run-id prefix required\n`,
|
|
168
|
+
);
|
|
169
|
+
return 2;
|
|
170
|
+
}
|
|
153
171
|
if (!wtRoot || !existsSync(wtRoot) || !statSync(wtRoot).isDirectory()) {
|
|
154
172
|
process.stderr.write(
|
|
155
173
|
`${persistOutCheckUsage()}\npersist-out-check: --from must be an existing worktree directory\n`,
|
|
156
174
|
);
|
|
157
175
|
return 2;
|
|
158
176
|
}
|
|
159
|
-
const
|
|
160
|
-
|
|
177
|
+
const foreignDivergent = new Set<string>();
|
|
178
|
+
const skips: Array<{ id: string; reason: string }> = [];
|
|
179
|
+
const candidates = [
|
|
180
|
+
successJsonPath,
|
|
181
|
+
join(invokeRoot, '.spur', 'run', 'persist-out.json'),
|
|
182
|
+
join(wtRoot, '.spur', 'run', 'persist-out.json'),
|
|
183
|
+
].filter(Boolean);
|
|
184
|
+
for (const cand of candidates) {
|
|
185
|
+
if (existsSync(cand)) {
|
|
186
|
+
try {
|
|
187
|
+
const parsed = JSON.parse(readFileSync(cand, 'utf8'));
|
|
188
|
+
if (Array.isArray(parsed.evidenceSkipped)) {
|
|
189
|
+
for (const item of parsed.evidenceSkipped) {
|
|
190
|
+
if (item.reason === 'foreign-divergent' && typeof item.name === 'string') {
|
|
191
|
+
foreignDivergent.add(item.name);
|
|
192
|
+
foreignDivergent.add(join(EVIDENCE_DIR, item.name));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (Array.isArray(parsed.skipped)) {
|
|
197
|
+
for (const item of parsed.skipped) {
|
|
198
|
+
if (typeof item.id === 'string' && typeof item.reason === 'string') {
|
|
199
|
+
skips.push({ id: item.id, reason: item.reason });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
} catch {}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const obligations = listObligations(wtRoot, wbsList, runIds);
|
|
208
|
+
if (obligations === null) {
|
|
161
209
|
process.stderr.write(
|
|
162
|
-
`persist-out-check: BLOCKED — worktree evidence listing failed or exceeded caps (
|
|
210
|
+
`persist-out-check: BLOCKED — worktree evidence listing failed or exceeded caps (${PREFIX_CAP}/prefix) — inspect by hand\n`,
|
|
163
211
|
);
|
|
164
212
|
return 1;
|
|
165
213
|
}
|
|
166
|
-
const {
|
|
214
|
+
const { files, unowned } = obligations;
|
|
215
|
+
const { missing, divergent, ok } = compareTrees(wtRoot, invokeRoot, files, foreignDivergent);
|
|
167
216
|
const findings = [
|
|
168
217
|
...missing.map((f) => `MISSING ${f}`),
|
|
169
218
|
...divergent.map((f) => `DIVERGENT ${f} — reconcile by hand (persist-out never overwrites)`),
|
|
@@ -171,7 +220,13 @@ export function main(argv: string[], invokeRoot = defaultInvokeRoot(process.cwd(
|
|
|
171
220
|
for (const line of findings.slice(0, NAMED_CAP)) process.stdout.write(`${line}\n`);
|
|
172
221
|
if (findings.length > NAMED_CAP) process.stdout.write(`… +${findings.length - NAMED_CAP} more\n`);
|
|
173
222
|
if (findings.length === 0) {
|
|
174
|
-
|
|
223
|
+
for (const skip of skips) {
|
|
224
|
+
process.stdout.write(`SKIP ${skip.id}: ${skip.reason}\n`);
|
|
225
|
+
}
|
|
226
|
+
const abandonedMsg = skips.length > 0 ? `${skips.length} skipped` : 'nothing abandoned';
|
|
227
|
+
process.stdout.write(
|
|
228
|
+
`persist-out-check: ok — ${ok} evidence file(s) persisted, unowned: ${unowned}, ${abandonedMsg}\n`,
|
|
229
|
+
);
|
|
175
230
|
return 0;
|
|
176
231
|
}
|
|
177
232
|
process.stderr.write(
|