@gobing-ai/spur 0.4.0 → 0.4.2
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 +4 -2
- 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/plugin-scripts.json +15 -0
- package/config/rules/docs/sourced-api-claims.yaml +60 -0
- package/config/rules/recommended-pre-check.yaml +8 -3
- package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
- package/config/script-placement-baseline.json +2 -2
- package/config/templates/AGENTS.md +1 -1
- 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 +216 -57
- package/config/workflows/wrapup-pipeline.yaml +107 -14
- package/package.json +10 -10
- package/plugins/sp/README.md +12 -3
- package/plugins/sp/agents/super-planner.md +16 -6
- package/plugins/sp/agents/super-reviewer.md +14 -0
- package/plugins/sp/bunfig.toml +8 -0
- package/plugins/sp/commands/dev-brainstorm.md +29 -16
- package/plugins/sp/commands/dev-find-way.md +80 -0
- package/plugins/sp/commands/dev-idea.md +6 -0
- package/plugins/sp/commands/dev-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +4 -1
- package/plugins/sp/commands/dev-run.md +12 -2
- package/plugins/sp/commands/dev-runall.md +32 -15
- package/plugins/sp/hooks/context-owner.ts +56 -0
- package/plugins/sp/hooks/context-post-tool.ts +23 -0
- package/plugins/sp/hooks/context-session-start.ts +49 -27
- package/plugins/sp/hooks/context-session-stop.ts +131 -30
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
- package/plugins/sp/lib/idea-handoff.generated.mjs +43 -5
- package/plugins/sp/lib/inline-run-app.ts +28 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
- package/plugins/sp/lib/inline-run.generated.mjs +351 -46
- package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
- 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 +150 -7
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +488 -0
- package/plugins/sp/scripts/batch-plan.ts +145 -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/inline-run-dispatch.mjs +130 -0
- package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
- package/plugins/sp/scripts/inline-run-setup.mjs +562 -80
- package/plugins/sp/scripts/inline-run-setup.ts +52 -78
- package/plugins/sp/scripts/persist-out-check.mjs +90 -41
- package/plugins/sp/scripts/persist-out-check.ts +112 -57
- package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +30213 -61
- package/plugins/sp/scripts/quality-gate.ts +21 -5
- package/plugins/sp/scripts/residual-scan.mjs +86 -31
- package/plugins/sp/scripts/residual-scan.ts +28 -30
- package/plugins/sp/scripts/run-summary.mjs +432 -14
- package/plugins/sp/scripts/run-summary.ts +41 -177
- package/plugins/sp/scripts/session-timeline.mjs +109 -11
- package/plugins/sp/scripts/session-timeline.ts +57 -19
- package/plugins/sp/scripts/task-diffstat.mjs +32 -29
- package/plugins/sp/scripts/task-diffstat.ts +34 -34
- 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 +3 -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 +1 -1
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +40 -7
- 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 +5 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
- package/plugins/sp/skills/spur-cli/references/decision.md +16 -8
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -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 +1 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +20 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -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 +44 -12
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
- package/schemas/state-machine-workflow.schema.json +23 -0
- package/spur.js +42402 -26889
- package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
- package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
- package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
- package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
- package/web/_astro/channel.D4nG9YLf.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
- package/web/_astro/index.BhSwN8yY.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
- package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
- package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/commands/dev-parallel.md +0 -31
- package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
- package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
- package/web/_astro/channel.CUFdtWhm.js +0 -1
- package/web/_astro/index.Dg_3bOaJ.css +0 -1
|
@@ -9,6 +9,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
// Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
|
|
10
10
|
import type * as spurApp from '@gobing-ai/spur-app';
|
|
11
11
|
import { getEnvVar } from '../lib/env';
|
|
12
|
+
import { loadInlineApp } from '../lib/inline-run-app';
|
|
13
|
+
import { writeCloseSummary } from './run-summary';
|
|
12
14
|
|
|
13
15
|
// EMBEDDED_SPUR_SCHEMAS: declared by the bundle twin, never exported by the barrel — optional
|
|
14
16
|
// intersection member, exactly what the previous structural cast expressed.
|
|
@@ -18,9 +20,12 @@ function usage(): never {
|
|
|
18
20
|
const text = [
|
|
19
21
|
'Usage: bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]',
|
|
20
22
|
' bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]',
|
|
21
|
-
' 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> [--estimated] [--spur-bin <path>]',
|
|
22
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --
|
|
23
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --
|
|
23
|
+
' 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>] [--estimated] [--project-root <path>] [--spur-bin <path>]',
|
|
24
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --node-enter --run-id <id> --node <state> [--project-root <path>] [--spur-bin <path>] (1136 R4: stamps the node enter time)',
|
|
25
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --actions-file <json-file> --run-id <id> [--project-root <path>] [--spur-bin <path>] (1007 R5 batch trace emission)',
|
|
26
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--no-summary] [--project-root <path>] [--spur-bin <path>]',
|
|
27
|
+
' --project-root (1136 R1) names the tree that OWNS the run row; without it the run row is read from the cwd tree, and a miss exits 1 with RUN_NOT_FOUND',
|
|
28
|
+
' --duration-ms is optional on --action (1136 R4): omitted, the duration is measured from the calling --node-enter; supplied, it records provenance host-reported',
|
|
24
29
|
' bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]',
|
|
25
30
|
' terminal-reason is a closed enum (0937 R2): done, paused-operator, failed-check, failed-agent, failed-timeout, failed-guard, cancelled, interrupted, retry-exhausted',
|
|
26
31
|
' close defaults (1051 AC1): --status done → reason done, --status paused → reason paused-operator; --status failed requires an explicit --reason',
|
|
@@ -61,19 +66,7 @@ function resolveAppEntry(spurBin: string): { entry: string; portable: boolean }
|
|
|
61
66
|
return { entry, portable: true };
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
// packages/app/src/workflow/terminal-reason.ts (no value import); parity test asserts equality.
|
|
66
|
-
const TERMINAL_REASONS = new Set([
|
|
67
|
-
'done',
|
|
68
|
-
'paused-operator',
|
|
69
|
-
'failed-check',
|
|
70
|
-
'failed-agent',
|
|
71
|
-
'failed-timeout',
|
|
72
|
-
'failed-guard',
|
|
73
|
-
'cancelled',
|
|
74
|
-
'interrupted',
|
|
75
|
-
'retry-exhausted',
|
|
76
|
-
]);
|
|
69
|
+
const SETUP_EXPORTS = ['readInstalledInventory', 'runInlineRunSetup', 'writeInlineRunOutcome'] as const;
|
|
77
70
|
|
|
78
71
|
async function main(): Promise<void> {
|
|
79
72
|
// The portable Node twin has no workspace imports; SQLite still uses Spur's existing Bun runtime.
|
|
@@ -90,7 +83,9 @@ async function main(): Promise<void> {
|
|
|
90
83
|
close = false,
|
|
91
84
|
decide = false,
|
|
92
85
|
persistOut = false,
|
|
93
|
-
|
|
86
|
+
nodeEnter = false,
|
|
87
|
+
estimated = false,
|
|
88
|
+
noSummary = false;
|
|
94
89
|
const taskFiles: string[] = [];
|
|
95
90
|
let spurBin = getEnvVar('SPUR_BIN') ?? '';
|
|
96
91
|
const argv = process.argv.slice(2);
|
|
@@ -100,8 +95,10 @@ async function main(): Promise<void> {
|
|
|
100
95
|
else if (flag === '--action') action = true;
|
|
101
96
|
else if (flag === '--close') close = true;
|
|
102
97
|
else if (flag === '--decide') decide = true;
|
|
98
|
+
else if (flag === '--node-enter') nodeEnter = true;
|
|
103
99
|
else if (flag === '--persist-out') persistOut = true;
|
|
104
100
|
else if (flag === '--estimated') estimated = true;
|
|
101
|
+
else if (flag === '--no-summary') noSummary = true;
|
|
105
102
|
else if (flag === '--task-file') taskFiles.push(argv[++i] ?? '');
|
|
106
103
|
else if (flag === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
107
104
|
else if (flag !== undefined) flags.set(flag, argv[++i] ?? '');
|
|
@@ -114,6 +111,7 @@ async function main(): Promise<void> {
|
|
|
114
111
|
const optionsJson = flags.get('--options-json') ?? '';
|
|
115
112
|
const node = flags.get('--node') ?? '';
|
|
116
113
|
const kind = flags.get('--kind') ?? '';
|
|
114
|
+
const projectRoot = flags.get('--project-root') ?? '';
|
|
117
115
|
const status = flags.get('--status') ?? '';
|
|
118
116
|
const reason = flags.get('--reason') ?? '';
|
|
119
117
|
const okRaw = flags.get('--ok') ?? '';
|
|
@@ -123,37 +121,33 @@ async function main(): Promise<void> {
|
|
|
123
121
|
// `--estimated` labels a host-reported duration the driver did NOT time around the action
|
|
124
122
|
// (1070 R2). Only `--action` carries one: every other mode is refused here, before any write.
|
|
125
123
|
if (estimated && !action) usage();
|
|
124
|
+
// 1146 AC3: `--no-summary` belongs to the close step; any other mode is a usage error.
|
|
125
|
+
if (noSummary && !close) usage();
|
|
126
126
|
|
|
127
127
|
if (fingerprint) {
|
|
128
128
|
if (runId !== '' || file !== '' || taskFiles.length !== 1 || (taskFiles[0] ?? '').trim() === '') usage();
|
|
129
|
-
const app =
|
|
129
|
+
const { app } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunFingerprint']);
|
|
130
130
|
process.exit(await app.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? '', featureFile }));
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
if (actionsFile !== '') {
|
|
134
|
-
if (action || close || fingerprint || decide || persistOut || file !== '' || taskFiles.length > 0) usage();
|
|
135
|
-
if (runId.trim() === '' || status !== '' || node !== '' || kind !== '') usage();
|
|
136
|
-
if (okRaw !== '' || durationRaw !== '') usage();
|
|
137
|
-
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
138
|
-
const app = (await import(resolveAppEntry(spurBin).entry)) as InlineApp;
|
|
139
|
-
process.exit(await app.runInlineRunTraceBatch({ runId, actionsFile }));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
133
|
if (decide) {
|
|
143
134
|
if (action || close || fingerprint || file !== '' || taskFiles.length > 0) usage();
|
|
144
135
|
if (runId.trim() === '' || node.trim() === '' || optionsJson.trim() === '') usage();
|
|
145
136
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
146
137
|
try {
|
|
147
|
-
const {
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
process.cwd(),
|
|
154
|
-
portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined,
|
|
138
|
+
const { app, portable } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunDecide']);
|
|
139
|
+
// Decide switch + catalog config, resolved once (1094, ADR-082), behind the skew guard (1120 R2).
|
|
140
|
+
const { app: lib } = await loadInlineApp<typeof import('../lib/inline-run.generated.mjs')>(
|
|
141
|
+
spurBin,
|
|
142
|
+
() => ({ entry: fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url)), portable }),
|
|
143
|
+
['resolveDecideDecisionMakerEnabled', 'loadSpurConfig'],
|
|
155
144
|
);
|
|
156
|
-
|
|
145
|
+
const loadOpts = portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined;
|
|
146
|
+
const [enabled, spurConfig] = await Promise.all([
|
|
147
|
+
lib.resolveDecideDecisionMakerEnabled(process.cwd(), loadOpts),
|
|
148
|
+
lib.loadSpurConfig(process.cwd(), loadOpts),
|
|
149
|
+
]);
|
|
150
|
+
process.exit(await app.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled, spurConfig }));
|
|
157
151
|
} catch (error) {
|
|
158
152
|
const message = error instanceof Error ? error.message : String(error);
|
|
159
153
|
process.stdout.write(`${JSON.stringify({ ok: false, runId, error: message })}\n`);
|
|
@@ -164,56 +158,36 @@ async function main(): Promise<void> {
|
|
|
164
158
|
if (persistOut) {
|
|
165
159
|
if (fingerprint || decide || action || close || runId !== '' || file !== '') usage();
|
|
166
160
|
if (from.trim() === '' || taskFiles.some((taskFile) => taskFile.trim() === '')) usage();
|
|
167
|
-
const app =
|
|
161
|
+
const { app } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunPersistOut']);
|
|
168
162
|
process.exit(await app.runInlineRunPersistOut({ from, taskFiles }));
|
|
169
163
|
}
|
|
170
164
|
|
|
171
|
-
if (action || close) {
|
|
172
|
-
if (action && close) usage();
|
|
173
|
-
if (runId.trim() === '' || status.trim() === '') usage();
|
|
165
|
+
if (nodeEnter || actionsFile !== '' || action || close) {
|
|
174
166
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (!app.isInlineRunActionStatus(status)) usage();
|
|
195
|
-
// `--ok` and `--duration-ms` are required and exact (0868 #2): no silent defaults.
|
|
196
|
-
if (okRaw !== 'true' && okRaw !== 'false') usage();
|
|
197
|
-
const durationMs = Number(durationRaw);
|
|
198
|
-
if (durationRaw.trim() === '' || !Number.isFinite(durationMs) || durationMs < 0) usage();
|
|
199
|
-
process.exit(
|
|
200
|
-
await app.runInlineRunTrace({
|
|
201
|
-
runId,
|
|
202
|
-
close: false,
|
|
203
|
-
node,
|
|
204
|
-
kind,
|
|
205
|
-
status,
|
|
206
|
-
ok: okRaw === 'true',
|
|
207
|
-
durationMs,
|
|
208
|
-
...(estimated ? { estimated: true } : {}),
|
|
209
|
-
}),
|
|
210
|
-
);
|
|
167
|
+
const mode = nodeEnter ? 'node-enter' : actionsFile !== '' ? 'actions-file' : close ? 'close' : 'action';
|
|
168
|
+
const { app } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunTraceMode']);
|
|
169
|
+
const code = await app.runInlineRunTraceMode({
|
|
170
|
+
mode,
|
|
171
|
+
runId,
|
|
172
|
+
node,
|
|
173
|
+
kind,
|
|
174
|
+
status,
|
|
175
|
+
reason,
|
|
176
|
+
ok: okRaw,
|
|
177
|
+
durationMs: durationRaw,
|
|
178
|
+
actionsFile,
|
|
179
|
+
projectRoot,
|
|
180
|
+
estimated,
|
|
181
|
+
noSummary,
|
|
182
|
+
...(noSummary ? {} : { summarize: async (run) => writeCloseSummary(run) }),
|
|
183
|
+
});
|
|
184
|
+
if (code === 2) usage();
|
|
185
|
+
process.exit(code);
|
|
211
186
|
}
|
|
212
187
|
|
|
213
188
|
if (runId.trim() === '' || file.trim() === '') usage();
|
|
214
189
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
215
|
-
const {
|
|
216
|
-
const app = (await import(entry)) as InlineApp;
|
|
190
|
+
const { app, portable } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, SETUP_EXPORTS);
|
|
217
191
|
let inventory: unknown;
|
|
218
192
|
try {
|
|
219
193
|
// The CLI walk lives in the app service (ADR-130 glue budget): the script passes only the
|
|
@@ -9,60 +9,64 @@ var EVIDENCE_DIR = join(".spur", "memory", "evidence");
|
|
|
9
9
|
var RUN_DIR = join(".spur", "run");
|
|
10
10
|
var NAMED_CAP = 32;
|
|
11
11
|
var PREFIX_CAP = 64;
|
|
12
|
-
var EVIDENCE_CAP = 256;
|
|
13
12
|
function wbsOfTaskFile(path) {
|
|
14
13
|
const m = /^(\d+)_/.exec(basename(path));
|
|
15
14
|
return m?.[1] ?? null;
|
|
16
15
|
}
|
|
17
|
-
function
|
|
18
|
-
const dir = join(root, rel);
|
|
16
|
+
function walkFiles(dir, root) {
|
|
19
17
|
if (!existsSync(dir))
|
|
20
18
|
return [];
|
|
21
19
|
const out = [];
|
|
22
20
|
const walk = (cur) => {
|
|
23
|
-
for (const
|
|
24
|
-
const p = join(cur,
|
|
25
|
-
if (
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} else {
|
|
21
|
+
for (const e of readdirSync(cur, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
22
|
+
const p = join(cur, e.name);
|
|
23
|
+
if (e.isDirectory())
|
|
24
|
+
walk(p);
|
|
25
|
+
else if (e.isFile())
|
|
29
26
|
out.push(relative(root, p));
|
|
30
|
-
if (out.length > cap)
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
27
|
}
|
|
34
|
-
return true;
|
|
35
28
|
};
|
|
36
|
-
|
|
29
|
+
walk(dir);
|
|
30
|
+
return out;
|
|
37
31
|
}
|
|
38
|
-
function
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const out = [...evidence];
|
|
43
|
-
const prefixes = [...wbsList, ...runIds];
|
|
44
|
-
for (const prefix of prefixes) {
|
|
45
|
-
const dir = join(wtRoot, RUN_DIR);
|
|
46
|
-
if (!existsSync(dir))
|
|
32
|
+
function collectOwned(rels, prefixes, counts, out) {
|
|
33
|
+
for (const rel of rels) {
|
|
34
|
+
const prefix = prefixes.find((p) => basename(rel).startsWith(`${p}-`));
|
|
35
|
+
if (prefix === undefined)
|
|
47
36
|
continue;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
const owned = entries.filter((n) => n.startsWith(`${prefix}-`));
|
|
55
|
-
if (owned.length > PREFIX_CAP)
|
|
56
|
-
return null;
|
|
57
|
-
out.push(...owned.map((n) => join(RUN_DIR, n)));
|
|
37
|
+
const count = (counts.get(prefix) ?? 0) + 1;
|
|
38
|
+
if (count > PREFIX_CAP)
|
|
39
|
+
return false;
|
|
40
|
+
counts.set(prefix, count);
|
|
41
|
+
out.push(rel);
|
|
58
42
|
}
|
|
59
|
-
return
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function listObligations(wtRoot, wbsList, runIds) {
|
|
46
|
+
const prefixes = [...new Set([...wbsList, ...runIds].filter(Boolean))];
|
|
47
|
+
if (prefixes.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
const evDir = join(wtRoot, EVIDENCE_DIR);
|
|
50
|
+
const runDir = join(wtRoot, RUN_DIR);
|
|
51
|
+
const evFiles = walkFiles(evDir, wtRoot);
|
|
52
|
+
const runFiles = existsSync(runDir) ? readdirSync(runDir, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => join(RUN_DIR, e.name)).sort() : [];
|
|
53
|
+
const files = [];
|
|
54
|
+
const counts = new Map;
|
|
55
|
+
if (!collectOwned(evFiles, prefixes, counts, files))
|
|
56
|
+
return null;
|
|
57
|
+
if (!collectOwned(runFiles, prefixes, counts, files))
|
|
58
|
+
return null;
|
|
59
|
+
const unowned = evFiles.length - files.filter((f) => f.startsWith(EVIDENCE_DIR)).length;
|
|
60
|
+
return { files: [...new Set(files)].sort(), unowned };
|
|
60
61
|
}
|
|
61
|
-
function compareTrees(wtRoot, invokeRoot, files) {
|
|
62
|
+
function compareTrees(wtRoot, invokeRoot, files, foreignDivergent = new Set) {
|
|
62
63
|
const missing = [];
|
|
63
64
|
const divergent = [];
|
|
64
65
|
let ok = 0;
|
|
65
66
|
for (const rel of files) {
|
|
67
|
+
if (foreignDivergent.has(rel) || foreignDivergent.has(basename(rel))) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
66
70
|
const wtPath = join(wtRoot, rel);
|
|
67
71
|
const invPath = join(invokeRoot, rel);
|
|
68
72
|
if (!existsSync(invPath)) {
|
|
@@ -82,7 +86,7 @@ function compareTrees(wtRoot, invokeRoot, files) {
|
|
|
82
86
|
return { missing, divergent, ok };
|
|
83
87
|
}
|
|
84
88
|
function persistOutCheckUsage() {
|
|
85
|
-
return "usage: persist-out-check --from <worktree> [--task-file <path>]\u2026 [--run-id <id>]\u2026 [--root <invoke-tree>]";
|
|
89
|
+
return "usage: persist-out-check --from <worktree> [--task-file <path>]\u2026 [--run-id <id>]\u2026 [--root <invoke-tree>] [--success-json <path>]";
|
|
86
90
|
}
|
|
87
91
|
function defaultInvokeRoot(cwd) {
|
|
88
92
|
try {
|
|
@@ -95,6 +99,7 @@ function defaultInvokeRoot(cwd) {
|
|
|
95
99
|
}
|
|
96
100
|
function main(argv, invokeRoot = defaultInvokeRoot(process.cwd())) {
|
|
97
101
|
let wtRoot = "";
|
|
102
|
+
let successJsonPath = "";
|
|
98
103
|
const wbsList = [];
|
|
99
104
|
const runIds = [];
|
|
100
105
|
for (let i = 0;i < argv.length; i++) {
|
|
@@ -113,25 +118,64 @@ function main(argv, invokeRoot = defaultInvokeRoot(process.cwd())) {
|
|
|
113
118
|
const id = argv[++i] ?? "";
|
|
114
119
|
if (id)
|
|
115
120
|
runIds.push(id);
|
|
121
|
+
} else if (arg === "--success-json") {
|
|
122
|
+
successJsonPath = argv[++i] ?? "";
|
|
116
123
|
} else {
|
|
117
124
|
process.stderr.write(`${persistOutCheckUsage()}
|
|
118
125
|
`);
|
|
119
126
|
return 2;
|
|
120
127
|
}
|
|
121
128
|
}
|
|
129
|
+
if (wbsList.length === 0 && runIds.length === 0) {
|
|
130
|
+
process.stderr.write(`${persistOutCheckUsage()}
|
|
131
|
+
persist-out-check: at least one --task-file or --run-id prefix required
|
|
132
|
+
`);
|
|
133
|
+
return 2;
|
|
134
|
+
}
|
|
122
135
|
if (!wtRoot || !existsSync(wtRoot) || !statSync(wtRoot).isDirectory()) {
|
|
123
136
|
process.stderr.write(`${persistOutCheckUsage()}
|
|
124
137
|
persist-out-check: --from must be an existing worktree directory
|
|
125
138
|
`);
|
|
126
139
|
return 2;
|
|
127
140
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
141
|
+
const foreignDivergent = new Set;
|
|
142
|
+
const skips = [];
|
|
143
|
+
const candidates = [
|
|
144
|
+
successJsonPath,
|
|
145
|
+
join(invokeRoot, ".spur", "run", "persist-out.json"),
|
|
146
|
+
join(wtRoot, ".spur", "run", "persist-out.json")
|
|
147
|
+
].filter(Boolean);
|
|
148
|
+
for (const cand of candidates) {
|
|
149
|
+
if (existsSync(cand)) {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(readFileSync(cand, "utf8"));
|
|
152
|
+
if (Array.isArray(parsed.evidenceSkipped)) {
|
|
153
|
+
for (const item of parsed.evidenceSkipped) {
|
|
154
|
+
if (item.reason === "foreign-divergent" && typeof item.name === "string") {
|
|
155
|
+
foreignDivergent.add(item.name);
|
|
156
|
+
foreignDivergent.add(join(EVIDENCE_DIR, item.name));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (Array.isArray(parsed.skipped)) {
|
|
161
|
+
for (const item of parsed.skipped) {
|
|
162
|
+
if (typeof item.id === "string" && typeof item.reason === "string") {
|
|
163
|
+
skips.push({ id: item.id, reason: item.reason });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const obligations = listObligations(wtRoot, wbsList, runIds);
|
|
172
|
+
if (obligations === null) {
|
|
173
|
+
process.stderr.write(`persist-out-check: BLOCKED \u2014 worktree evidence listing failed or exceeded caps (${PREFIX_CAP}/prefix) \u2014 inspect by hand
|
|
131
174
|
`);
|
|
132
175
|
return 1;
|
|
133
176
|
}
|
|
134
|
-
const {
|
|
177
|
+
const { files, unowned } = obligations;
|
|
178
|
+
const { missing, divergent, ok } = compareTrees(wtRoot, invokeRoot, files, foreignDivergent);
|
|
135
179
|
const findings = [
|
|
136
180
|
...missing.map((f) => `MISSING ${f}`),
|
|
137
181
|
...divergent.map((f) => `DIVERGENT ${f} \u2014 reconcile by hand (persist-out never overwrites)`)
|
|
@@ -143,7 +187,12 @@ persist-out-check: --from must be an existing worktree directory
|
|
|
143
187
|
process.stdout.write(`\u2026 +${findings.length - NAMED_CAP} more
|
|
144
188
|
`);
|
|
145
189
|
if (findings.length === 0) {
|
|
146
|
-
|
|
190
|
+
for (const skip of skips) {
|
|
191
|
+
process.stdout.write(`SKIP ${skip.id}: ${skip.reason}
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
const abandonedMsg = skips.length > 0 ? `${skips.length} skipped` : "nothing abandoned";
|
|
195
|
+
process.stdout.write(`persist-out-check: ok \u2014 ${ok} evidence file(s) persisted, unowned: ${unowned}, ${abandonedMsg}
|
|
147
196
|
`);
|
|
148
197
|
return 0;
|
|
149
198
|
}
|