@gobing-ai/spur 0.3.95 → 0.3.97
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/plugin-scripts.json +0 -54
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +6 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
- package/config/rules/typescript/output-boundaries.yaml +15 -2
- package/config/script-placement-baseline.json +51 -0
- package/config/templates/AGENTS.md +3 -3
- package/config/workflows/feature-verification.yaml +1 -1
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +80 -85
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +42 -95
- package/config/workflows/wrapup-pipeline.yaml +91 -38
- package/package.json +2 -2
- package/plugins/sp/README.md +14 -10
- package/plugins/sp/agents/super-reviewer.md +43 -18
- package/plugins/sp/commands/dev-fixgha.md +83 -0
- package/plugins/sp/commands/dev-gitmsg.md +8 -6
- package/plugins/sp/commands/dev-gtd.md +2 -2
- package/plugins/sp/commands/dev-idea.md +22 -12
- package/plugins/sp/commands/dev-job-dump.md +29 -0
- package/plugins/sp/commands/dev-job-resume.md +29 -0
- package/plugins/sp/commands/dev-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verifyall.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
- package/plugins/sp/lib/inline-run.generated.mjs +39 -13
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +445 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +63 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +226 -0
- package/plugins/sp/lib/spur-bin.ts +36 -0
- package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
- package/plugins/sp/lib/step-profile.generated.mjs +174 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +3 -3
- package/plugins/sp/scripts/feature-verification-steps.mjs +14 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +14 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
- package/plugins/sp/scripts/inline-run-setup.ts +104 -667
- package/plugins/sp/scripts/quality-gate.mjs +46 -24
- package/plugins/sp/scripts/quality-gate.ts +13 -659
- package/plugins/sp/scripts/residual-scan.mjs +179 -174
- package/plugins/sp/scripts/residual-scan.ts +125 -517
- package/plugins/sp/scripts/script-root.mjs +5 -1
- package/plugins/sp/scripts/script-root.ts +5 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
- package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +72 -33
- package/plugins/sp/scripts/wrapup-steps.ts +128 -37
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +34 -9
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
- package/plugins/sp/skills/functional-review/SKILL.md +7 -4
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/source-driven-development/SKILL.md +11 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
- package/plugins/sp/skills/spur-cli/references/features.md +17 -6
- package/plugins/sp/skills/spur-cli/references/init.md +17 -16
- package/plugins/sp/skills/spur-cli/references/self.md +3 -2
- package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-cli/references/workflows.md +17 -14
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -10
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +165 -75
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +7 -5
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +84 -39
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -12
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +40 -21
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +72 -15
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/spur.js +4461 -2085
- package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.Bmv5WkJ9.js} +1 -1
- package/web/_astro/BoardApp.vveLOdDq.js +322 -0
- package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.IKc3uDYV.js} +1 -1
- package/web/_astro/{arc.CyjRvNMY.js → arc.C63Ufg35.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.CP8Hyldk.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.Bo_48MRA.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.Ch61sw-O.js} +1 -1
- package/web/_astro/channel.BKCmJpWM.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.CHpzS7xf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.DzMNNWIw.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.BqPrDw_Q.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ZzBHhNw-.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.1_O_LLkx.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.GS4UHg7_.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.BnqotN4g.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.Ghuh8zoJ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.BW9vC3zg.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.BW9vC3zg.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.D6jNC0ti.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.BOGoCs4L.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.8iU5VcWi.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.CVt88HwH.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.C9HxUUMl.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.4ywkQN3t.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.C48A6gpz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.DxFieQW4.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.CtjtQqYs.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.C5zAiahU.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.D_L6zsny.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.Ignnrk3V.js} +1 -1
- package/web/_astro/index.CbNz17Sx.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DLv8-P5u.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.p0OTpwFO.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.CIaBt-DD.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.DGySIntq.js} +1 -1
- package/web/_astro/{linear.zRsuuDTE.js → linear.BATV4RXu.js} +1 -1
- package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DzkwM3VX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.3piBRRzA.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DUNueLub.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.9bihVbrt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.CkjsC-YT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.Dbq4vaoQ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.BivrH99P.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.CqLQ1Vzz.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DvvoLD3b.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.B-KR78gv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.Ck0Eieb5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRfdd_do.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.Ck_Jxk9C.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
- package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
- package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
- package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
- package/plugins/sp/scripts/record-feature-sync.ts +0 -84
- package/plugins/sp/scripts/script-contract-check.ts +0 -506
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
- package/plugins/sp/scripts/task-size-precheck.ts +0 -212
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
- package/web/_astro/BoardApp.BxJuwD7I.js +0 -191
- package/web/_astro/channel.CI6N_tCg.js +0 -1
- package/web/_astro/index.CENnIEqT.css +0 -1
|
@@ -1,180 +1,56 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Unlike the subprocess path (`spur workflow run`), the interactive inline driver
|
|
6
|
-
* allocated a run id but never persisted an authoritative `runs` row, so bound
|
|
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 loads the source
|
|
9
|
-
* app service when the SPUR_BIN chain identifies a checkout, otherwise its generated bundle
|
|
10
|
-
* (`createOrAttachInlineRun` / `openInlineRunProjectDb` from packages/app), and lets it
|
|
11
|
-
* resolve the SAME project/registered/shared definition the engine would launch, compute the
|
|
12
|
-
* canonical definition digest with the exported hash machinery, and create-or-attach the
|
|
13
|
-
* run row through the existing engine persistence adapter.
|
|
14
|
-
*
|
|
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, 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.
|
|
19
|
-
*
|
|
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.
|
|
26
|
-
*
|
|
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
|
-
*
|
|
30
|
-
* Usage:
|
|
31
|
-
* bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]
|
|
32
|
-
* bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]
|
|
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>]
|
|
35
|
-
* --status <done|failed> --ok <true|false> --duration-ms <n> [--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>]
|
|
37
|
-
* bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]
|
|
38
|
-
*
|
|
39
|
-
* The `--fingerprint` mode prints the engine's proof-input digest for the given spec files and
|
|
40
|
-
* creates nothing (task 0862 R5).
|
|
41
|
-
*
|
|
42
|
-
* The `--action` / `--close` modes are the inline driver's ADR-117 emission boundary (task
|
|
43
|
-
* 0868): `--action` records one completed action boundary as an `action_runs` row through the
|
|
44
|
-
* shared `WorkflowActionTraceWriter`, `--close` marks the run row terminal through the same
|
|
45
|
-
* writer. `--action` is best-effort: a persistence failure is appended to
|
|
46
|
-
* `.spur/run/<run-id>.md` and the script still exits 0 with `{"ok":false}` on stdout, so
|
|
47
|
-
* observation never wedges the run. `--close` is NOT best-effort: the run-row closure is
|
|
48
|
-
* bookkeeping, so a missing run row or a persistence failure exits 1 with a named error.
|
|
49
|
-
* The `--decide` mode (task 0941 R5) executes the non-pausing decide action through the SAME
|
|
50
|
-
* app runner the engine registers (`runDecideForInlineRun`), writing the same resultFile row
|
|
51
|
-
* and recording the same `action_runs` trace row (`kind=decide`, best-effort). A degraded
|
|
52
|
-
* decision is still `ok: true` — the run continues — so exit 0 covers every model-level
|
|
53
|
-
* outcome; only an invalid options schema exits 1 (fail closed).
|
|
54
|
-
*
|
|
55
|
-
* The `--persist-out --from <worktree-path>` mode (task 0975 R1) is the WT-4a step a
|
|
56
|
-
* `--worktree` run executes BEFORE teardown: it copies the worktree's run provenance — the
|
|
57
|
-
* `runs`/`action_runs`/`phase_runs`/`transition_runs`/`workflow_states` rows and the two-file
|
|
58
|
-
* run records — into THIS tree's project DB and `.spur/run/` (cwd = the invoking tree).
|
|
59
|
-
* Repeatable `--task-file <path>` (0984 R2) forwards each merged task file so the app service
|
|
60
|
-
* also copies/verifies the `.spur/run/<file>` evidence that file cites; citation selection,
|
|
61
|
-
* safe-path validation and conflict behavior live entirely in the app service. Idempotent;
|
|
62
|
-
* collisions are reported as `skipped[{id,reason}]`, never overwrites. Exit 0 = persisted
|
|
63
|
-
* (possibly with skips); exit 1 = fail closed — an unreadable/unsafe/over-cap/unresolved
|
|
64
|
-
* citation or a divergent cited file fails the pass too — the driver routes to WT-5 and
|
|
65
|
-
* retains the worktree; usage errors exit 2.
|
|
66
|
-
*
|
|
67
|
-
* Exit 2 is reserved for usage errors.
|
|
68
|
-
*
|
|
69
|
-
* Env: SPUR_BIN
|
|
70
|
-
*/
|
|
71
|
-
|
|
2
|
+
/** Inline run setup driver (ADR-117/1006 R3): thin glue — argv/env, app-entry resolution, the installed-CLI
|
|
3
|
+
* handshake, mode dispatch. Mode bodies live in the bundled app service (`runInlineRun*` exports); the `.mjs`
|
|
4
|
+
* twin re-enters Bun in `main()` (SQLite) and imports the bundle dynamically, so bare `node` never loads app code. */
|
|
72
5
|
import { spawnSync } from 'node:child_process';
|
|
73
|
-
import {
|
|
74
|
-
appendFileSync,
|
|
75
|
-
closeSync,
|
|
76
|
-
existsSync,
|
|
77
|
-
mkdirSync,
|
|
78
|
-
openSync,
|
|
79
|
-
readFileSync,
|
|
80
|
-
renameSync,
|
|
81
|
-
unlinkSync,
|
|
82
|
-
writeFileSync,
|
|
83
|
-
writeSync,
|
|
84
|
-
} from 'node:fs';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
85
7
|
import { dirname, join, resolve } from 'node:path';
|
|
86
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
// Type-only namespace import (erased before bundling; the smoke scanner exempts `import type`).
|
|
10
|
+
import type * as spurApp from '@gobing-ai/spur-app';
|
|
87
11
|
import { getEnvVar } from '../lib/env';
|
|
88
12
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
readonly runId: string;
|
|
93
|
-
readonly attached?: boolean;
|
|
94
|
-
readonly definitionDigest?: string;
|
|
95
|
-
readonly workflowName?: string;
|
|
96
|
-
readonly workflowVersion?: string | null;
|
|
97
|
-
readonly resolvedPath?: string;
|
|
98
|
-
readonly layer?: string;
|
|
99
|
-
readonly workdir?: string;
|
|
100
|
-
readonly status?: string;
|
|
101
|
-
readonly error?: string;
|
|
102
|
-
}
|
|
13
|
+
// EMBEDDED_SPUR_SCHEMAS: declared by the bundle twin, never exported by the barrel — optional
|
|
14
|
+
// intersection member, exactly what the previous structural cast expressed.
|
|
15
|
+
type InlineApp = typeof spurApp & { EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string> };
|
|
103
16
|
|
|
104
17
|
function usage(): never {
|
|
105
18
|
console.error(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
'
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]',
|
|
117
|
-
);
|
|
118
|
-
console.error(
|
|
119
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]',
|
|
120
|
-
);
|
|
121
|
-
console.error(
|
|
122
|
-
' terminal-reason is a closed enum (0937 R2): done, paused-operator, failed-check, failed-agent, ' +
|
|
123
|
-
'failed-timeout, failed-guard, cancelled, interrupted, retry-exhausted',
|
|
124
|
-
);
|
|
125
|
-
console.error(
|
|
126
|
-
' bun plugins/sp/scripts/inline-run-setup.ts --decide --run-id <id> --node <state> --options-json <file> [--spur-bin <path>]',
|
|
19
|
+
[
|
|
20
|
+
'Usage: bun plugins/sp/scripts/inline-run-setup.ts --run-id <id> --file <definition> [--spur-bin <path>]',
|
|
21
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --fingerprint --task-file <path> [--feature-file <path>] [--spur-bin <path>]',
|
|
22
|
+
' 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>]',
|
|
23
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --actions-file <json-file> --run-id <id> [--spur-bin <path>] (1007 R5 batch trace emission)',
|
|
24
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --close --run-id <id> --status <done|failed|paused> [--reason <terminal-reason>] [--spur-bin <path>]',
|
|
25
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --persist-out --from <worktree-path> [--task-file <path>]... [--spur-bin <path>]',
|
|
26
|
+
' terminal-reason is a closed enum (0937 R2): done, paused-operator, failed-check, failed-agent, failed-timeout, failed-guard, cancelled, interrupted, retry-exhausted',
|
|
27
|
+
' bun plugins/sp/scripts/inline-run-setup.ts --decide --run-id <id> --node <state> --options-json <file> [--spur-bin <path>]',
|
|
28
|
+
].join('\n'),
|
|
127
29
|
);
|
|
128
30
|
process.exit(2);
|
|
129
31
|
}
|
|
130
32
|
|
|
131
|
-
|
|
132
|
-
* The run id becomes filenames under `.spur/run/` (`<run-id>.state.json` + `<run-id>.md`),
|
|
133
|
-
* so it must be a single safe filename component before anything is written — the same guard class the
|
|
134
|
-
* task-pipeline.yaml route-reason action applies to `$__runId` (task 0804 R8). The allowlist
|
|
135
|
-
* refuses path separators, dot traversal (leading `.`), unresolved interpolation (`$`/`{`/`}`) and
|
|
136
|
-
* every other shell/unspecified metachar; valid UUID/timestamp-slug ids pass.
|
|
137
|
-
*/
|
|
33
|
+
// Run ids become filenames under `.spur/run/` — single safe component only (0804 R8).
|
|
138
34
|
const SAFE_RUN_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
139
35
|
|
|
140
36
|
function refuseUnsafeRunId(runId: string): never {
|
|
141
|
-
// Refuse BEFORE any outcome write: an unsafe id must never reach
|
|
142
|
-
// `.spur/run/<run-id>.state.json` / `.md` (no traversal, no unintended file).
|
|
143
37
|
console.error(`inline-run-setup: refusing unsafe run id: ${runId}`);
|
|
144
38
|
console.error(
|
|
145
|
-
' The run id must be a single safe filename component (alphanumeric/._-, no leading dot, '
|
|
146
|
-
'no path separators, interpolation or traversal; same class as the task-pipeline ' +
|
|
147
|
-
'route-reason guard, task 0804 R8). Allocate a fresh run id (uuid or timestamp slug) and retry.',
|
|
39
|
+
' The run id must be a single safe filename component (alphanumeric/._-, no leading dot, no path separators, interpolation or traversal; same class as the task-pipeline route-reason guard, task 0804 R8). Allocate a fresh run id (uuid or timestamp slug) and retry.',
|
|
148
40
|
);
|
|
149
41
|
process.exit(1);
|
|
150
42
|
}
|
|
151
43
|
|
|
152
|
-
/**
|
|
153
|
-
* Resolve the spur repo checkout the same way the other prechecks resolve the CLI
|
|
154
|
-
* (--spur-bin > SPUR_BIN > monorepo-local CLI entry > PATH `spur`), then derive the repo
|
|
155
|
-
* root from the resolved main module. `bun <repo>/apps/cli/src/index.ts` → repo root is
|
|
156
|
-
* three levels up. A bundle-only install (`spur` on PATH, a bundled `spur.js`, or a spur
|
|
157
|
-
* binary without the app workspace) uses the adjacent generated application bundle.
|
|
158
|
-
*/
|
|
44
|
+
/** Resolve the app entry: --spur-bin/SPUR_BIN main module if it proves a repo checkout, else the adjacent bundle. */
|
|
159
45
|
function resolveAppEntry(spurBin: string): { entry: string; portable: boolean } {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
candidates = [spurBin];
|
|
163
|
-
} else {
|
|
164
|
-
// scripts/ -> plugins/sp/ -> <repo>/apps/cli/src/index.ts (fileURLToPath — raw
|
|
165
|
-
// pathname breaks on %-encoded paths, e.g. spaces in the checkout directory).
|
|
166
|
-
candidates = [fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url))];
|
|
167
|
-
}
|
|
46
|
+
const fallback = fileURLToPath(new URL('../../../apps/cli/src/index.ts', import.meta.url));
|
|
47
|
+
const candidates = spurBin !== '' ? [spurBin] : [fallback];
|
|
168
48
|
for (const candidate of candidates) {
|
|
169
49
|
const tokens = candidate.split(/\s+/).filter(Boolean);
|
|
170
|
-
//
|
|
171
|
-
// `bun run <path>` lead tokens). Only a TypeScript source entry proves a repo
|
|
172
|
-
// checkout; a bundled `spur.js` or a bare `spur` binary does not.
|
|
50
|
+
// Main module = last path-like token; only a .ts source entry proves a repo checkout.
|
|
173
51
|
const mainModule = [...tokens].reverse().find((t) => t.endsWith('.ts'));
|
|
174
52
|
if (mainModule === undefined || !existsSync(mainModule)) continue;
|
|
175
|
-
|
|
176
|
-
const srcDir = dirname(mainModule);
|
|
177
|
-
const repoRoot = resolve(srcDir, '..', '..', '..');
|
|
53
|
+
const repoRoot = resolve(dirname(mainModule), '..', '..', '..');
|
|
178
54
|
const appEntry = join(repoRoot, 'packages', 'app', 'src', 'index.ts');
|
|
179
55
|
if (existsSync(appEntry)) return { entry: appEntry, portable: false };
|
|
180
56
|
}
|
|
@@ -208,144 +84,12 @@ async function readInstalledInventory(file: string, spurBin: string): Promise<un
|
|
|
208
84
|
}
|
|
209
85
|
const value: unknown = JSON.parse(result.stdout);
|
|
210
86
|
// Honor the existing optional JSON envelope without inventing another projection format.
|
|
211
|
-
if (value && typeof value === 'object' && 'ok' in value && 'data' in value && value.ok === true)
|
|
212
|
-
return value.data;
|
|
213
|
-
}
|
|
87
|
+
if (value && typeof value === 'object' && 'ok' in value && 'data' in value && value.ok === true) return value.data;
|
|
214
88
|
return value;
|
|
215
89
|
}
|
|
216
90
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
* merges into `.spur/run/<run-id>.state.json` (atomic same-directory temp + rename, the
|
|
220
|
-
* 0925 R1 pattern) and `.spur/run/<run-id>.md` receives its run-start header exactly once.
|
|
221
|
-
* A re-setup of the same run id rewrites the state from the current outcome but keeps the
|
|
222
|
-
* prior `startedAt` instead of resurrecting setup time. Identity/provenance fields only —
|
|
223
|
-
* never prompt bodies — so the record carries no secret-bearing content by construction
|
|
224
|
-
* (0927 AC2). The retired `<run-id>-inline-setup.json` sidecar is not written (0927 R4);
|
|
225
|
-
* its readers were migrated to the pair state in the same change.
|
|
226
|
-
*/
|
|
227
|
-
function writeOutcome(runId: string, outcome: SetupOutcome): void {
|
|
228
|
-
const runDir = join(process.cwd(), '.spur', 'run');
|
|
229
|
-
if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
|
|
230
|
-
const statePath = join(runDir, `${runId}.state.json`);
|
|
231
|
-
const markdownPath = join(runDir, `${runId}.md`);
|
|
232
|
-
let prior: Record<string, unknown> = {};
|
|
233
|
-
try {
|
|
234
|
-
const parsed: unknown = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
235
|
-
if (parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
236
|
-
prior = parsed as Record<string, unknown>;
|
|
237
|
-
}
|
|
238
|
-
} catch {
|
|
239
|
-
// No prior state (new run) or unreadable file — the outcome alone defines the state.
|
|
240
|
-
}
|
|
241
|
-
const at = new Date().toISOString();
|
|
242
|
-
const state = {
|
|
243
|
-
schemaVersion: 1 as const,
|
|
244
|
-
runId,
|
|
245
|
-
...(outcome.workflowName !== undefined ? { workflowName: outcome.workflowName } : {}),
|
|
246
|
-
...(outcome.status !== undefined ? { status: outcome.status } : {}),
|
|
247
|
-
startedAt: typeof prior.startedAt === 'string' ? prior.startedAt : at,
|
|
248
|
-
updatedAt: at,
|
|
249
|
-
...(outcome.attached !== undefined ? { attached: outcome.attached } : {}),
|
|
250
|
-
...(outcome.definitionDigest !== undefined ? { definitionDigest: outcome.definitionDigest } : {}),
|
|
251
|
-
...(outcome.workflowVersion !== undefined ? { workflowVersion: outcome.workflowVersion } : {}),
|
|
252
|
-
...(outcome.resolvedPath !== undefined ? { resolvedPath: outcome.resolvedPath } : {}),
|
|
253
|
-
...(outcome.layer !== undefined ? { layer: outcome.layer } : {}),
|
|
254
|
-
...(outcome.workdir !== undefined ? { workdir: outcome.workdir } : {}),
|
|
255
|
-
// 0948 R7: project the setup outcome itself, so the retired `-inline-setup.json`
|
|
256
|
-
// sidecar's `ok` is still readable from the pair, and a SUCCESSFUL re-setup
|
|
257
|
-
// explicitly drops any prior `error` — a stale failure message must not outlive
|
|
258
|
-
// the failure it described (the state object is rebuilt, never merged with `prior`).
|
|
259
|
-
ok: outcome.ok,
|
|
260
|
-
...(outcome.ok === false && outcome.error !== undefined ? { error: outcome.error } : {}),
|
|
261
|
-
};
|
|
262
|
-
const temp = `${statePath}.tmp`;
|
|
263
|
-
try {
|
|
264
|
-
writeFileSync(temp, `${JSON.stringify(state, null, 4)}\n`);
|
|
265
|
-
renameSync(temp, statePath);
|
|
266
|
-
} catch {
|
|
267
|
-
// Best-effort: a failing record write must not wedge setup reporting — and never
|
|
268
|
-
// leaves `.tmp` residue behind (0926 R1).
|
|
269
|
-
try {
|
|
270
|
-
unlinkSync(temp);
|
|
271
|
-
} catch {
|
|
272
|
-
// Nothing to clean (temp was never created).
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// 0948 R7: create the header with `wx` (O_EXCL) instead of `existsSync` → `appendFileSync`.
|
|
276
|
-
// The check-then-append pair could append a SECOND header when two setups raced, and it
|
|
277
|
-
// kept "header present" as a separate fact from the identity write. One exclusive create
|
|
278
|
-
// makes exactly-one-header atomic; EEXIST just means it is already there.
|
|
279
|
-
let headerFd: number | undefined;
|
|
280
|
-
try {
|
|
281
|
-
headerFd = openSync(markdownPath, 'wx');
|
|
282
|
-
writeSync(
|
|
283
|
-
headerFd,
|
|
284
|
-
`# spur inline run ${runId} — ${outcome.workflowName ?? 'unknown workflow'} — setup ${at}\n`,
|
|
285
|
-
);
|
|
286
|
-
} catch {
|
|
287
|
-
// EEXIST (already written) or an unwritable path — the human header is not the setup
|
|
288
|
-
// identity, so this stays best-effort exactly like the state write above.
|
|
289
|
-
} finally {
|
|
290
|
-
if (headerFd !== undefined) closeSync(headerFd);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* `--fingerprint` mode (task 0862 R5): print the engine's proof-input digest for a task file
|
|
296
|
-
* (plus optional feature file) instead of creating a run, so the inline driver can capture the
|
|
297
|
-
* fresh digest bound `run.artifact` registration checks against.
|
|
298
|
-
*
|
|
299
|
-
* The digest itself comes from the exported app functions — never reimplemented here — and the
|
|
300
|
-
* options mirror `ProofFingerprintActionRunner` (cwd, spec contents, fileSystem; no `--expect`).
|
|
301
|
-
* Returns the process exit code: 0 printed, 1 read/resolve failure.
|
|
302
|
-
*/
|
|
303
|
-
async function printFingerprint(taskFile: string, featureFile: string, spurBin: string): Promise<number> {
|
|
304
|
-
const { entry } = resolveAppEntry(spurBin);
|
|
305
|
-
const app = (await import(entry)) as {
|
|
306
|
-
computeProofInputFingerprint: (options: Record<string, unknown>) => Promise<string>;
|
|
307
|
-
readProofInputContents: (
|
|
308
|
-
fileSystem: unknown,
|
|
309
|
-
workdir: string,
|
|
310
|
-
options: { taskFile?: unknown; featureFile?: unknown },
|
|
311
|
-
) => Promise<{ ok: true; taskContent?: string; featureContent?: string } | { ok: false; error: string }>;
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const workdir = process.cwd();
|
|
315
|
-
// `undefined` fs takes readProofInputContents' node-filesystem default — the same default its
|
|
316
|
-
// sibling createGitAlternateTree applies, and the same Node FS the CLI's runner injects.
|
|
317
|
-
const inputs = await app.readProofInputContents(undefined, workdir, {
|
|
318
|
-
taskFile,
|
|
319
|
-
...(featureFile.trim() !== '' ? { featureFile } : {}),
|
|
320
|
-
});
|
|
321
|
-
if (!inputs.ok) {
|
|
322
|
-
console.error(`inline-run-setup: FAIL — ${inputs.error}`);
|
|
323
|
-
return 1;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const digest = await app.computeProofInputFingerprint({
|
|
327
|
-
cwd: workdir,
|
|
328
|
-
...(inputs.taskContent !== undefined ? { taskContent: inputs.taskContent } : {}),
|
|
329
|
-
...(inputs.featureContent !== undefined ? { featureContent: inputs.featureContent } : {}),
|
|
330
|
-
});
|
|
331
|
-
process.stdout.write(`${digest}\n`);
|
|
332
|
-
return 0;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/** Terminal statuses the inline driver may declare when closing its run row. */
|
|
336
|
-
const CLOSE_STATUSES: ReadonlySet<string> = new Set<'done' | 'failed' | 'paused'>(['done', 'failed', 'paused']);
|
|
337
|
-
|
|
338
|
-
/** Narrow an argv `--status` to the close vocabulary (`CLOSE_STATUSES`). */
|
|
339
|
-
function isCloseStatus(status: string): status is 'done' | 'failed' | 'paused' {
|
|
340
|
-
return CLOSE_STATUSES.has(status);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* 0937 R2: closed terminal-reason vocabulary the driver may declare on `--close`.
|
|
345
|
-
* COPIED from packages/app/src/workflow/terminal-reason.ts — the plugin standalone
|
|
346
|
-
* contract forbids a value import of app code; the parity test asserts the copy
|
|
347
|
-
* equals the app export.
|
|
348
|
-
*/
|
|
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.
|
|
349
93
|
const TERMINAL_REASONS = new Set([
|
|
350
94
|
'done',
|
|
351
95
|
'paused-operator',
|
|
@@ -358,278 +102,6 @@ const TERMINAL_REASONS = new Set([
|
|
|
358
102
|
'retry-exhausted',
|
|
359
103
|
]);
|
|
360
104
|
|
|
361
|
-
/** Finalize statuses — a finish emission is terminal, so only done|failed are valid (0868 #4). */
|
|
362
|
-
const ACTION_STATUSES: ReadonlySet<string> = new Set<'done' | 'failed'>(['done', 'failed']);
|
|
363
|
-
|
|
364
|
-
/** Narrow an argv `--status` to the finalize vocabulary (`ACTION_STATUSES`). */
|
|
365
|
-
function isActionStatus(status: string): status is 'done' | 'failed' {
|
|
366
|
-
return ACTION_STATUSES.has(status);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** Input for the ADR-117 emission modes (`--action` / `--close`). */
|
|
370
|
-
import type { WorkflowActionTraceWriter } from '@gobing-ai/spur-app';
|
|
371
|
-
|
|
372
|
-
interface TraceModeInput {
|
|
373
|
-
readonly runId: string;
|
|
374
|
-
readonly close: boolean;
|
|
375
|
-
readonly node: string;
|
|
376
|
-
readonly kind: string;
|
|
377
|
-
/** Declared terminal reason (0937 R2) — validated against TERMINAL_REASONS before this point. */
|
|
378
|
-
readonly reason?: string;
|
|
379
|
-
/**
|
|
380
|
-
* Trace status: the finalize vocabulary plus the close-only `paused` (`CLOSE_STATUSES`).
|
|
381
|
-
* Both are assignable to the engine's `WorkflowStatus`; the plugin cannot import that
|
|
382
|
-
* type (standalone contract), so the literal union stands in for it.
|
|
383
|
-
*/
|
|
384
|
-
readonly status: 'done' | 'failed' | 'paused';
|
|
385
|
-
readonly ok: boolean;
|
|
386
|
-
readonly durationMs: number;
|
|
387
|
-
readonly spurBin: string;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* The run-record file for appended driver lines (task 0927 R1): `.spur/run/<run-id>.md`
|
|
392
|
-
* for pair-based runs. A legacy run that predates the pair keeps appending to its declared
|
|
393
|
-
* `.spur/run/<run-id>.log` — the same precedence as `readWorkflowRunRecord` (the pair wins,
|
|
394
|
-
* legacy stays readable in place).
|
|
395
|
-
*/
|
|
396
|
-
function runRecordLogPath(runDir: string, runId: string): string {
|
|
397
|
-
const markdownPath = join(runDir, `${runId}.md`);
|
|
398
|
-
const legacyLogPath = join(runDir, `${runId}.log`);
|
|
399
|
-
if (existsSync(legacyLogPath) && !existsSync(markdownPath)) return legacyLogPath;
|
|
400
|
-
return markdownPath;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Append one emission-failure line to the run record markdown — `.spur/run/<run-id>.md`,
|
|
405
|
-
* the run log the inline driver already owns. Best-effort and synchronous (the process may
|
|
406
|
-
* exit immediately after), and never throws: an unwritable log must not wedge the run
|
|
407
|
-
* (ADR-117 R3).
|
|
408
|
-
*/
|
|
409
|
-
function appendRunLogLine(runId: string, detail: string): void {
|
|
410
|
-
try {
|
|
411
|
-
const runDir = join(process.cwd(), '.spur', 'run');
|
|
412
|
-
if (!existsSync(runDir)) mkdirSync(runDir, { recursive: true });
|
|
413
|
-
const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
414
|
-
const stamp = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
415
|
-
appendFileSync(runRecordLogPath(runDir, safeRunId), `[${stamp}] ${detail}\n`);
|
|
416
|
-
} catch {
|
|
417
|
-
// Best-effort (R3): the run continues even when the failure cannot be recorded.
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Emit one trace write through the SHARED `WorkflowActionTraceWriter` (task 0868 R5/R7).
|
|
423
|
-
* `--action` is best-effort: an emission failure is recorded to the run log and reported
|
|
424
|
-
* on stdout as `{"ok":false}`, and the script exits 0 so the run still reaches its declared
|
|
425
|
-
* terminal state (R3/R12). `--close` is bookkeeping, so a missing run row or a persistence
|
|
426
|
-
* failure fails loudly with exit 1 (review findings #1/#4).
|
|
427
|
-
*/
|
|
428
|
-
async function runTraceMode(input: TraceModeInput): Promise<number> {
|
|
429
|
-
const operation = input.close ? 'run.close' : 'action.finish';
|
|
430
|
-
const fail = (error: string): number => {
|
|
431
|
-
appendRunLogLine(
|
|
432
|
-
input.runId,
|
|
433
|
-
`trace-emission-failed operation=${operation} run=${input.runId}` +
|
|
434
|
-
`${input.node === '' ? '' : ` node=${input.node}`}${input.kind === '' ? '' : ` kind=${input.kind}`}: ${error}`,
|
|
435
|
-
);
|
|
436
|
-
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}\n`);
|
|
437
|
-
// The action boundary is best-effort (exit 0); the run-row closure fails loudly (exit 1).
|
|
438
|
-
return input.close ? 1 : 0;
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
// Compile-time link (0868 finding #5): the writer half is typed by the real packages/app
|
|
442
|
-
// export (type-only import, erased at runtime) so a signature drift breaks THIS file's
|
|
443
|
-
// typecheck instead of hiding behind the hand-declared cast.
|
|
444
|
-
let projectDb: { adapter: unknown; close: () => void } | undefined;
|
|
445
|
-
try {
|
|
446
|
-
const { entry } = resolveAppEntry(input.spurBin);
|
|
447
|
-
const app = (await import(entry)) as {
|
|
448
|
-
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
449
|
-
createWorkflowActionTraceWriter: (
|
|
450
|
-
db: unknown,
|
|
451
|
-
recordFailure?: (failure: unknown) => void,
|
|
452
|
-
) => WorkflowActionTraceWriter;
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
projectDb = await app.openInlineRunProjectDb(process.cwd());
|
|
456
|
-
const writer = app.createWorkflowActionTraceWriter(projectDb.adapter, (failure: unknown) => {
|
|
457
|
-
const detail = failure as { operation?: string; error?: string };
|
|
458
|
-
appendRunLogLine(
|
|
459
|
-
input.runId,
|
|
460
|
-
`trace-emission-failed operation=${detail.operation ?? operation} run=${input.runId}: ${detail.error ?? 'unknown error'}`,
|
|
461
|
-
);
|
|
462
|
-
});
|
|
463
|
-
const result = (
|
|
464
|
-
input.close
|
|
465
|
-
? await writer.closeRun(input.runId, input.status, undefined, input.reason)
|
|
466
|
-
: await writer.recordAction({
|
|
467
|
-
runId: input.runId,
|
|
468
|
-
node: input.node,
|
|
469
|
-
kind: input.kind,
|
|
470
|
-
status: input.status,
|
|
471
|
-
ok: input.ok,
|
|
472
|
-
durationMs: input.durationMs,
|
|
473
|
-
})
|
|
474
|
-
) as Record<string, unknown>;
|
|
475
|
-
if (result.ok !== true && result.failure !== undefined) {
|
|
476
|
-
// One stdout shape for emission failures (0868 finding #1): flatten the guard's
|
|
477
|
-
// nested failure object to the same `{ok, runId, error}` the direct paths emit.
|
|
478
|
-
const failure = result.failure as { error?: string };
|
|
479
|
-
return fail(failure.error ?? 'unknown trace emission failure');
|
|
480
|
-
}
|
|
481
|
-
if (input.close && input.status === 'done' && result.actionRows === 0) {
|
|
482
|
-
// A run finalized `done` with ZERO recorded action rows is a bookkeeping defect
|
|
483
|
-
// (task 0975 R2): the row is already terminal — closeRun ran above — but the
|
|
484
|
-
// driver must surface this instead of reporting a clean close, and must never
|
|
485
|
-
// backfill rows. Exit 1 with the named code; the run record carries the finding.
|
|
486
|
-
const error = `run ${input.runId} closed done with zero action_runs rows`;
|
|
487
|
-
appendRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${error}`);
|
|
488
|
-
process.stdout.write(
|
|
489
|
-
`${JSON.stringify({ ok: false, runId: input.runId, error, code: 'NO_ACTION_ROWS', actionRows: 0 })}\n`,
|
|
490
|
-
);
|
|
491
|
-
return 1;
|
|
492
|
-
}
|
|
493
|
-
process.stdout.write(`${JSON.stringify({ ...result, runId: input.runId })}\n`);
|
|
494
|
-
return 0;
|
|
495
|
-
} catch (error) {
|
|
496
|
-
if (input.close && (error as { name?: string }).name === 'RunRowNotFoundError') {
|
|
497
|
-
// The run row must exist before --close can mark it terminal (R6); a missing row
|
|
498
|
-
// is a loud correctness failure, not a best-effort emission failure (finding #4).
|
|
499
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
500
|
-
appendRunLogLine(input.runId, `trace-close-failed run=${input.runId}: ${message}`);
|
|
501
|
-
process.stdout.write(
|
|
502
|
-
`${JSON.stringify({ ok: false, runId: input.runId, error: message, code: 'RUN_NOT_FOUND' })}\n`,
|
|
503
|
-
);
|
|
504
|
-
return 1;
|
|
505
|
-
}
|
|
506
|
-
return fail(error instanceof Error ? error.message : String(error));
|
|
507
|
-
} finally {
|
|
508
|
-
projectDb?.close();
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* `--decide` mode (task 0941 R5): execute the non-pausing decide action through the same app
|
|
514
|
-
* function the engine registers (`runDecideForInlineRun` → `DecideActionRunner`), then record
|
|
515
|
-
* the `action_runs` trace row through the shared writer. The decide-enabled switch
|
|
516
|
-
* (`workflow.decideDecisionMaker`) is resolved HERE at the driver boundary (ADR-082) through
|
|
517
|
-
* the bridged facade derivation and passed into the app runner as an explicit parameter —
|
|
518
|
-
* app services never load Spur config. Degraded outcomes (`disabled`,
|
|
519
|
-
* `no-backend`, `error`, `timeout`, `low-confidence`) are normal — the value is the declared
|
|
520
|
-
* default and the run continues — so they print `ok: true` and exit 0. Only an invalid options
|
|
521
|
-
* schema, an unreadable options file, or a failed config load fails closed with exit 1.
|
|
522
|
-
*/
|
|
523
|
-
async function runDecideMode(input: {
|
|
524
|
-
runId: string;
|
|
525
|
-
node: string;
|
|
526
|
-
optionsFile: string;
|
|
527
|
-
spurBin: string;
|
|
528
|
-
}): Promise<number> {
|
|
529
|
-
type DecideOutcome = {
|
|
530
|
-
ok: boolean;
|
|
531
|
-
error?: string;
|
|
532
|
-
value?: string;
|
|
533
|
-
degraded?: boolean;
|
|
534
|
-
reason?: string;
|
|
535
|
-
source?: 'model' | 'default';
|
|
536
|
-
backend?: string | null;
|
|
537
|
-
confidence?: number | null;
|
|
538
|
-
resultFile?: string;
|
|
539
|
-
durationMs?: number;
|
|
540
|
-
};
|
|
541
|
-
const decideFailed = (error: string): number => {
|
|
542
|
-
process.stdout.write(`${JSON.stringify({ ok: false, runId: input.runId, error })}\n`);
|
|
543
|
-
return 1;
|
|
544
|
-
};
|
|
545
|
-
let outcome: DecideOutcome;
|
|
546
|
-
try {
|
|
547
|
-
const { entry, portable } = resolveAppEntry(input.spurBin);
|
|
548
|
-
const app = (await import(entry)) as {
|
|
549
|
-
runDecideForInlineRun: (input2: {
|
|
550
|
-
workdir: string;
|
|
551
|
-
optionsFile: string;
|
|
552
|
-
enabled: boolean;
|
|
553
|
-
}) => Promise<DecideOutcome>;
|
|
554
|
-
};
|
|
555
|
-
// Config switch at the driver boundary (ADR-082 / task 0941 gate fix): the flag comes
|
|
556
|
-
// from the bridged facade derivation in the generated lib — the same committed bundle
|
|
557
|
-
// the inventory flow imports — never a @gobing-ai/* value import. Embedded schemas
|
|
558
|
-
// ride along only in the portable layout, mirroring the setup mode's load posture.
|
|
559
|
-
const lib = (await import(
|
|
560
|
-
fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url))
|
|
561
|
-
)) as typeof import('../lib/inline-run.generated.mjs');
|
|
562
|
-
const enabled = await lib.resolveDecideDecisionMakerEnabled(
|
|
563
|
-
process.cwd(),
|
|
564
|
-
portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined,
|
|
565
|
-
);
|
|
566
|
-
outcome = await app.runDecideForInlineRun({
|
|
567
|
-
workdir: process.cwd(),
|
|
568
|
-
optionsFile: input.optionsFile,
|
|
569
|
-
enabled,
|
|
570
|
-
});
|
|
571
|
-
} catch (error) {
|
|
572
|
-
return decideFailed(error instanceof Error ? error.message : String(error));
|
|
573
|
-
}
|
|
574
|
-
if (!outcome.ok) return decideFailed(outcome.error ?? 'decide failed without an error message');
|
|
575
|
-
process.stdout.write(`${JSON.stringify({ runId: input.runId, node: input.node, ...outcome, ok: true })}\n`);
|
|
576
|
-
// 0976 R2: the run log names the decision's provenance, so a declared-default fallback is
|
|
577
|
-
// never read as a model decision. Best-effort through the same run-log appender.
|
|
578
|
-
appendRunLogLine(
|
|
579
|
-
input.runId,
|
|
580
|
-
`decide node=${input.node} value=${outcome.value ?? ''} source=${outcome.source ?? 'default'} reason=${outcome.reason ?? ''}`,
|
|
581
|
-
);
|
|
582
|
-
// Trace row is best-effort, exactly like --action: an emission failure never wedges the run.
|
|
583
|
-
return await runTraceMode({
|
|
584
|
-
runId: input.runId,
|
|
585
|
-
close: false,
|
|
586
|
-
node: input.node,
|
|
587
|
-
kind: 'decide',
|
|
588
|
-
status: 'done',
|
|
589
|
-
ok: true,
|
|
590
|
-
durationMs: outcome.durationMs ?? 0,
|
|
591
|
-
spurBin: input.spurBin,
|
|
592
|
-
});
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* `--persist-out --from <worktree-path>` (task 0975 R1; `--task-file` per 0984 R2): resolve
|
|
597
|
-
* the app through the shared chain and copy the worktree's inline-run provenance into THIS
|
|
598
|
-
* tree. No persistence policy lives here — the app's `persistWorktreeRuns` (→ domain
|
|
599
|
-
* `transferRunTables`) owns the DB transfer, the citation selection/copy rules, the
|
|
600
|
-
* idempotence and the conflict skips; this delegate only forwards the merged task file
|
|
601
|
-
* paths. Success prints `{ok:true,persisted:<n>,skipped:[…]}` and exits 0; any failure
|
|
602
|
-
* prints `{ok:false,error}` and exits 1 (the driver routes to WT-5 and retains the
|
|
603
|
-
* worktree).
|
|
604
|
-
*/
|
|
605
|
-
async function runPersistOutMode(input: {
|
|
606
|
-
from: string;
|
|
607
|
-
taskFiles: readonly string[];
|
|
608
|
-
spurBin: string;
|
|
609
|
-
}): Promise<number> {
|
|
610
|
-
try {
|
|
611
|
-
const { entry } = resolveAppEntry(input.spurBin);
|
|
612
|
-
const app = (await import(entry)) as {
|
|
613
|
-
persistWorktreeRuns: (i: {
|
|
614
|
-
fromWorkdir: string;
|
|
615
|
-
toWorkdir: string;
|
|
616
|
-
taskFiles?: readonly string[];
|
|
617
|
-
}) => Promise<{ ok: true; persisted: number; skipped: ReadonlyArray<{ id: string; reason: string }> }>;
|
|
618
|
-
};
|
|
619
|
-
const result = await app.persistWorktreeRuns({
|
|
620
|
-
fromWorkdir: input.from,
|
|
621
|
-
toWorkdir: process.cwd(),
|
|
622
|
-
...(input.taskFiles.length > 0 ? { taskFiles: input.taskFiles } : {}),
|
|
623
|
-
});
|
|
624
|
-
process.stdout.write(`${JSON.stringify({ ok: true, persisted: result.persisted, skipped: result.skipped })}\n`);
|
|
625
|
-
return 0;
|
|
626
|
-
} catch (error) {
|
|
627
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
628
|
-
process.stdout.write(`${JSON.stringify({ ok: false, error: message })}\n`);
|
|
629
|
-
return 1;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
105
|
async function main(): Promise<void> {
|
|
634
106
|
// The portable Node twin has no workspace imports; SQLite still uses Spur's existing Bun runtime.
|
|
635
107
|
if (!process.versions.bun) {
|
|
@@ -639,92 +111,95 @@ async function main(): Promise<void> {
|
|
|
639
111
|
if (child.error) console.error(`inline-run-setup requires Bun on PATH: ${child.error.message}`);
|
|
640
112
|
process.exit(child.status ?? 1);
|
|
641
113
|
}
|
|
642
|
-
|
|
643
|
-
let
|
|
644
|
-
|
|
114
|
+
const flags = new Map<string, string>();
|
|
115
|
+
let fingerprint = false,
|
|
116
|
+
action = false,
|
|
117
|
+
close = false,
|
|
118
|
+
decide = false,
|
|
119
|
+
persistOut = false;
|
|
645
120
|
const taskFiles: string[] = [];
|
|
646
|
-
let featureFile = '';
|
|
647
|
-
let action = false;
|
|
648
|
-
let close = false;
|
|
649
|
-
let decide = false;
|
|
650
|
-
let persistOut = false;
|
|
651
|
-
let from = '';
|
|
652
|
-
let optionsJson = '';
|
|
653
|
-
let node = '';
|
|
654
|
-
let kind = '';
|
|
655
|
-
let status = '';
|
|
656
|
-
let reason = '';
|
|
657
|
-
let okRaw = '';
|
|
658
|
-
let durationRaw = '';
|
|
659
121
|
let spurBin = getEnvVar('SPUR_BIN') ?? '';
|
|
660
122
|
const argv = process.argv.slice(2);
|
|
661
123
|
for (let i = 0; i < argv.length; i++) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
else if (
|
|
665
|
-
else if (
|
|
666
|
-
else if (
|
|
667
|
-
else if (
|
|
668
|
-
else if (
|
|
669
|
-
else if (
|
|
670
|
-
else if (argv[i]
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
124
|
+
const flag = argv[i];
|
|
125
|
+
if (flag === '--fingerprint') fingerprint = true;
|
|
126
|
+
else if (flag === '--action') action = true;
|
|
127
|
+
else if (flag === '--close') close = true;
|
|
128
|
+
else if (flag === '--decide') decide = true;
|
|
129
|
+
else if (flag === '--persist-out') persistOut = true;
|
|
130
|
+
else if (flag === '--task-file') taskFiles.push(argv[++i] ?? '');
|
|
131
|
+
else if (flag === '--spur-bin') spurBin = argv[++i] ?? spurBin;
|
|
132
|
+
else if (flag !== undefined) flags.set(flag, argv[++i] ?? '');
|
|
133
|
+
}
|
|
134
|
+
// Repeated value flags: last occurrence wins (Map.set), matching the previous assignment chain.
|
|
135
|
+
const runId = flags.get('--run-id') ?? '';
|
|
136
|
+
const file = flags.get('--file') ?? '';
|
|
137
|
+
const featureFile = flags.get('--feature-file') ?? '';
|
|
138
|
+
const from = flags.get('--from') ?? '';
|
|
139
|
+
const optionsJson = flags.get('--options-json') ?? '';
|
|
140
|
+
const node = flags.get('--node') ?? '';
|
|
141
|
+
const kind = flags.get('--kind') ?? '';
|
|
142
|
+
const status = flags.get('--status') ?? '';
|
|
143
|
+
const reason = flags.get('--reason') ?? '';
|
|
144
|
+
const okRaw = flags.get('--ok') ?? '';
|
|
145
|
+
const durationRaw = flags.get('--duration-ms') ?? '';
|
|
146
|
+
const actionsFile = flags.get('--actions-file') ?? '';
|
|
681
147
|
|
|
682
|
-
// Two mutually exclusive modes share this entry point: create/attach a run (run-id + file), or
|
|
683
|
-
// print the proof digest for the inline driver (task 0862 R5). Mixing them is a usage error.
|
|
684
|
-
// `--task-file` belongs to the fingerprint (exactly one) and persist-out (zero or more) modes.
|
|
685
148
|
if (fingerprint) {
|
|
686
149
|
if (runId !== '' || file !== '' || taskFiles.length !== 1 || (taskFiles[0] ?? '').trim() === '') usage();
|
|
687
|
-
|
|
150
|
+
const app = (await import(resolveAppEntry(spurBin).entry)) as InlineApp;
|
|
151
|
+
process.exit(await app.runInlineRunFingerprint({ taskFile: taskFiles[0] ?? '', featureFile }));
|
|
152
|
+
}
|
|
153
|
+
|
|
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 }));
|
|
688
161
|
}
|
|
689
162
|
|
|
690
|
-
// ADR-117 emission modes (task 0868): the inline driver reports one completed action
|
|
691
|
-
// boundary, or closes its run row at the declared terminal state. Both share the run-id
|
|
692
|
-
// filename guard, the app-entry resolution chain and the best-effort failure contract.
|
|
693
|
-
// Non-pausing decide action (0941 R5): --decide is mutually exclusive with the run-setup,
|
|
694
|
-
// fingerprint, and trace modes; it requires the run id (filename-guarded), the state id
|
|
695
|
-
// for the trace row, and the options JSON file.
|
|
696
163
|
if (decide) {
|
|
697
164
|
if (action || close || fingerprint || file !== '' || taskFiles.length > 0) usage();
|
|
698
165
|
if (runId.trim() === '' || node.trim() === '' || optionsJson.trim() === '') usage();
|
|
699
166
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
700
|
-
|
|
167
|
+
try {
|
|
168
|
+
const { entry, portable } = resolveAppEntry(spurBin);
|
|
169
|
+
const app = (await import(entry)) as InlineApp;
|
|
170
|
+
const bundlePath = fileURLToPath(new URL('../lib/inline-run.generated.mjs', import.meta.url));
|
|
171
|
+
const lib = (await import(bundlePath)) as typeof import('../lib/inline-run.generated.mjs');
|
|
172
|
+
// Decide-enabled switch resolved at the driver boundary (ADR-082), passed as a param.
|
|
173
|
+
const enabled = await lib.resolveDecideDecisionMakerEnabled(
|
|
174
|
+
process.cwd(),
|
|
175
|
+
portable ? { embeddedSchemas: lib.EMBEDDED_SPUR_SCHEMAS } : undefined,
|
|
176
|
+
);
|
|
177
|
+
process.exit(await app.runInlineRunDecide({ runId, node, optionsFile: optionsJson, enabled }));
|
|
178
|
+
} catch (error) {
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
process.stdout.write(`${JSON.stringify({ ok: false, runId, error: message })}\n`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
701
183
|
}
|
|
702
184
|
|
|
703
|
-
// Worktree provenance persist-out (task 0975 R1): mutually exclusive with every other
|
|
704
|
-
// mode; requires the source worktree path and accepts zero or more merged task files
|
|
705
|
-
// (0984 R2) whose cited `.spur/run/` evidence the app service copies/verifies. Runs
|
|
706
|
-
// with cwd = the invoking tree.
|
|
707
185
|
if (persistOut) {
|
|
708
186
|
if (fingerprint || decide || action || close || runId !== '' || file !== '') usage();
|
|
709
187
|
if (from.trim() === '' || taskFiles.some((taskFile) => taskFile.trim() === '')) usage();
|
|
710
|
-
|
|
188
|
+
const app = (await import(resolveAppEntry(spurBin).entry)) as InlineApp;
|
|
189
|
+
process.exit(await app.runInlineRunPersistOut({ from, taskFiles }));
|
|
711
190
|
}
|
|
712
191
|
|
|
713
192
|
if (action || close) {
|
|
714
193
|
if (action && close) usage();
|
|
715
194
|
if (runId.trim() === '' || status.trim() === '') usage();
|
|
716
195
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
196
|
+
const app = (await import(resolveAppEntry(spurBin).entry)) as InlineApp;
|
|
717
197
|
if (close) {
|
|
718
|
-
if (!
|
|
719
|
-
// 0937 R2:
|
|
720
|
-
|
|
721
|
-
if (reason.trim() === '') {
|
|
722
|
-
if (status === 'failed') usage();
|
|
723
|
-
} else if (!TERMINAL_REASONS.has(reason)) {
|
|
724
|
-
usage();
|
|
725
|
-
}
|
|
198
|
+
if (!app.isInlineRunCloseStatus(status)) usage();
|
|
199
|
+
// 0937 R2: failed close needs a declared closed-enum reason — before any write.
|
|
200
|
+
if (reason.trim() === '' ? status === 'failed' : !TERMINAL_REASONS.has(reason)) usage();
|
|
726
201
|
process.exit(
|
|
727
|
-
await
|
|
202
|
+
await app.runInlineRunTrace({
|
|
728
203
|
runId,
|
|
729
204
|
close: true,
|
|
730
205
|
node: '',
|
|
@@ -732,79 +207,41 @@ async function main(): Promise<void> {
|
|
|
732
207
|
status,
|
|
733
208
|
ok: true,
|
|
734
209
|
durationMs: 0,
|
|
735
|
-
spurBin,
|
|
736
210
|
...(reason.trim() === '' ? {} : { reason }),
|
|
737
211
|
}),
|
|
738
212
|
);
|
|
739
213
|
}
|
|
740
214
|
if (node.trim() === '' || kind.trim() === '') usage();
|
|
741
|
-
if (!
|
|
742
|
-
// `--ok` and `--duration-ms` are required and exact
|
|
743
|
-
// (review finding #2): a miscased `--ok True` or an omitted `--duration-ms`
|
|
744
|
-
// must be a loud usage error, never a silently-defaulted `ok=0` /
|
|
745
|
-
// `duration_ms=0` row.
|
|
215
|
+
if (!app.isInlineRunActionStatus(status)) usage();
|
|
216
|
+
// `--ok` and `--duration-ms` are required and exact (0868 #2): no silent defaults.
|
|
746
217
|
if (okRaw !== 'true' && okRaw !== 'false') usage();
|
|
747
|
-
const ok = okRaw === 'true';
|
|
748
218
|
const durationMs = Number(durationRaw);
|
|
749
219
|
if (durationRaw.trim() === '' || !Number.isFinite(durationMs) || durationMs < 0) usage();
|
|
750
|
-
process.exit(
|
|
220
|
+
process.exit(
|
|
221
|
+
await app.runInlineRunTrace({ runId, close: false, node, kind, status, ok: okRaw === 'true', durationMs }),
|
|
222
|
+
);
|
|
751
223
|
}
|
|
752
224
|
|
|
753
225
|
if (runId.trim() === '' || file.trim() === '') usage();
|
|
754
226
|
if (!SAFE_RUN_ID_RE.test(runId)) refuseUnsafeRunId(runId);
|
|
755
|
-
|
|
756
227
|
const { entry, portable } = resolveAppEntry(spurBin);
|
|
757
|
-
|
|
758
|
-
// Dynamic import by absolute path: the app source graph resolves its own workspace
|
|
759
|
-
// dependencies from the repo checkout, never from this plugin script's location.
|
|
760
|
-
const app = (await import(entry)) as {
|
|
761
|
-
createOrAttachInlineRun: (input: {
|
|
762
|
-
workdir: string;
|
|
763
|
-
getDb: () => Promise<unknown>;
|
|
764
|
-
file: string;
|
|
765
|
-
runId: string;
|
|
766
|
-
inventory?: unknown;
|
|
767
|
-
embeddedSchemas?: ReadonlyMap<string, string>;
|
|
768
|
-
}) => Promise<SetupOutcome & { ok: boolean }>;
|
|
769
|
-
openInlineRunProjectDb: (workdir: string) => Promise<{ adapter: unknown; close: () => void }>;
|
|
770
|
-
EMBEDDED_SPUR_SCHEMAS?: ReadonlyMap<string, string>;
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
const workdir = process.cwd();
|
|
228
|
+
const app = (await import(entry)) as InlineApp;
|
|
774
229
|
let inventory: unknown;
|
|
775
230
|
try {
|
|
776
231
|
inventory = await readInstalledInventory(file, spurBin);
|
|
777
232
|
} catch (error) {
|
|
778
233
|
const message = `could not resolve the workflow definition: ${error instanceof Error ? error.message : String(error)}`;
|
|
779
|
-
|
|
234
|
+
app.writeInlineRunOutcome(runId, { ok: false, runId, error: message });
|
|
780
235
|
throw new Error(message);
|
|
781
236
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
try {
|
|
785
|
-
const result = await app.createOrAttachInlineRun({
|
|
786
|
-
workdir,
|
|
787
|
-
getDb: async () => projectDb.adapter,
|
|
788
|
-
file,
|
|
237
|
+
process.exit(
|
|
238
|
+
await app.runInlineRunSetup({
|
|
789
239
|
runId,
|
|
240
|
+
file,
|
|
790
241
|
inventory,
|
|
791
242
|
...(portable ? { embeddedSchemas: app.EMBEDDED_SPUR_SCHEMAS } : {}),
|
|
792
|
-
})
|
|
793
|
-
|
|
794
|
-
if (!result.ok) {
|
|
795
|
-
console.error(`inline-run-setup: FAIL for run ${runId}`);
|
|
796
|
-
console.error(` ${result.error}`);
|
|
797
|
-
exitCode = 1;
|
|
798
|
-
} else {
|
|
799
|
-
console.error(
|
|
800
|
-
`inline-run-setup: ${result.attached ? 'attached' : 'created'} run ${runId} ` +
|
|
801
|
-
`(${result.workflowName}, layer ${result.layer}, digest ${result.definitionDigest}, status ${result.status})`,
|
|
802
|
-
);
|
|
803
|
-
}
|
|
804
|
-
} finally {
|
|
805
|
-
projectDb.close();
|
|
806
|
-
}
|
|
807
|
-
process.exit(exitCode);
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
808
245
|
}
|
|
809
246
|
|
|
810
247
|
main().catch((e: unknown) => {
|