@gobing-ai/spur 0.3.91 → 0.3.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +2 -9
- package/config/plugin-scripts.json +27 -1
- package/config/templates/AGENTS.md +4 -0
- package/config/templates/docs/02_ROADMAP.md +2 -0
- package/config/templates/docs/03_ARCHITECTURE.md +3 -1
- package/config/templates/docs/04_DESIGN.md +2 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
- package/config/workflow-candidates.json +72 -1
- package/config/workflows/feature-lifecycle.yaml +14 -5
- package/config/workflows/feature-verification.yaml +36 -27
- package/config/workflows/history-anatomy.yaml +16 -25
- package/config/workflows/idea-pipeline.yaml +76 -23
- package/config/workflows/pr-review.yaml +8 -0
- package/config/workflows/task-pipeline.yaml +362 -40
- package/config/workflows/wayfinder-resolution.yaml +5 -0
- package/config/workflows/wrapup-pipeline.yaml +102 -19
- package/package.json +9 -9
- package/plugins/sp/README.md +7 -2
- package/plugins/sp/agents/super-planner.md +14 -5
- package/plugins/sp/commands/dev-dogfood.md +4 -4
- package/plugins/sp/commands/dev-fixall.md +8 -5
- package/plugins/sp/commands/dev-run.md +8 -2
- package/plugins/sp/commands/dev-runall.md +14 -8
- package/plugins/sp/commands/dev-verify.md +9 -0
- package/plugins/sp/commands/dev-verifyall.md +5 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +306 -301
- package/plugins/sp/lib/inline-run.generated.d.mts +18 -0
- package/plugins/sp/lib/inline-run.generated.mjs +1468 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/environment-lens.md +1 -1
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +136 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +196 -2
- package/plugins/sp/scripts/feature-verification-steps.mjs +174 -0
- package/plugins/sp/scripts/feature-verification-steps.ts +275 -0
- package/plugins/sp/scripts/history-anatomy-cache.mjs +104 -4
- package/plugins/sp/scripts/history-anatomy-cache.ts +137 -13
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +2 -1
- package/plugins/sp/scripts/inline-run-setup.mjs +421 -0
- package/plugins/sp/scripts/inline-run-setup.ts +325 -78
- package/plugins/sp/scripts/quality-gate.mjs +248 -6
- package/plugins/sp/scripts/quality-gate.ts +410 -9
- package/plugins/sp/scripts/record-feature-sync.mjs +63 -0
- package/plugins/sp/scripts/record-feature-sync.ts +84 -0
- package/plugins/sp/scripts/residual-scan.mjs +484 -0
- package/plugins/sp/scripts/residual-scan.ts +640 -0
- package/plugins/sp/scripts/task-diffstat.mjs +156 -0
- package/plugins/sp/scripts/task-diffstat.ts +229 -0
- package/plugins/sp/scripts/task-evidence-precheck.ts +8 -3
- package/plugins/sp/scripts/task-size-precheck.ts +8 -3
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
- package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
- package/plugins/sp/scripts/wrapup-steps.ts +89 -4
- package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
- package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
- package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
- package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +2 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +17 -0
- package/plugins/sp/skills/code-verification/SKILL.md +21 -0
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +1 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +5 -3
- package/plugins/sp/skills/dogfood-testing/references/monitor-ledger.md +63 -26
- package/plugins/sp/skills/dogfood-testing/references/report-template.md +33 -10
- package/plugins/sp/skills/history-anatomy/references/modes.md +5 -3
- package/plugins/sp/skills/next-feature/references/ranking-rubric.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +7 -0
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/session-review/SKILL.md +12 -2
- package/plugins/sp/skills/spur-check/SKILL.md +112 -0
- package/plugins/sp/skills/spur-cli/references/features.md +1 -1
- package/plugins/sp/skills/spur-cli/references/projects.md +3 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +39 -19
- package/plugins/sp/skills/spur-dev/SKILL.md +13 -5
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +18 -3
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -2
- package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +286 -58
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +1 -1
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +27 -4
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +61 -16
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +10 -5
- package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
- package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
- package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
- package/schemas/state-machine-workflow.schema.json +4 -0
- package/spur.js +22501 -19644
- package/web/_astro/BoardApp.CerSBgis.js +192 -0
- package/web/_astro/BoardApp.eoTz0pZs.js +1 -0
- package/web/_astro/{TaskDetail.CXGltuT_.js → TaskDetail.DCqiC-OZ.js} +1 -1
- package/web/_astro/arc.CPwg6Rw0.js +1 -0
- package/web/_astro/{architectureDiagram-3BPJPVTR.DJ8DHkWE.js → architectureDiagram-3BPJPVTR.DM_vp_hO.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.D8DHK3Jl.js → blockDiagram-GPEHLZMM.DXVIiv0p.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BugQbX9u.js → c4Diagram-AAUBKEIU.BbF_zCxW.js} +1 -1
- package/web/_astro/channel.MYZLKNwy.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.Mv26KlVn.js → chunk-2J33WTMH.CAgQHpPC.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.CD51JoT_.js → chunk-4BX2VUAB.BN-5tpw4.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.D_PFaIEe.js → chunk-55IACEB6.CnPkEEr0.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DemMW1ao.js → chunk-727SXJPM.BQzQeMVm.js} +4 -4
- package/web/_astro/{chunk-AQP2D5EJ.Iu2V5-ex.js → chunk-AQP2D5EJ.B6xNyDnL.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.MsNgSP-E.js → chunk-FMBD7UC4.C7f9Ih78.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.DfbRaAlm.js → chunk-ND2GUHAM.CNV1dFXT.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BbJAQ4h-.js → chunk-QZHKN3VN.Cudn2TkJ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CPurtiC2.js → classDiagram-4FO5ZUOK.D1NwP50q.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CPurtiC2.js → classDiagram-v2-Q7XG4LA2.D1NwP50q.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKKdx1bM.js → cose-bilkent-S5V4N54A.B1wSL-Xb.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.CoKMTg-R.js → cynefin-OW5HDTMX.BmK52w8G.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D4h4_k56.js → dagre-BM42HDAG.Bfy5CTDT.js} +2 -2
- package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +43 -0
- package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +10 -0
- package/web/_astro/{diagram-KO2AKTUF.BFoCkiCr.js → diagram-KO2AKTUF.CW_vMJ4z.js} +3 -3
- package/web/_astro/{diagram-LMA3HP47.exHn9OVx.js → diagram-LMA3HP47.B_8ZGF67.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.CeqO34nN.js → diagram-OG6HWLK6.BppnHsdS.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.D_v7HqxR.js → erDiagram-TEJ5UH35.BEuHXcjJ.js} +5 -5
- package/web/_astro/{flowDiagram-I6XJVG4X.EUmrpbwh.js → flowDiagram-I6XJVG4X.CH-UlnGr.js} +4 -4
- package/web/_astro/{ganttDiagram-6RSMTGT7.BOCF5lII.js → ganttDiagram-6RSMTGT7.BO81S85v.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Di7otYZD.js → gitGraphDiagram-PVQCEYII.XnPxPPZN.js} +1 -1
- package/web/_astro/index.Hjbr15fG.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.TcBkCAJk.js → infoDiagram-5YYISTIA.JyjYRu_T.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.D-2y4M0c.js → ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js} +5 -5
- package/web/_astro/{journeyDiagram-JHISSGLW.DPbJI_n2.js → journeyDiagram-JHISSGLW.C_iymSyp.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.EFxhQ9Fj.js → kanban-definition-UN3LZRKU.DdfW-Oqt.js} +7 -7
- package/web/_astro/{linear.DSAsQLzs.js → linear.C2_IkbZT.js} +1 -1
- package/web/_astro/mermaid.core.GAOYeSR0.js +303 -0
- package/web/_astro/{mindmap-definition-RKZ34NQL.CJY1N_7V.js → mindmap-definition-RKZ34NQL.DAZIxQSK.js} +2 -2
- package/web/_astro/{pieDiagram-4H26LBE5.567ZNoL2.js → pieDiagram-4H26LBE5.CN8sIhKM.js} +3 -3
- package/web/_astro/{quadrantDiagram-W4KKPZXB.mqfz9-MY.js → quadrantDiagram-W4KKPZXB.3dGcX5GP.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.Bv1Gv9In.js → requirementDiagram-4Y6WPE33.BV2y4dd6.js} +3 -3
- package/web/_astro/{sankeyDiagram-5OEKKPKP.B6Gs4X4r.js → sankeyDiagram-5OEKKPKP.Cqo15Tvo.js} +4 -4
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BhYj4v-m.js → sequenceDiagram-3UESZ5HK.CROCPMJB.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BPbBnkpw.js → stateDiagram-AJRCARHV.RfXZrkFE.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.C4squMNK.js → stateDiagram-v2-BHNVJYJU.CPXmbBs9.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.C_SwIHgl.js → timeline-definition-PNZ67QCA.DdgKTiO8.js} +3 -3
- package/web/_astro/{vennDiagram-CIIHVFJN.Bz4NZGpQ.js → vennDiagram-CIIHVFJN.CPNVSHF1.js} +5 -5
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CozMVZ3i.js → wardleyDiagram-YWT4CUSO.CQhA0Jyr.js} +3 -3
- package/web/_astro/{xychartDiagram-2RQKCTM6.BwMGBwjB.js → xychartDiagram-2RQKCTM6.n61BWyy4.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/decision-routing-example.yaml +0 -134
- package/web/_astro/BoardApp.BEDWpzsr.js +0 -188
- package/web/_astro/BoardApp.DQG2xfEz.js +0 -1
- package/web/_astro/arc.C0rrflm_.js +0 -1
- package/web/_astro/channel.SRrg1P-w.js +0 -1
- package/web/_astro/diagram-2AECGRRQ.DJ0h9zgw.js +0 -43
- package/web/_astro/diagram-5GNKFQAL.DvPk1jYd.js +0 -10
- package/web/_astro/index.Bx6GY4RH.css +0 -1
- package/web/_astro/mermaid.core.kAZjgJHG.js +0 -301
|
@@ -5,34 +5,35 @@
|
|
|
5
5
|
* Unlike the subprocess path (`spur workflow run`), the interactive inline driver
|
|
6
6
|
* allocated a run id but never persisted an authoritative `runs` row, so bound
|
|
7
7
|
* `run.artifact` registration (0785 R3) correctly refused every inline record. This
|
|
8
|
-
* script is the thin delegate the driver now runs at Run setup: it
|
|
9
|
-
*
|
|
8
|
+
* script is the thin delegate the driver now runs at Run setup: it loads the source
|
|
9
|
+
* app service when the SPUR_BIN chain identifies a checkout, otherwise its generated bundle
|
|
10
10
|
* (`createOrAttachInlineRun` / `openInlineRunProjectDb` from packages/app), and lets it
|
|
11
|
-
* resolve the SAME project
|
|
11
|
+
* resolve the SAME project/registered/shared definition the engine would launch, compute the
|
|
12
12
|
* canonical definition digest with the exported hash machinery, and create-or-attach the
|
|
13
13
|
* run row through the existing engine persistence adapter.
|
|
14
14
|
*
|
|
15
15
|
* The script itself contains NO direct SQL, NO second hasher and NO persistence policy —
|
|
16
|
-
* every rule lives in packages/app (0804 D1). On a bundle-only install
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* unbound run (0804 R1 failure policy).
|
|
16
|
+
* every rule lives in packages/app (0804 D1). On a bundle-only install, the existing
|
|
17
|
+
* workflow-show projection selects the CLI's definition; the bundled app revalidates its
|
|
18
|
+
* schema and digest before recording the selected layer. It never creates an unbound run.
|
|
20
19
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
20
|
+
* The setup outcome is written into the two-file run record (task 0927 R1) — machine state
|
|
21
|
+
* at `.spur/run/<run-id>.state.json` (atomic replace) plus the `.spur/run/<run-id>.md`
|
|
22
|
+
* run-start header — so the driver can seed the inline var overlay (`__runId`,
|
|
23
|
+
* `__definitionDigest`) that proof capture and bound registration verify against. The
|
|
24
|
+
* pre-0927 `<run-id>-inline-setup.json` sidecar is no longer written. Exit 0 = authoritative
|
|
25
|
+
* identity ready (created or idempotently attached); exit 1 = fail closed, the driver must stop.
|
|
25
26
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* task-evidence-precheck.ts.
|
|
27
|
+
* Standard script (ADR-065): the generated Node-runnable twin re-enters Bun for the
|
|
28
|
+
* existing SQLite runtime. Bun on PATH is required; a monorepo checkout is not.
|
|
29
29
|
*
|
|
30
30
|
* Usage:
|
|
31
31
|
* bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]
|
|
32
32
|
* bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]
|
|
33
33
|
* bun plugins/sp/scripts/inline-run-setup.ts --action --run-id <id> --node <state> --kind <kind> \
|
|
34
|
+
* bun plugins/sp/scripts/inline-run-setup.ts --decide --run-id <id> --node <state> --options-json <file> [--spur-bin <path>]
|
|
34
35
|
* --status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]
|
|
35
|
-
* bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--spur-bin <path>]
|
|
36
|
+
* bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]
|
|
36
37
|
*
|
|
37
38
|
* The `--fingerprint` mode prints the engine's proof-input digest for the given spec files and
|
|
38
39
|
* creates nothing (task 0862 R5).
|
|
@@ -41,20 +42,38 @@
|
|
|
41
42
|
* 0868): `--action` records one completed action boundary as an `action_runs` row through the
|
|
42
43
|
* shared `WorkflowActionTraceWriter`, `--close` marks the run row terminal through the same
|
|
43
44
|
* writer. `--action` is best-effort: a persistence failure is appended to
|
|
44
|
-
* `.spur/run/<run-id>.
|
|
45
|
+
* `.spur/run/<run-id>.md` and the script still exits 0 with `{"ok":false}` on stdout, so
|
|
45
46
|
* observation never wedges the run. `--close` is NOT best-effort: the run-row closure is
|
|
46
47
|
* bookkeeping, so a missing run row or a persistence failure exits 1 with a named error.
|
|
48
|
+
* The `--decide` mode (task 0941 R5) executes the non-pausing decide action through the SAME
|
|
49
|
+
* app runner the engine registers (`runDecideForInlineRun`), writing the same resultFile row
|
|
50
|
+
* and recording the same `action_runs` trace row (`kind=decide`, best-effort). A degraded
|
|
51
|
+
* decision is still `ok: true` — the run continues — so exit 0 covers every model-level
|
|
52
|
+
* outcome; only an invalid options schema exits 1 (fail closed).
|
|
53
|
+
*
|
|
47
54
|
* Exit 2 is reserved for usage errors.
|
|
48
55
|
*
|
|
49
56
|
* Env: SPUR_BIN
|
|
50
57
|
*/
|
|
51
58
|
|
|
52
|
-
import {
|
|
59
|
+
import { spawnSync } from 'node:child_process';
|
|
60
|
+
import {
|
|
61
|
+
appendFileSync,
|
|
62
|
+
closeSync,
|
|
63
|
+
existsSync,
|
|
64
|
+
mkdirSync,
|
|
65
|
+
openSync,
|
|
66
|
+
readFileSync,
|
|
67
|
+
renameSync,
|
|
68
|
+
unlinkSync,
|
|
69
|
+
writeFileSync,
|
|
70
|
+
writeSync,
|
|
71
|
+
} from 'node:fs';
|
|
53
72
|
import { dirname, join, resolve } from 'node:path';
|
|
54
73
|
import { fileURLToPath } from 'node:url';
|
|
55
74
|
import { getEnvVar } from '../lib/env';
|
|
56
75
|
|
|
57
|
-
/**
|
|
76
|
+
/** Setup outcome projected into the run-record state `.spur/run/<run-id>.state.json` (0927 R1). */
|
|
58
77
|
interface SetupOutcome {
|
|
59
78
|
readonly ok: boolean;
|
|
60
79
|
readonly runId: string;
|
|
@@ -81,14 +100,17 @@ function usage(): never {
|
|
|
81
100
|
'--status <done|failed> --ok <true|false> --duration-ms <n> [--spur-bin <path>]',
|
|
82
101
|
);
|
|
83
102
|
console.error(
|
|
84
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--spur-bin <path>]',
|
|
103
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]',
|
|
104
|
+
);
|
|
105
|
+
console.error(
|
|
106
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --decide --run-id <id> --node <state> --options-json <file> [--spur-bin <path>]',
|
|
85
107
|
);
|
|
86
108
|
process.exit(2);
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
/**
|
|
90
|
-
* The run id becomes
|
|
91
|
-
* single safe filename component before anything is written — the same guard class the
|
|
112
|
+
* The run id becomes filenames under `.spur/run/` (`<run-id>.state.json` + `<run-id>.md`),
|
|
113
|
+
* so it must be a single safe filename component before anything is written — the same guard class the
|
|
92
114
|
* task-pipeline.yaml route-reason action applies to `$__runId` (task 0804 R8). The allowlist
|
|
93
115
|
* refuses path separators, dot traversal (leading `.`), unresolved interpolation (`$`/`{`/`}`) and
|
|
94
116
|
* every other shell/unspecified metachar; valid UUID/timestamp-slug ids pass.
|
|
@@ -97,7 +119,7 @@ const SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
|
97
119
|
|
|
98
120
|
function refuseUnsafeRunId(runId: string): never {
|
|
99
121
|
// Refuse BEFORE any outcome write: an unsafe id must never reach
|
|
100
|
-
// `.spur/run/<run-id
|
|
122
|
+
// `.spur/run/<run-id>.state.json` / `.md` (no traversal, no unintended file).
|
|
101
123
|
console.error(`inline-run-setup: refusing unsafe run id: ${runId}`);
|
|
102
124
|
console.error(
|
|
103
125
|
' The run id must be a single safe filename component (alphanumeric/._-, no leading dot, ' +
|
|
@@ -112,12 +134,9 @@ function refuseUnsafeRunId(runId: string): never {
|
|
|
112
134
|
* (--spur-bin > SPUR_BIN > monorepo-local CLI entry > PATH `spur`), then derive the repo
|
|
113
135
|
* root from the resolved main module. `bun <repo>/apps/cli/src/index.ts` → repo root is
|
|
114
136
|
* three levels up. A bundle-only install (`spur` on PATH, a bundled `spur.js`, or a spur
|
|
115
|
-
* binary without the app workspace)
|
|
116
|
-
* closed with remediation guidance.
|
|
137
|
+
* binary without the app workspace) uses the adjacent generated application bundle.
|
|
117
138
|
*/
|
|
118
|
-
function resolveAppEntry(
|
|
119
|
-
spurBin: string,
|
|
120
|
-
): { entry: string; repoRoot: string } | { entry: null; repoRoot: null; chain: string } {
|
|
139
|
+
function resolveAppEntry(spurBin: string): { entry: string; portable: boolean } {
|
|
121
140
|
let candidates: string[] = [];
|
|
122
141
|
if (spurBin !== '') {
|
|
123
142
|
candidates = [spurBin];
|
|
@@ -137,16 +156,119 @@ function resolveAppEntry(
|
|
|
137
156
|
const srcDir = dirname(mainModule);
|
|
138
157
|
const repoRoot = resolve(srcDir, '..', '..', '..');
|
|
139
158
|
const appEntry = join(repoRoot, 'packages', 'app', 'src', 'index.ts');
|
|
140
|
-
if (existsSync(appEntry)) return { entry: appEntry,
|
|
141
|
-
|
|
159
|
+
if (existsSync(appEntry)) return { entry: appEntry, portable: false };
|
|
160
|
+
}
|
|
161
|
+
const entry = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
|
|
162
|
+
if (!existsSync(entry)) {
|
|
163
|
+
throw new Error('inline application bundle is missing — rebuild/install the sp plugin before running inline');
|
|
164
|
+
}
|
|
165
|
+
return { entry, portable: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Let the selected CLI own config and layer resolution, then revalidate its snapshot in the app. */
|
|
169
|
+
async function readInstalledInventory(file: string, spurBin: string): Promise<unknown> {
|
|
170
|
+
const localCli = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
171
|
+
const bundle = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
|
|
172
|
+
const { splitLaunchCommand } = (await import(bundle)) as typeof import('../lib/inline-run.generated.mjs');
|
|
173
|
+
const launch = spurBin
|
|
174
|
+
? splitLaunchCommand(spurBin, 'inline-run-setup "spurBin"')
|
|
175
|
+
: existsSync(localCli)
|
|
176
|
+
? { command: 'bun', leadingArgs: [localCli] }
|
|
177
|
+
: { command: 'spur', leadingArgs: [] };
|
|
178
|
+
if ('error' in launch) throw new Error(launch.error);
|
|
179
|
+
const result = spawnSync(
|
|
180
|
+
launch.command,
|
|
181
|
+
[...launch.leadingArgs, 'workflow', 'show', file, '--format', 'todo', '--json'],
|
|
182
|
+
{ cwd: process.cwd(), encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024 },
|
|
183
|
+
);
|
|
184
|
+
if (result.status !== 0) {
|
|
185
|
+
throw new Error(
|
|
186
|
+
`could not resolve the workflow definition with the installed CLI: ${result.error?.message ?? result.stderr}`,
|
|
187
|
+
);
|
|
142
188
|
}
|
|
143
|
-
|
|
189
|
+
const value: unknown = JSON.parse(result.stdout);
|
|
190
|
+
// Honor the existing optional JSON envelope without inventing another projection format.
|
|
191
|
+
if (value && typeof value === 'object' && 'ok' in value && 'data' in value && value.ok === true) {
|
|
192
|
+
return value.data;
|
|
193
|
+
}
|
|
194
|
+
return value;
|
|
144
195
|
}
|
|
145
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Project the setup outcome into the two-file run record (task 0927 R1): the machine state
|
|
199
|
+
* merges into `.spur/run/<run-id>.state.json` (atomic same-directory temp + rename, the
|
|
200
|
+
* 0925 R1 pattern) and `.spur/run/<run-id>.md` receives its run-start header exactly once.
|
|
201
|
+
* A re-setup of the same run id rewrites the state from the current outcome but keeps the
|
|
202
|
+
* prior `startedAt` instead of resurrecting setup time. Identity/provenance fields only —
|
|
203
|
+
* never prompt bodies — so the record carries no secret-bearing content by construction
|
|
204
|
+
* (0927 AC2). The retired `<run-id>-inline-setup.json` sidecar is not written (0927 R4);
|
|
205
|
+
* its readers were migrated to the pair state in the same change.
|
|
206
|
+
*/
|
|
146
207
|
function writeOutcome(runId: string, outcome: SetupOutcome): void {
|
|
147
208
|
const runDir = join(process.cwd(), '.spur', 'run');
|
|
148
209
|
if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
|
|
149
|
-
|
|
210
|
+
const statePath = join(runDir, `${runId}.state.json`);
|
|
211
|
+
const markdownPath = join(runDir, `${runId}.md`);
|
|
212
|
+
let prior: Record<string, unknown> = {};
|
|
213
|
+
try {
|
|
214
|
+
const parsed: unknown = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
215
|
+
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
216
|
+
prior = parsed as Record<string, unknown>;
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// No prior state (new run) or unreadable file — the outcome alone defines the state.
|
|
220
|
+
}
|
|
221
|
+
const at = new Date().toISOString();
|
|
222
|
+
const state = {
|
|
223
|
+
schemaVersion: 1 as const,
|
|
224
|
+
runId,
|
|
225
|
+
...(outcome.workflowName !== undefined ? { workflowName: outcome.workflowName } : {}),
|
|
226
|
+
...(outcome.status !== undefined ? { status: outcome.status } : {}),
|
|
227
|
+
startedAt: typeof prior.startedAt === 'string' ? prior.startedAt : at,
|
|
228
|
+
updatedAt: at,
|
|
229
|
+
...(outcome.attached !== undefined ? { attached: outcome.attached } : {}),
|
|
230
|
+
...(outcome.definitionDigest !== undefined ? { definitionDigest: outcome.definitionDigest } : {}),
|
|
231
|
+
...(outcome.workflowVersion !== undefined ? { workflowVersion: outcome.workflowVersion } : {}),
|
|
232
|
+
...(outcome.resolvedPath !== undefined ? { resolvedPath: outcome.resolvedPath } : {}),
|
|
233
|
+
...(outcome.layer !== undefined ? { layer: outcome.layer } : {}),
|
|
234
|
+
...(outcome.workdir !== undefined ? { workdir: outcome.workdir } : {}),
|
|
235
|
+
// 0948 R7: project the setup outcome itself, so the retired `-inline-setup.json`
|
|
236
|
+
// sidecar's `ok` is still readable from the pair, and a SUCCESSFUL re-setup
|
|
237
|
+
// explicitly drops any prior `error` — a stale failure message must not outlive
|
|
238
|
+
// the failure it described (the state object is rebuilt, never merged with `prior`).
|
|
239
|
+
ok: outcome.ok,
|
|
240
|
+
...(outcome.ok === false && outcome.error !== undefined ? { error: outcome.error } : {}),
|
|
241
|
+
};
|
|
242
|
+
const temp = `${statePath}.tmp`;
|
|
243
|
+
try {
|
|
244
|
+
writeFileSync(temp, `${JSON.stringify(state, null, 4)}\n`);
|
|
245
|
+
renameSync(temp, statePath);
|
|
246
|
+
} catch {
|
|
247
|
+
// Best-effort: a failing record write must not wedge setup reporting — and never
|
|
248
|
+
// leaves `.tmp` residue behind (0926 R1).
|
|
249
|
+
try {
|
|
250
|
+
unlinkSync(temp);
|
|
251
|
+
} catch {
|
|
252
|
+
// Nothing to clean (temp was never created).
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// 0948 R7: create the header with `wx` (O_EXCL) instead of `existsSync` → `appendFileSync`.
|
|
256
|
+
// The check-then-append pair could append a SECOND header when two setups raced, and it
|
|
257
|
+
// kept "header present" as a separate fact from the identity write. One exclusive create
|
|
258
|
+
// makes exactly-one-header atomic; EEXIST just means it is already there.
|
|
259
|
+
let headerFd: number | undefined;
|
|
260
|
+
try {
|
|
261
|
+
headerFd = openSync(markdownPath, 'wx');
|
|
262
|
+
writeSync(
|
|
263
|
+
headerFd,
|
|
264
|
+
`# spur inline run ${runId} — ${outcome.workflowName ?? 'unknown workflow'} — setup ${at}\n`,
|
|
265
|
+
);
|
|
266
|
+
} catch {
|
|
267
|
+
// EEXIST (already written) or an unwritable path — the human header is not the setup
|
|
268
|
+
// identity, so this stays best-effort exactly like the state write above.
|
|
269
|
+
} finally {
|
|
270
|
+
if (headerFd !== undefined) closeSync(headerFd);
|
|
271
|
+
}
|
|
150
272
|
}
|
|
151
273
|
|
|
152
274
|
/**
|
|
@@ -159,14 +281,7 @@ function writeOutcome(runId: string, outcome: SetupOutcome): void {
|
|
|
159
281
|
* Returns the process exit code: 0 printed, 1 read/resolve failure.
|
|
160
282
|
*/
|
|
161
283
|
async function printFingerprint(taskFile: string, featureFile: string, spurBin: string): Promise<number> {
|
|
162
|
-
const { entry
|
|
163
|
-
if (entry === null || repoRoot === null) {
|
|
164
|
-
console.error(
|
|
165
|
-
`inline-run-setup: FAIL — no monorepo checkout of spur is reachable via ${chain}. ` +
|
|
166
|
-
'The proof-input digest must be computed by the app service; a bundle-only install cannot do it.',
|
|
167
|
-
);
|
|
168
|
-
return 1;
|
|
169
|
-
}
|
|
284
|
+
const { entry } = resolveAppEntry(spurBin);
|
|
170
285
|
const app = (await import(entry)) as {
|
|
171
286
|
computeProofInputFingerprint: (options: Record<string, unknown>) => Promise<string>;
|
|
172
287
|
readProofInputContents: (
|
|
@@ -200,17 +315,37 @@ async function printFingerprint(taskFile: string, featureFile: string, spurBin:
|
|
|
200
315
|
/** Terminal statuses the inline driver may declare when closing its run row. */
|
|
201
316
|
const CLOSE_STATUSES = new Set(['done', 'failed', 'paused']);
|
|
202
317
|
|
|
318
|
+
/**
|
|
319
|
+
* 0937 R2: closed terminal-reason vocabulary the driver may declare on `--close`.
|
|
320
|
+
* COPIED from packages/app/src/workflow/terminal-reason.ts — the plugin standalone
|
|
321
|
+
* contract forbids a value import of app code; the parity test asserts the copy
|
|
322
|
+
* equals the app export.
|
|
323
|
+
*/
|
|
324
|
+
const TERMINAL_REASONS = new Set([
|
|
325
|
+
'done',
|
|
326
|
+
'paused-operator',
|
|
327
|
+
'failed-check',
|
|
328
|
+
'failed-agent',
|
|
329
|
+
'failed-timeout',
|
|
330
|
+
'failed-guard',
|
|
331
|
+
'cancelled',
|
|
332
|
+
'interrupted',
|
|
333
|
+
'retry-exhausted',
|
|
334
|
+
]);
|
|
335
|
+
|
|
203
336
|
/** Finalize statuses — a finish emission is terminal, so only done|failed are valid (0868 #4). */
|
|
204
337
|
const ACTION_STATUSES = new Set(['done', 'failed']);
|
|
205
338
|
|
|
206
339
|
/** Input for the ADR-117 emission modes (`--action` / `--close`). */
|
|
207
|
-
import type { WorkflowActionTraceWriter } from '@gobing-ai/app';
|
|
340
|
+
import type { WorkflowActionTraceWriter } from '@gobing-ai/spur-app';
|
|
208
341
|
|
|
209
342
|
interface TraceModeInput {
|
|
210
343
|
readonly runId: string;
|
|
211
344
|
readonly close: boolean;
|
|
212
345
|
readonly node: string;
|
|
213
346
|
readonly kind: string;
|
|
347
|
+
/** Declared terminal reason (0937 R2) — validated against TERMINAL_REASONS before this point. */
|
|
348
|
+
readonly reason?: string;
|
|
214
349
|
readonly status: string;
|
|
215
350
|
readonly ok: boolean;
|
|
216
351
|
readonly durationMs: number;
|
|
@@ -218,9 +353,23 @@ interface TraceModeInput {
|
|
|
218
353
|
}
|
|
219
354
|
|
|
220
355
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
356
|
+
* The run-record file for appended driver lines (task 0927 R1): `.spur/run/<run-id>.md`
|
|
357
|
+
* for pair-based runs. A legacy run that predates the pair keeps appending to its declared
|
|
358
|
+
* `.spur/run/<run-id>.log` — the same precedence as `readWorkflowRunRecord` (the pair wins,
|
|
359
|
+
* legacy stays readable in place).
|
|
360
|
+
*/
|
|
361
|
+
function runRecordLogPath(runDir: string, runId: string): string {
|
|
362
|
+
const markdownPath = join(runDir, `${runId}.md`);
|
|
363
|
+
const legacyLogPath = join(runDir, `${runId}.log`);
|
|
364
|
+
if (existsSync(legacyLogPath) && !existsSync(markdownPath)) return legacyLogPath;
|
|
365
|
+
return markdownPath;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Append one emission-failure line to the run record markdown — `.spur/run/<run-id>.md`,
|
|
370
|
+
* the run log the inline driver already owns. Best-effort and synchronous (the process may
|
|
371
|
+
* exit immediately after), and never throws: an unwritable log must not wedge the run
|
|
372
|
+
* (ADR-117 R3).
|
|
224
373
|
*/
|
|
225
374
|
function appendTraceFailureLine(runId: string, detail: string): void {
|
|
226
375
|
try {
|
|
@@ -228,7 +377,7 @@ function appendTraceFailureLine(runId: string, detail: string): void {
|
|
|
228
377
|
if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
|
|
229
378
|
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
230
379
|
const stamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
231
|
-
appendFileSync(
|
|
380
|
+
appendFileSync(runRecordLogPath(runDir, safeRunId), `[${stamp}] ${detail}\n`);
|
|
232
381
|
} catch {
|
|
233
382
|
// Best-effort (R3): the run continues even when the failure cannot be recorded.
|
|
234
383
|
}
|
|
@@ -254,27 +403,20 @@ async function runTraceMode(input: TraceModeInput): Promise<number> {
|
|
|
254
403
|
return input.close ? 1 : 0;
|
|
255
404
|
};
|
|
256
405
|
|
|
257
|
-
const { entry, repoRoot, chain } = resolveAppEntry(input.spurBin);
|
|
258
|
-
if (entry === null || repoRoot === null) {
|
|
259
|
-
return fail(
|
|
260
|
-
`no monorepo checkout of spur is reachable via ${chain} — the shared trace writer ` +
|
|
261
|
-
'lives in packages/app (point SPUR_BIN at a repo checkout)',
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
406
|
// Compile-time link (0868 finding #5): the writer half is typed by the real packages/app
|
|
266
407
|
// export (type-only import, erased at runtime) so a signature drift breaks THIS file's
|
|
267
408
|
// typecheck instead of hiding behind the hand-declared cast.
|
|
268
|
-
const app = (await import(entry)) as {
|
|
269
|
-
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
270
|
-
createWorkflowActionTraceWriter: (
|
|
271
|
-
db: unknown,
|
|
272
|
-
recordFailure?: (failure: unknown) => void,
|
|
273
|
-
) => WorkflowActionTraceWriter;
|
|
274
|
-
};
|
|
275
|
-
|
|
276
409
|
let projectDb: { adapter: unknown; close: () => void } | undefined;
|
|
277
410
|
try {
|
|
411
|
+
const { entry } = resolveAppEntry(input.spurBin);
|
|
412
|
+
const app = (await import(entry)) as {
|
|
413
|
+
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
414
|
+
createWorkflowActionTraceWriter: (
|
|
415
|
+
db: unknown,
|
|
416
|
+
recordFailure?: (failure: unknown) => void,
|
|
417
|
+
) => WorkflowActionTraceWriter;
|
|
418
|
+
};
|
|
419
|
+
|
|
278
420
|
projectDb = await app.openInlineRunProjectDb(process.cwd());
|
|
279
421
|
const writer = app.createWorkflowActionTraceWriter(projectDb.adapter, (failure: unknown) => {
|
|
280
422
|
const detail = failure as { operation?: string; error?: string };
|
|
@@ -285,7 +427,7 @@ async function runTraceMode(input: TraceModeInput): Promise<number> {
|
|
|
285
427
|
});
|
|
286
428
|
const result = (
|
|
287
429
|
input.close
|
|
288
|
-
? await writer.closeRun(input.runId, input.status)
|
|
430
|
+
? await writer.closeRun(input.runId, input.status, undefined, input.reason)
|
|
289
431
|
: await writer.recordAction({
|
|
290
432
|
runId: input.runId,
|
|
291
433
|
node: input.node,
|
|
@@ -320,7 +462,91 @@ async function runTraceMode(input: TraceModeInput): Promise<number> {
|
|
|
320
462
|
}
|
|
321
463
|
}
|
|
322
464
|
|
|
465
|
+
/**
|
|
466
|
+
* `--decide` mode (task 0941 R5): execute the non-pausing decide action through the same app
|
|
467
|
+
* function the engine registers (`runDecideForInlineRun` → `DecideActionRunner`), then record
|
|
468
|
+
* the `action_runs` trace row through the shared writer. The decide-enabled switch
|
|
469
|
+
* (`workflow.decideDecisionMaker`) is resolved HERE at the driver boundary (ADR-082) through
|
|
470
|
+
* the bridged facade derivation and passed into the app runner as an explicit parameter —
|
|
471
|
+
* app services never load Spur config. Degraded outcomes (`disabled`,
|
|
472
|
+
* `no-backend`, `error`, `timeout`, `low-confidence`) are normal — the value is the declared
|
|
473
|
+
* default and the run continues — so they print `ok: true` and exit 0. Only an invalid options
|
|
474
|
+
* schema, an unreadable options file, or a failed config load fails closed with exit 1.
|
|
475
|
+
*/
|
|
476
|
+
async function runDecideMode(input: {
|
|
477
|
+
runId: string;
|
|
478
|
+
node: string;
|
|
479
|
+
optionsFile: string;
|
|
480
|
+
spurBin: string;
|
|
481
|
+
}): Promise<number> {
|
|
482
|
+
type DecideOutcome = {
|
|
483
|
+
ok: boolean;
|
|
484
|
+
error?: string;
|
|
485
|
+
value?: string;
|
|
486
|
+
degraded?: boolean;
|
|
487
|
+
reason?: string;
|
|
488
|
+
backend?: string | null;
|
|
489
|
+
confidence?: number | null;
|
|
490
|
+
resultFile?: string;
|
|
491
|
+
durationMs?: number;
|
|
492
|
+
};
|
|
493
|
+
const decideFailed = (error: string): number => {
|
|
494
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}\n`);
|
|
495
|
+
return 1;
|
|
496
|
+
};
|
|
497
|
+
let outcome: DecideOutcome;
|
|
498
|
+
try {
|
|
499
|
+
const { entry, portable } = resolveAppEntry(input.spurBin);
|
|
500
|
+
const app = (await import(entry)) as {
|
|
501
|
+
runDecideForInlineRun: (input2: {
|
|
502
|
+
workdir: string;
|
|
503
|
+
optionsFile: string;
|
|
504
|
+
enabled: boolean;
|
|
505
|
+
}) => Promise<DecideOutcome>;
|
|
506
|
+
};
|
|
507
|
+
// Config switch at the driver boundary (ADR-082 / task 0941 gate fix): the flag comes
|
|
508
|
+
// from the bridged facade derivation in the generated lib — the same committed bundle
|
|
509
|
+
// the inventory flow imports — never a @gobing-ai/* value import. Embedded schemas
|
|
510
|
+
// ride along only in the portable layout, mirroring the setup mode's load posture.
|
|
511
|
+
const lib = (await import(
|
|
512
|
+
fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url))
|
|
513
|
+
)) as typeof import('../lib/inline-run.generated.mjs');
|
|
514
|
+
const enabled = await lib.resolveDecideDecisionMakerEnabled(
|
|
515
|
+
process.cwd(),
|
|
516
|
+
portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined,
|
|
517
|
+
);
|
|
518
|
+
outcome = await app.runDecideForInlineRun({
|
|
519
|
+
workdir: process.cwd(),
|
|
520
|
+
optionsFile: input.optionsFile,
|
|
521
|
+
enabled,
|
|
522
|
+
});
|
|
523
|
+
} catch (error) {
|
|
524
|
+
return decideFailed(error instanceof Error ? error.message : String(error));
|
|
525
|
+
}
|
|
526
|
+
if (!outcome.ok) return decideFailed(outcome.error ?? 'decide failed without an error message');
|
|
527
|
+
process.stdout.write(`${JSON.stringify({ ok: true, runId: input.runId, node: input.node, ...outcome })}\n`);
|
|
528
|
+
// Trace row is best-effort, exactly like --action: an emission failure never wedges the run.
|
|
529
|
+
return await runTraceMode({
|
|
530
|
+
runId: input.runId,
|
|
531
|
+
close: false,
|
|
532
|
+
node: input.node,
|
|
533
|
+
kind: 'decide',
|
|
534
|
+
status: 'done',
|
|
535
|
+
ok: true,
|
|
536
|
+
durationMs: outcome.durationMs ?? 0,
|
|
537
|
+
spurBin: input.spurBin,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
|
|
323
541
|
async function main(): Promise<void> {
|
|
542
|
+
// The portable Node twin has no workspace imports; SQLite still uses Spur's existing Bun runtime.
|
|
543
|
+
if (!process.versions.bun) {
|
|
544
|
+
const child = spawnSync('bun', [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
|
|
545
|
+
stdio: 'inherit',
|
|
546
|
+
});
|
|
547
|
+
if (child.error) console.error(`inline-run-setup requires Bun on PATH: ${child.error.message}`);
|
|
548
|
+
process.exit(child.status ?? 1);
|
|
549
|
+
}
|
|
324
550
|
let runId = '';
|
|
325
551
|
let file = '';
|
|
326
552
|
let fingerprint = false;
|
|
@@ -328,9 +554,12 @@ async function main(): Promise<void> {
|
|
|
328
554
|
let featureFile = '';
|
|
329
555
|
let action = false;
|
|
330
556
|
let close = false;
|
|
557
|
+
let decide = false;
|
|
558
|
+
let optionsJson = '';
|
|
331
559
|
let node = '';
|
|
332
560
|
let kind = '';
|
|
333
561
|
let status = '';
|
|
562
|
+
let reason = '';
|
|
334
563
|
let okRaw = '';
|
|
335
564
|
let durationRaw = '';
|
|
336
565
|
let spurBin = getEnvVar('SPUR_BIN') ?? '';
|
|
@@ -343,9 +572,12 @@ async function main(): Promise<void> {
|
|
|
343
572
|
else if (argv[i] === '--feature-file') featureFile = argv[++i] ?? '';
|
|
344
573
|
else if (argv[i] === '--action') action = true;
|
|
345
574
|
else if (argv[i] === '--close') close = true;
|
|
575
|
+
else if (argv[i] === '--decide') decide = true;
|
|
576
|
+
else if (argv[i] === '--options-json') optionsJson = argv[++i] ?? '';
|
|
346
577
|
else if (argv[i] === '--node') node = argv[++i] ?? '';
|
|
347
578
|
else if (argv[i] === '--kind') kind = argv[++i] ?? '';
|
|
348
579
|
else if (argv[i] === '--status') status = argv[++i] ?? '';
|
|
580
|
+
else if (argv[i] === '--reason') reason = argv[++i] ?? '';
|
|
349
581
|
else if (argv[i] === '--ok') okRaw = argv[++i] ?? '';
|
|
350
582
|
else if (argv[i] === '--duration-ms') durationRaw = argv[++i] ?? '';
|
|
351
583
|
else if (argv[i] === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
@@ -361,12 +593,29 @@ async function main(): Promise<void> {
|
|
|
361
593
|
// ADR-117 emission modes (task 0868): the inline driver reports one completed action
|
|
362
594
|
// boundary, or closes its run row at the declared terminal state. Both share the run-id
|
|
363
595
|
// filename guard, the app-entry resolution chain and the best-effort failure contract.
|
|
596
|
+
// Non-pausing decide action (0941 R5): --decide is mutually exclusive with the run-setup,
|
|
597
|
+
// fingerprint, and trace modes; it requires the run id (filename-guarded), the state id
|
|
598
|
+
// for the trace row, and the options JSON file.
|
|
599
|
+
if (decide) {
|
|
600
|
+
if (action || close || fingerprint || file !== '' || taskFile !== '') usage();
|
|
601
|
+
if (runId.trim() === '' || node.trim() === '' || optionsJson.trim() === '') usage();
|
|
602
|
+
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
603
|
+
process.exit(await runDecideMode({ runId, node, optionsFile: optionsJson, spurBin }));
|
|
604
|
+
}
|
|
605
|
+
|
|
364
606
|
if (action || close) {
|
|
365
607
|
if (action && close) usage();
|
|
366
608
|
if (runId.trim() === '' || status.trim() === '') usage();
|
|
367
609
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
368
610
|
if (close) {
|
|
369
611
|
if (!CLOSE_STATUSES.has(status)) usage();
|
|
612
|
+
// 0937 R2: a failed close requires a declared reason, and any declared reason
|
|
613
|
+
// must be a closed-enum value — both fail loudly BEFORE any write happens.
|
|
614
|
+
if (reason.trim() === '') {
|
|
615
|
+
if (status === 'failed') usage();
|
|
616
|
+
} else if (!TERMINAL_REASONS.has(reason)) {
|
|
617
|
+
usage();
|
|
618
|
+
}
|
|
370
619
|
process.exit(
|
|
371
620
|
await runTraceMode({
|
|
372
621
|
runId,
|
|
@@ -377,6 +626,7 @@ async function main(): Promise<void> {
|
|
|
377
626
|
ok: true,
|
|
378
627
|
durationMs: 0,
|
|
379
628
|
spurBin,
|
|
629
|
+
...(reason.trim() === '' ? {} : { reason }),
|
|
380
630
|
}),
|
|
381
631
|
);
|
|
382
632
|
}
|
|
@@ -396,23 +646,7 @@ async function main(): Promise<void> {
|
|
|
396
646
|
if (runId.trim() === '' || file.trim() === '') usage();
|
|
397
647
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
398
648
|
|
|
399
|
-
const { entry,
|
|
400
|
-
if (entry === null || repoRoot === null) {
|
|
401
|
-
const outcome: SetupOutcome = {
|
|
402
|
-
ok: false,
|
|
403
|
-
runId,
|
|
404
|
-
error:
|
|
405
|
-
`inline run setup failed closed: no monorepo checkout of spur is reachable via ${chain}. ` +
|
|
406
|
-
'The authoritative run identity must be persisted by the app service ' +
|
|
407
|
-
'(packages/app/src/services/inline-run-setup.ts); a bundle-only install cannot do this. ' +
|
|
408
|
-
'Remediation: point SPUR_BIN at a repo checkout, e.g. ' +
|
|
409
|
-
'SPUR_BIN="bun /path/to/spur/apps/cli/src/index.ts". The pipeline must not run unbound.',
|
|
410
|
-
};
|
|
411
|
-
writeOutcome(runId, outcome);
|
|
412
|
-
console.error(`inline-run-setup: FAIL for run ${runId}`);
|
|
413
|
-
console.error(` ${outcome.error}`);
|
|
414
|
-
process.exit(1);
|
|
415
|
-
}
|
|
649
|
+
const { entry, portable } = resolveAppEntry(spurBin);
|
|
416
650
|
|
|
417
651
|
// Dynamic import by absolute path: the app source graph resolves its own workspace
|
|
418
652
|
// dependencies from the repo checkout, never from this plugin script's location.
|
|
@@ -422,11 +656,22 @@ async function main(): Promise<void> {
|
|
|
422
656
|
getDb: () => Promise<unknown>;
|
|
423
657
|
file: string;
|
|
424
658
|
runId: string;
|
|
659
|
+
inventory?: unknown;
|
|
660
|
+
embeddedSchemas?: ReadonlyMap<string, string>;
|
|
425
661
|
}) => Promise<SetupOutcome & { ok: boolean }>;
|
|
426
662
|
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
663
|
+
EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string>;
|
|
427
664
|
};
|
|
428
665
|
|
|
429
666
|
const workdir = process.cwd();
|
|
667
|
+
let inventory: unknown;
|
|
668
|
+
try {
|
|
669
|
+
inventory = await readInstalledInventory(file, spurBin);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
const message = `could not resolve the workflow definition: ${error instanceof Error ? error.message : String(error)}`;
|
|
672
|
+
writeOutcome(runId, { ok: false, runId, error: message });
|
|
673
|
+
throw new Error(message);
|
|
674
|
+
}
|
|
430
675
|
const projectDb = await app.openInlineRunProjectDb(workdir);
|
|
431
676
|
let exitCode = 0;
|
|
432
677
|
try {
|
|
@@ -435,6 +680,8 @@ async function main(): Promise<void> {
|
|
|
435
680
|
getDb: async () => projectDb.adapter,
|
|
436
681
|
file,
|
|
437
682
|
runId,
|
|
683
|
+
inventory,
|
|
684
|
+
...(portable ? { embeddedSchemas: app.EMBEDDED_SPUR_SCHEMAS } : {}),
|
|
438
685
|
});
|
|
439
686
|
writeOutcome(runId, result);
|
|
440
687
|
if (!result.ok) {
|