@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
|
@@ -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> [--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,46 +66,7 @@ function resolveAppEntry(spurBin: string): { entry: string; portable: boolean }
|
|
|
61
66
|
return { entry, portable: true };
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
async function readInstalledInventory(file: string, spurBin: string): Promise<unknown> {
|
|
66
|
-
const localCli = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
67
|
-
const bundle = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
|
|
68
|
-
const { splitLaunchCommand } = (await import(bundle)) as typeof import('../lib/inline-run.generated.mjs');
|
|
69
|
-
const launch = spurBin
|
|
70
|
-
? splitLaunchCommand(spurBin, 'inline-run-setup "spurBin"')
|
|
71
|
-
: existsSync(localCli)
|
|
72
|
-
? { command: 'bun', leadingArgs: [localCli] }
|
|
73
|
-
: { command: 'spur', leadingArgs: [] };
|
|
74
|
-
if ('error' in launch) throw new Error(launch.error);
|
|
75
|
-
const result = spawnSync(
|
|
76
|
-
launch.command,
|
|
77
|
-
[...launch.leadingArgs, 'workflow', 'show', file, '--format', 'todo', '--json'],
|
|
78
|
-
{ cwd: process.cwd(), encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 },
|
|
79
|
-
);
|
|
80
|
-
if (result.status !== 0) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`could not resolve the workflow definition with the installed CLI: ${result.error?.message ?? result.stderr}`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
const value: unknown = JSON.parse(result.stdout);
|
|
86
|
-
// Honor the existing optional JSON envelope without inventing another projection format.
|
|
87
|
-
if (value && typeof value === 'object' && 'ok' in value && 'data' in value && value.ok === true) return value.data;
|
|
88
|
-
return value;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 0937 R2 closed terminal-reason vocabulary for `--close`. COPIED from
|
|
92
|
-
// packages/app/src/workflow/terminal-reason.ts (no value import); parity test asserts equality.
|
|
93
|
-
const TERMINAL_REASONS = new Set([
|
|
94
|
-
'done',
|
|
95
|
-
'paused-operator',
|
|
96
|
-
'failed-check',
|
|
97
|
-
'failed-agent',
|
|
98
|
-
'failed-timeout',
|
|
99
|
-
'failed-guard',
|
|
100
|
-
'cancelled',
|
|
101
|
-
'interrupted',
|
|
102
|
-
'retry-exhausted',
|
|
103
|
-
]);
|
|
69
|
+
const SETUP_EXPORTS = ['readInstalledInventory', 'runInlineRunSetup', 'writeInlineRunOutcome'] as const;
|
|
104
70
|
|
|
105
71
|
async function main(): Promise<void> {
|
|
106
72
|
// The portable Node twin has no workspace imports; SQLite still uses Spur's existing Bun runtime.
|
|
@@ -116,7 +82,10 @@ async function main(): Promise<void> {
|
|
|
116
82
|
action = false,
|
|
117
83
|
close = false,
|
|
118
84
|
decide = false,
|
|
119
|
-
persistOut = false
|
|
85
|
+
persistOut = false,
|
|
86
|
+
nodeEnter = false,
|
|
87
|
+
estimated = false,
|
|
88
|
+
noSummary = false;
|
|
120
89
|
const taskFiles: string[] = [];
|
|
121
90
|
let spurBin = getEnvVar('SPUR_BIN') ?? '';
|
|
122
91
|
const argv = process.argv.slice(2);
|
|
@@ -126,7 +95,10 @@ async function main(): Promise<void> {
|
|
|
126
95
|
else if (flag === '--action') action = true;
|
|
127
96
|
else if (flag === '--close') close = true;
|
|
128
97
|
else if (flag === '--decide') decide = true;
|
|
98
|
+
else if (flag === '--node-enter') nodeEnter = true;
|
|
129
99
|
else if (flag === '--persist-out') persistOut = true;
|
|
100
|
+
else if (flag === '--estimated') estimated = true;
|
|
101
|
+
else if (flag === '--no-summary') noSummary = true;
|
|
130
102
|
else if (flag === '--task-file') taskFiles.push(argv[++i] ?? '');
|
|
131
103
|
else if (flag === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
132
104
|
else if (flag !== undefined) flags.set(flag, argv[++i] ?? '');
|
|
@@ -139,42 +111,43 @@ async function main(): Promise<void> {
|
|
|
139
111
|
const optionsJson = flags.get('--options-json') ?? '';
|
|
140
112
|
const node = flags.get('--node') ?? '';
|
|
141
113
|
const kind = flags.get('--kind') ?? '';
|
|
114
|
+
const projectRoot = flags.get('--project-root') ?? '';
|
|
142
115
|
const status = flags.get('--status') ?? '';
|
|
143
116
|
const reason = flags.get('--reason') ?? '';
|
|
144
117
|
const okRaw = flags.get('--ok') ?? '';
|
|
145
118
|
const durationRaw = flags.get('--duration-ms') ?? '';
|
|
146
119
|
const actionsFile = flags.get('--actions-file') ?? '';
|
|
147
120
|
|
|
121
|
+
// `--estimated` labels a host-reported duration the driver did NOT time around the action
|
|
122
|
+
// (1070 R2). Only `--action` carries one: every other mode is refused here, before any write.
|
|
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
|
+
|
|
148
127
|
if (fingerprint) {
|
|
149
128
|
if (runId !== '' || file !== '' || taskFiles.length !== 1 || (taskFiles[0] ?? '').trim() === '') usage();
|
|
150
|
-
const app =
|
|
129
|
+
const { app } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunFingerprint']);
|
|
151
130
|
process.exit(await app.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? '', featureFile }));
|
|
152
131
|
}
|
|
153
132
|
|
|
154
|
-
if (actionsFile !== '') {
|
|
155
|
-
if (action || close || fingerprint || decide || persistOut || file !== '' || taskFiles.length > 0) usage();
|
|
156
|
-
if (runId.trim() === '' || status !== '' || node !== '' || kind !== '') usage();
|
|
157
|
-
if (okRaw !== '' || durationRaw !== '') usage();
|
|
158
|
-
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
159
|
-
const app = (await import(resolveAppEntry(spurBin).entry)) as InlineApp;
|
|
160
|
-
process.exit(await app.runInlineRunTraceBatch({ runId, actionsFile }));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
133
|
if (decide) {
|
|
164
134
|
if (action || close || fingerprint || file !== '' || taskFiles.length > 0) usage();
|
|
165
135
|
if (runId.trim() === '' || node.trim() === '' || optionsJson.trim() === '') usage();
|
|
166
136
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
167
137
|
try {
|
|
168
|
-
const {
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
process.cwd(),
|
|
175
|
-
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'],
|
|
176
144
|
);
|
|
177
|
-
|
|
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 }));
|
|
178
151
|
} catch (error) {
|
|
179
152
|
const message = error instanceof Error ? error.message : String(error);
|
|
180
153
|
process.stdout.write(`${JSON.stringify({ ok: false, runId, error: message })}\n`);
|
|
@@ -185,50 +158,45 @@ async function main(): Promise<void> {
|
|
|
185
158
|
if (persistOut) {
|
|
186
159
|
if (fingerprint || decide || action || close || runId !== '' || file !== '') usage();
|
|
187
160
|
if (from.trim() === '' || taskFiles.some((taskFile) => taskFile.trim() === '')) usage();
|
|
188
|
-
const app =
|
|
161
|
+
const { app } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, ['runInlineRunPersistOut']);
|
|
189
162
|
process.exit(await app.runInlineRunPersistOut({ from, taskFiles }));
|
|
190
163
|
}
|
|
191
164
|
|
|
192
|
-
if (action || close) {
|
|
193
|
-
if (action && close) usage();
|
|
194
|
-
if (runId.trim() === '' || status.trim() === '') usage();
|
|
165
|
+
if (nodeEnter || actionsFile !== '' || action || close) {
|
|
195
166
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (!app.isInlineRunActionStatus(status)) usage();
|
|
216
|
-
// `--ok` and `--duration-ms` are required and exact (0868 #2): no silent defaults.
|
|
217
|
-
if (okRaw !== 'true' && okRaw !== 'false') usage();
|
|
218
|
-
const durationMs = Number(durationRaw);
|
|
219
|
-
if (durationRaw.trim() === '' || !Number.isFinite(durationMs) || durationMs < 0) usage();
|
|
220
|
-
process.exit(
|
|
221
|
-
await app.runInlineRunTrace({ runId, close: false, node, kind, status, ok: okRaw === 'true', durationMs }),
|
|
222
|
-
);
|
|
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);
|
|
223
186
|
}
|
|
224
187
|
|
|
225
188
|
if (runId.trim() === '' || file.trim() === '') usage();
|
|
226
189
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
227
|
-
const {
|
|
228
|
-
const app = (await import(entry)) as InlineApp;
|
|
190
|
+
const { app, portable } = await loadInlineApp<InlineApp>(spurBin, resolveAppEntry, SETUP_EXPORTS);
|
|
229
191
|
let inventory: unknown;
|
|
230
192
|
try {
|
|
231
|
-
|
|
193
|
+
// The CLI walk lives in the app service (ADR-130 glue budget): the script passes only the
|
|
194
|
+
// paths it derives from its own module URL.
|
|
195
|
+
inventory = await app.readInstalledInventory({
|
|
196
|
+
file,
|
|
197
|
+
spurBin,
|
|
198
|
+
localCli: fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url)),
|
|
199
|
+
});
|
|
232
200
|
} catch (error) {
|
|
233
201
|
const message = `could not resolve the workflow definition: ${error instanceof Error ? error.message : String(error)}`;
|
|
234
202
|
app.writeInlineRunOutcome(runId, { ok: false, runId, error: message });
|
|
@@ -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
|
}
|