@gobing-ai/spur 0.3.66 → 0.3.68
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/corpus-baseline.json +463 -10507
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +21 -16
- package/config/workflow-composition-baseline.json +109 -59
- package/config/workflows/docs-pipeline.yaml +98 -22
- package/config/workflows/idea-pipeline.yaml +20 -22
- package/config/workflows/task-pipeline.yaml +207 -108
- package/package.json +9 -9
- package/plugins/sp/README.md +6 -7
- package/plugins/sp/agents/expert-spur.md +61 -88
- package/plugins/sp/commands/dev-idea.md +5 -3
- package/plugins/sp/commands/dev-plan.md +3 -1
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/hooks/context-post-tool.ts +101 -2
- package/plugins/sp/hooks/context-session-start.ts +22 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/stage-registry-adapter.ts +144 -2
- package/plugins/sp/skills/session-review/SKILL.md +16 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +38 -13
- package/plugins/sp/skills/spur-cli/references/agent.md +7 -4
- package/plugins/sp/skills/spur-cli/references/history.md +69 -0
- package/plugins/sp/skills/spur-cli/references/message.md +2 -2
- package/plugins/sp/skills/spur-cli/references/projects.md +59 -0
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +28 -0
- package/plugins/sp/skills/spur-cli/references/team.md +1 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +6 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +40 -16
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +6 -6
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +80 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +18 -13
- package/spur.js +2099 -650
- package/web/_astro/{BoardApp.BEtcJqde.js → BoardApp.BQFbkeqq.js} +15 -15
- package/web/_astro/BoardApp.CTkqrhWd.js +1 -0
- package/web/_astro/{TaskDetail.ClAbCXom.js → TaskDetail.Dl2Eaj1w.js} +1 -1
- package/web/_astro/{arc.CCvf51_y.js → arc.uG14rp8A.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.C0cb0J5M.js → architectureDiagram-3BPJPVTR.Dye6uD_x.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.CIyjqoCE.js → blockDiagram-GPEHLZMM.B9Pkh7Hb.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.fs14IuFs.js → c4Diagram-AAUBKEIU.C2x7SC_X.js} +1 -1
- package/web/_astro/channel.Dsvulp7W.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CaBKv4ZO.js → chunk-2J33WTMH.D2p4-nWk.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BOllTPto.js → chunk-4BX2VUAB.S-6jf33o.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.ChEof0O4.js → chunk-55IACEB6.DVXj4Fdh.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Co2kdjD8.js → chunk-727SXJPM.Dz689FMN.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.SWmfcnog.js → chunk-AQP2D5EJ.KxYj5TnI.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.rDAFifF3.js → chunk-FMBD7UC4.itTQyHQB.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BCnoXKCw.js → chunk-ND2GUHAM.euSrbJf5.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.RSmy2hDO.js → chunk-QZHKN3VN.OWASJRQy.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.Be7PEfrX.js → classDiagram-4FO5ZUOK.BLvrlpNO.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.Be7PEfrX.js → classDiagram-v2-Q7XG4LA2.BLvrlpNO.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.BkUp2aSK.js → cose-bilkent-S5V4N54A.XBF-rmyD.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BegGGlUV.js → cynefin-OW5HDTMX.DlCx762Z.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BkUdjsaC.js → dagre-BM42HDAG.D17Rshxv.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.E9vugt3-.js → diagram-2AECGRRQ.AhBIVJC8.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Dj4yeHXB.js → diagram-5GNKFQAL.C9ximjyC.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Buaquwli.js → diagram-KO2AKTUF.CZb7Ru_9.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BV3dgGgm.js → diagram-LMA3HP47.BW7LwqoS.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.Cnx3s-tc.js → diagram-OG6HWLK6.XC025W0V.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.DKK_abu4.js → erDiagram-TEJ5UH35.CpMXmBDP.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.BNuu9fbm.js → flowDiagram-I6XJVG4X.D2ednJWg.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.b16KUMjy.js → ganttDiagram-6RSMTGT7.BjL9FGKO.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Kh41lbG5.js → gitGraphDiagram-PVQCEYII.B90g1VGk.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.DEWBXkp-.js → infoDiagram-5YYISTIA.RqgycKtQ.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.DiAdmcL6.js → ishikawaDiagram-YF4QCWOH.Ctn-zt6a.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.D1Ki7IRm.js → journeyDiagram-JHISSGLW.DJhT8Ctp.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CWUhrQpc.js → kanban-definition-UN3LZRKU.BY1QdejI.js} +1 -1
- package/web/_astro/{linear.BaFsgcCe.js → linear.Di7YObSt.js} +1 -1
- package/web/_astro/{mermaid.core.CHw_AsGy.js → mermaid.core.CbxtJS3Q.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.UIhghgmN.js → mindmap-definition-RKZ34NQL.CxvR4g_J.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.D05l3JUA.js → pieDiagram-4H26LBE5.jNWqnBHH.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.BcWIhIcE.js → quadrantDiagram-W4KKPZXB.BCp12MbA.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.B1rYvKGn.js → requirementDiagram-4Y6WPE33.Dxhm4TyR.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.CKylVRC4.js → sankeyDiagram-5OEKKPKP.BtQXp4J9.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.Dm3uA_s4.js → sequenceDiagram-3UESZ5HK.BWEM1R_Q.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bgca_BLe.js → stateDiagram-AJRCARHV.BG3wUkWB.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.C1T7YFrG.js → stateDiagram-v2-BHNVJYJU.BLtMeFVP.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.ZOHJn3Sn.js → timeline-definition-PNZ67QCA.D5fHo0az.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DegZitjD.js → vennDiagram-CIIHVFJN.0DcuMluU.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BDsC115d.js → wardleyDiagram-YWT4CUSO.BZ-dxgHm.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.D0MO70ea.js → xychartDiagram-2RQKCTM6.Bg-XWF7z.js} +1 -1
- package/web/index.html +1 -1
- package/web/_astro/BoardApp.DBEin4N5.js +0 -1
- package/web/_astro/channel.BGn_DUCD.js +0 -1
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: expert-spur
|
|
3
3
|
description: |
|
|
4
|
-
Use PROACTIVELY for "create tasks for this feature", "update all task statuses", "audit task traceability", "create a feature with acceptance criteria", "harden the rule catalog", "author a batch of workflows", or "expert-spur". Multi-step
|
|
4
|
+
Use PROACTIVELY for "create tasks for this feature", "update all task statuses", "audit task traceability", "create a feature with acceptance criteria", "harden the rule catalog", "author a batch of workflows", or "expert-spur". Multi-step corpus work across `spur task`, `feature`, `rule`, and `workflow`: batch creation, status sweeps, section campaigns, traceability audits, rule hardening, and workflow authoring/refactoring. For one deterministic operation, run the CLI directly.
|
|
5
5
|
|
|
6
6
|
<example>
|
|
7
|
-
Context: Batch task status update across a feature
|
|
7
|
+
Context: Batch task status update across a feature.
|
|
8
8
|
user: "Move all A1 tasks from backlog to wip."
|
|
9
|
-
assistant: "Delegating to sp:expert-spur —
|
|
10
|
-
<commentary>
|
|
9
|
+
assistant: "Delegating to sp:expert-spur — loads the task reference, resolves the set, then applies and checks each transition."
|
|
10
|
+
<commentary>A multi-task sweep needs isolated sequencing and between-operation judgment.</commentary>
|
|
11
11
|
</example>
|
|
12
12
|
tools: [Read, Grep, Glob, Bash, Skill]
|
|
13
13
|
model: inherit
|
|
@@ -17,113 +17,86 @@ skills: [sp:spur-cli]
|
|
|
17
17
|
|
|
18
18
|
# Expert Spur
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
rule-catalog hardening, workflow refactors) that benefits from isolation; for a single operation,
|
|
24
|
-
use the `spur` CLI directly or invoke `sp:spur-dev`.
|
|
20
|
+
Thin specialist for multi-step Spur **corpus** work. The backend skill `sp:spur-cli` owns noun,
|
|
21
|
+
verb, flag, output, and exit semantics; this agent selects its task/feature/rule/workflow reference,
|
|
22
|
+
sequences operations, and evaluates each result before continuing.
|
|
25
23
|
|
|
26
24
|
## Role
|
|
27
25
|
|
|
28
|
-
You are the
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
`docs/design/workflow-shell-ownership.md`; never reformat to dodge the measure.
|
|
58
|
-
- **Latency and observability are authoring decisions.** Minimize `agent.run` node count first;
|
|
59
|
-
soft status-file probe over repeated probing; guards ordered cheapest-discriminating-first;
|
|
60
|
-
`iterationBound` from a latency budget; states named for outcomes; `failureStates` declared.
|
|
61
|
-
- **Refactor in a named direction** — promote (prose → workflow), demote (workflow → prose),
|
|
62
|
-
or optimize in place. Back an optimization with a before/after `spur workflow trace` pair,
|
|
63
|
-
never with a feel.
|
|
64
|
-
- Composition-advisory findings (`workflow validate --json` → `composition`) are advisory only;
|
|
65
|
-
never block a run or edit an executing pipeline over them.
|
|
66
|
-
- **Corpus health checks** — run `check`/`validate` across a batch and report findings.
|
|
67
|
-
|
|
68
|
-
For a single operation, use the `spur` CLI directly. For the planning/execution lifecycle, use
|
|
69
|
-
`sp:spur-dev`.
|
|
70
|
-
|
|
71
|
-
## Skill invocation
|
|
72
|
-
|
|
73
|
-
Invoke `sp:spur-cli` for verb guidance and per-noun conventions:
|
|
74
|
-
|
|
75
|
-
| Platform | Invocation |
|
|
76
|
-
| ---------- | ----------- |
|
|
77
|
-
| Claude Code | `Skill(skill="sp:spur-cli", args="<noun> <query>")` |
|
|
78
|
-
| Other platforms | Invoke `sp:spur-cli` directly as a skill |
|
|
26
|
+
You are the Spur corpus steward: a specialist sequencer over `skill: sp:spur-cli`, not a second
|
|
27
|
+
implementation of the CLI or lifecycle spine.
|
|
28
|
+
|
|
29
|
+
## Scope
|
|
30
|
+
|
|
31
|
+
Use for:
|
|
32
|
+
|
|
33
|
+
- Batch task or feature creation, mutation, status, section, refresh, and check campaigns.
|
|
34
|
+
- Cross-corpus traceability or structural audits.
|
|
35
|
+
- Rule catalog authoring, validation, execution, and hardening.
|
|
36
|
+
- Workflow fit decisions, authoring/refactoring, validation, dry-runs, and trace comparison.
|
|
37
|
+
|
|
38
|
+
Do not use for one CLI invocation. Do not use for planning→implementation→verification lifecycle
|
|
39
|
+
or batch task execution; `sp:spur-dev` owns that orchestration. The backend skill covers the other
|
|
40
|
+
CLI nouns for direct use, but they are not this corpus specialist's scope.
|
|
41
|
+
|
|
42
|
+
## Process
|
|
43
|
+
|
|
44
|
+
1. Load `plugins/sp/skills/spur-cli/SKILL.md` and the exact noun reference before invoking a verb.
|
|
45
|
+
2. Resolve and freeze the target set. Report ambiguity instead of guessing identifiers or flags.
|
|
46
|
+
3. Run the noun's read/check/validate path before mutation where available.
|
|
47
|
+
4. Mutate only through `spur`; parse `--json` output when the verb advertises it.
|
|
48
|
+
5. Inspect each result before the next dependent operation; stop on structural or validation failure.
|
|
49
|
+
6. Run the scoped check/validate/refresh path after mutation. After task/feature batch writes, run
|
|
50
|
+
`spur task check --corpus --json` once.
|
|
51
|
+
|
|
52
|
+
Workflow fit, mode selection, simplicity budgets, authoring, and tuning live in the workflow
|
|
53
|
+
references under `plugins/sp/skills/spur-cli/references/workflows/`; load them rather than copying
|
|
54
|
+
their runbook here.
|
|
79
55
|
|
|
80
56
|
## Rules
|
|
81
57
|
|
|
82
58
|
### Always
|
|
83
59
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
88
|
-
- [ ] Run the noun's scoped `refresh` after batch operations where one exists (`spur task refresh`, `spur feature refresh --feature <id>` or `--all`).
|
|
89
|
-
- [ ] Run the workflow fit gate before authoring any new workflow, and recommend a descriptive procedure when it does not clear all three parts.
|
|
60
|
+
- Use the source-local CLI when working in the Spur repository.
|
|
61
|
+
- Use `spur task update --section --from-file` for task section writes.
|
|
62
|
+
- Keep check-before/write/check-after evidence and the final scoped refresh result.
|
|
63
|
+
- Preserve declaration order and currently executing runs when changing workflows.
|
|
90
64
|
|
|
91
65
|
### Never
|
|
92
66
|
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
67
|
+
- Edit task or feature corpus files directly.
|
|
68
|
+
- Invent a noun, verb, flag, JSON field, or exit code.
|
|
69
|
+
- Reimplement CLI validation in prose or shell.
|
|
70
|
+
- Never drive the planning/execution lifecycle; do not run application implementation or task
|
|
71
|
+
pipelines through this agent.
|
|
97
72
|
|
|
98
73
|
## Output Format
|
|
99
74
|
|
|
100
|
-
Report using this template:
|
|
101
|
-
|
|
102
75
|
```markdown
|
|
103
76
|
## Spur Corpus Operations Report
|
|
104
77
|
|
|
105
|
-
**Noun(s)
|
|
106
|
-
**
|
|
107
|
-
**Confidence
|
|
78
|
+
**Noun(s):** task | feature | rule | workflow
|
|
79
|
+
**Scope:** <resolved ids/files>
|
|
80
|
+
**Confidence:** HIGH | MEDIUM | LOW
|
|
108
81
|
|
|
109
82
|
### Changes
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
| 0042 | update wip |
|
|
113
|
-
|
|
114
|
-
###
|
|
115
|
-
- check
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
### Next Steps
|
|
119
|
-
1. [Actionable step]
|
|
83
|
+
| Target | Operation | Result |
|
|
84
|
+
| --- | --- | --- |
|
|
85
|
+
| 0042 | update wip | pass |
|
|
86
|
+
|
|
87
|
+
### Gates
|
|
88
|
+
- pre-check: <result>
|
|
89
|
+
- post-check/validate: <result>
|
|
90
|
+
- refresh/corpus sweep: <result or n/a>
|
|
120
91
|
```
|
|
121
92
|
|
|
122
93
|
## Platform Notes
|
|
123
94
|
|
|
124
|
-
-
|
|
125
|
-
-
|
|
95
|
+
- Claude Code: use `Skill(skill="sp:spur-cli", args="<noun> <query>")`, then Bash for `spur`.
|
|
96
|
+
- Other platforms: invoke `sp:spur-cli` directly; the agent wrapper is optional.
|
|
126
97
|
|
|
127
98
|
## Dispatch surface
|
|
128
99
|
|
|
129
|
-
|
|
100
|
+
If corpus work must be dispatched again, follow
|
|
101
|
+
[dispatch-surface.md](../skills/parallel-execution/references/dispatch-surface.md): native subagent
|
|
102
|
+
by default, `spur agent run` only on a named trigger.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Turn a vague idea into a feature with AC and a decomposed task batch — discovery, idea-eval, feature-create, AC, feature-check, system-design, decompose, batch-create (Design by default), handoff
|
|
3
3
|
role: planner
|
|
4
|
-
argument-hint: "\"<idea>\" [--auto] [--skip-design] [--approve-taste] [--agent <auto|name>]"
|
|
4
|
+
argument-hint: "\"<idea>\" [--auto] [--skip-design] [--approve-taste] [--agent <inline|auto|name>]"
|
|
5
5
|
allowed-tools: ["Bash", "Read", "Skill", "AskUserQuestion"]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -20,7 +20,7 @@ contract below maps to that workflow's transitions.
|
|
|
20
20
|
| `--approve-taste` | With `--auto`: set idea_approved + design_approved so idea-eval / design-approval do not pause. | off |
|
|
21
21
|
| `--idea-approved` | Compatibility alias for idea_approved=true (subset of --approve-taste). | off |
|
|
22
22
|
| `--design-approved` | Compatibility alias for design_approved=true (subset of --approve-taste). | off |
|
|
23
|
-
| `--agent` `<auto\|name>` | Who runs the model-bearing ideation.
|
|
23
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing ideation. Omission and `inline` drive `idea-pipeline.yaml` in this session with zero external agent/workflow processes; `auto` tier-resolves an executor and a name pins one, both through the async workflow worker. | inline |
|
|
24
24
|
|
|
25
25
|
For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
31
31
|
[--auto] # skip objective HITL only (feature-check, batch-create)
|
|
32
32
|
[--skip-design] # design package off (system-design + task Design)
|
|
33
33
|
[--approve-taste] # with --auto: skip idea-eval + design-approval pauses
|
|
34
|
-
[--agent <auto|name>]
|
|
34
|
+
[--agent <inline|auto|name>] # inline is the current session; auto/name are async workers
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
There is **no** `--design` force flag. Design is default-on; only `--skip-design` opts out.
|
|
@@ -42,6 +42,8 @@ vars as subsets of `--approve-taste` (`idea_approved` / `design_approved`). Pref
|
|
|
42
42
|
## Implementation
|
|
43
43
|
|
|
44
44
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
45
|
+
- Omitted/`inline`: drive `idea-pipeline.yaml` through the [inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md). Do not launch `spur workflow run`, `spur agent run`, or a native subagent unless the operator explicitly requests delegation.
|
|
46
|
+
- `auto`/name: launch `spur workflow run idea-pipeline.yaml --async`, observe with one `workflow trace --follow`, and only report cancellation as stopped when `workflow cancel --json` returns `killed: true`.
|
|
45
47
|
- `Skill(skill="sp:spur-dev", args="idea $ARGUMENTS")`
|
|
46
48
|
- Stage contract (discovery → idea-eval → feature-create → AC → feature-check → system-design →
|
|
47
49
|
decompose → batch-create → handoff): `plugins/sp/skills/spur-dev/references/dev-operations.md` § idea.
|
|
@@ -16,7 +16,7 @@ Wraps the **sp:spur-dev** skill.
|
|
|
16
16
|
| `"<description>"` | Feature description to plan. | required |
|
|
17
17
|
| `--feature` `<id>` | Attach to an existing feature. | omitted |
|
|
18
18
|
| `--parent` `<feature-id>` | Create under a parent feature. | omitted |
|
|
19
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing planning.
|
|
19
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing planning. Omission and `inline` drive `idea-pipeline.yaml` in this session with zero external agent/workflow processes; `auto` tier-resolves an executor and a name pins one, both through the async workflow worker. | inline |
|
|
20
20
|
| `--skip-design` | Omit the system-design hop. | off |
|
|
21
21
|
| `--auto` | Skip objective HITL gates. | off |
|
|
22
22
|
| `--approve-taste` | With --auto: skip design-approval pause. | off |
|
|
@@ -43,5 +43,7 @@ for taste gates). Alias: `--design-approved` (prefer `--approve-taste`).
|
|
|
43
43
|
## Implementation
|
|
44
44
|
|
|
45
45
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
46
|
+
- Omitted/`inline`: drive `idea-pipeline.yaml` through the [inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md). Do not launch `spur workflow run`, `spur agent run`, or a native subagent unless the operator explicitly requests delegation.
|
|
47
|
+
- `auto`/name: launch `spur workflow run idea-pipeline.yaml --async`, observe with one `workflow trace --follow`, and only report cancellation as stopped when `workflow cancel --json` returns `killed: true`.
|
|
46
48
|
- `Skill(skill="sp:spur-dev", args="plan $ARGUMENTS")`
|
|
47
49
|
- Full Design package + batch `design` field contract: `plugins/sp/skills/spur-dev/references/dev-operations.md` § plan and `planning-workflow.md` Step 5.5.
|
|
@@ -12,7 +12,8 @@ default it is report-only: current conversation plus read-only repository eviden
|
|
|
12
12
|
the session context is preserved — no workflow launch, history import, task creation, or
|
|
13
13
|
remediation. With `--triage`, it first triages the findings, then applies direct fixes (pure
|
|
14
14
|
documentation work and one-to-two-line fixes) inline and files everything remaining as exactly one
|
|
15
|
-
new task for further fixing.
|
|
15
|
+
new task for further fixing. The result includes a non-overlapping time breakdown with durations in
|
|
16
|
+
`M:SS` or `H:MM:SS` form and `n/a` for unavailable measurements.
|
|
16
17
|
|
|
17
18
|
## Argument Flags
|
|
18
19
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* context-post-tool — PostToolUse hook for indexed-context
|
|
4
5
|
* (matcher: Bash|Grep|Glob|Read|Write|Edit — task 0248).
|
|
@@ -19,8 +20,9 @@
|
|
|
19
20
|
* `superskill hook run sp context-post-tool`.
|
|
20
21
|
*/
|
|
21
22
|
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
23
|
+
import { execSync } from 'node:child_process';
|
|
24
|
+
import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { join, resolve, sep } from 'node:path';
|
|
24
26
|
|
|
25
27
|
/** Tools recorded by this hook (must match hooks.json PostToolUse matcher). */
|
|
26
28
|
export const ALLOWED_TOOLS = new Set(['Bash', 'Grep', 'Glob', 'Read', 'Write', 'Edit']);
|
|
@@ -282,6 +284,15 @@ export function recordToolUseEvent(
|
|
|
282
284
|
const session = sessionMeta.session ?? '';
|
|
283
285
|
if (!session) return null;
|
|
284
286
|
|
|
287
|
+
// Freshness stamp (task 0711 R4): the indexed-context producer is the agent
|
|
288
|
+
// following the skill's update guidance; the observable producer moment in
|
|
289
|
+
// code is a Write/Edit landing on a `.spur/context/*.md` file. Each such
|
|
290
|
+
// write refreshes `.freshness.json` so staleness checks can compare the
|
|
291
|
+
// sidecar's source commit against HEAD until the next regeneration.
|
|
292
|
+
if ((toolName === 'Write' || toolName === 'Edit') && isContextIndexFile(contextDir, filePath)) {
|
|
293
|
+
stampContextFreshness(contextDir, currentHeadCommit(), now());
|
|
294
|
+
}
|
|
295
|
+
|
|
285
296
|
const tokens = resolveTokenEstimate(toolName, payload.tool_input, payload.tool_response);
|
|
286
297
|
const ts = now().toISOString();
|
|
287
298
|
const type = mapToolType(toolName);
|
|
@@ -309,6 +320,94 @@ export function recordToolUseEvent(
|
|
|
309
320
|
return event;
|
|
310
321
|
}
|
|
311
322
|
|
|
323
|
+
// ─── Context freshness sidecar (task 0711 R4) ───────────────────────────
|
|
324
|
+
|
|
325
|
+
export interface ContextFreshness {
|
|
326
|
+
schema_version: number;
|
|
327
|
+
source_commit: string;
|
|
328
|
+
generated_at: string;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const CONTEXT_FRESHNESS_SCHEMA_VERSION = 1;
|
|
332
|
+
|
|
333
|
+
/** True when `filePath` is a regenerated context index inside `contextDir` (not the sidecars). */
|
|
334
|
+
export function isContextIndexFile(contextDir: string, filePath: string): boolean {
|
|
335
|
+
const resolvedDir = resolve(contextDir);
|
|
336
|
+
const resolvedFile = resolve(filePath);
|
|
337
|
+
if (!resolvedFile.startsWith(resolvedDir + sep)) return false;
|
|
338
|
+
const name = resolvedFile.slice(resolvedDir.length + 1);
|
|
339
|
+
return name.endsWith('.md');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** Current HEAD, best-effort — null when git is unavailable or the dir is not a work tree. */
|
|
343
|
+
export function currentHeadCommit(): string | null {
|
|
344
|
+
try {
|
|
345
|
+
const out = execSync('git rev-parse HEAD', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
346
|
+
const commit = out.trim();
|
|
347
|
+
return commit.length > 0 ? commit : null;
|
|
348
|
+
} catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Write/refresh `.spur/context/.freshness.json`. Best-effort: any I/O failure
|
|
355
|
+
* leaves the previous sidecar (or none) in place — the hook contract is
|
|
356
|
+
* fail-open, never blocking.
|
|
357
|
+
*/
|
|
358
|
+
export function stampContextFreshness(contextDir: string, sourceCommit: string | null, at: Date): void {
|
|
359
|
+
if (!sourceCommit) return;
|
|
360
|
+
const body: ContextFreshness = {
|
|
361
|
+
schema_version: CONTEXT_FRESHNESS_SCHEMA_VERSION,
|
|
362
|
+
source_commit: sourceCommit,
|
|
363
|
+
generated_at: at.toISOString(),
|
|
364
|
+
};
|
|
365
|
+
try {
|
|
366
|
+
writeFileSync(join(contextDir, '.freshness.json'), JSON.stringify(body));
|
|
367
|
+
} catch {
|
|
368
|
+
/* fail-open */
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export interface ContextFreshnessCheck {
|
|
373
|
+
stale: boolean;
|
|
374
|
+
reason?: string;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Compare a freshness sidecar against the current HEAD (task 0711 R4). A
|
|
379
|
+
* missing/unparsable sidecar is stale (`never stamped`); a commit mismatch is
|
|
380
|
+
* stale until the producer regenerates the index.
|
|
381
|
+
*/
|
|
382
|
+
export function checkContextFreshness(raw: string | null, headCommit: string | null): ContextFreshnessCheck {
|
|
383
|
+
if (raw === null) return { stale: true, reason: 'never stamped' };
|
|
384
|
+
let body: ContextFreshness;
|
|
385
|
+
try {
|
|
386
|
+
body = JSON.parse(raw) as ContextFreshness;
|
|
387
|
+
} catch {
|
|
388
|
+
return { stale: true, reason: 'malformed sidecar' };
|
|
389
|
+
}
|
|
390
|
+
if (body.schema_version !== CONTEXT_FRESHNESS_SCHEMA_VERSION) {
|
|
391
|
+
return { stale: true, reason: `schema_version ${String(body.schema_version)}` };
|
|
392
|
+
}
|
|
393
|
+
if (typeof body.source_commit !== 'string' || body.source_commit === '') {
|
|
394
|
+
return { stale: true, reason: 'missing source_commit' };
|
|
395
|
+
}
|
|
396
|
+
if (headCommit !== null && body.source_commit !== headCommit) {
|
|
397
|
+
return { stale: true, reason: 'source commit changed since generation' };
|
|
398
|
+
}
|
|
399
|
+
return { stale: false };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/** Read the freshness sidecar, or null when absent/unreadable. */
|
|
403
|
+
export function readContextFreshness(contextDir: string): string | null {
|
|
404
|
+
try {
|
|
405
|
+
return readFileSync(join(contextDir, '.freshness.json'), 'utf-8');
|
|
406
|
+
} catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
312
411
|
// Entrypoint — thin wrapper; logic lives in {@link recordToolUseEvent} for unit coverage.
|
|
313
412
|
if (import.meta.main) {
|
|
314
413
|
void (async () => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* context-session-start — SessionStart hook for indexed-context.
|
|
4
5
|
*
|
|
@@ -11,10 +12,11 @@
|
|
|
11
12
|
* Self-contained by design (task 0232/0246).
|
|
12
13
|
*/
|
|
13
14
|
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
14
16
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
15
17
|
import { join } from 'node:path';
|
|
16
|
-
|
|
17
18
|
import { resolveAgentHint, resolveModelHint } from './agent-hint';
|
|
19
|
+
import { checkContextFreshness } from './context-post-tool';
|
|
18
20
|
|
|
19
21
|
export { resolveAgentHint, resolveModelHint } from './agent-hint';
|
|
20
22
|
|
|
@@ -161,6 +163,25 @@ export function recordSessionStart(
|
|
|
161
163
|
if (agent) startEvent.agent = agent;
|
|
162
164
|
if (model) startEvent.model = model;
|
|
163
165
|
|
|
166
|
+
// Freshness check (task 0711 R4): report whether the context indexes were
|
|
167
|
+
// regenerated at the current HEAD. Best-effort, fail-open — git failures
|
|
168
|
+
// yield head=null, which marks stale only via the sidecar's own defects.
|
|
169
|
+
let headCommit: string | null = null;
|
|
170
|
+
try {
|
|
171
|
+
const out = execSync('git rev-parse HEAD', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
172
|
+
const trimmed = out.trim();
|
|
173
|
+
headCommit = trimmed.length > 0 ? trimmed : null;
|
|
174
|
+
} catch {
|
|
175
|
+
headCommit = null;
|
|
176
|
+
}
|
|
177
|
+
let freshnessRaw: string | null = null;
|
|
178
|
+
try {
|
|
179
|
+
freshnessRaw = readFileSync(join(dir, '.freshness.json'), 'utf-8');
|
|
180
|
+
} catch {
|
|
181
|
+
freshnessRaw = null;
|
|
182
|
+
}
|
|
183
|
+
startEvent.contextFreshness = checkContextFreshness(freshnessRaw, headCommit);
|
|
184
|
+
|
|
164
185
|
const ledgerPath = join(dir, 'token-ledger.jsonl');
|
|
165
186
|
try {
|
|
166
187
|
appendFileSync(ledgerPath, `${JSON.stringify(startEvent)}\n`);
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.68",
|
|
4
4
|
"description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
|
|
5
5
|
"extensions": {
|
|
6
6
|
"pi": ["./hooks/pi/guard-extension.ts"]
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
* bun plugins/sp/scripts/stage-registry-adapter.ts --help
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import {
|
|
27
|
+
import { execSync } from 'node:child_process';
|
|
28
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
29
|
import { join } from 'node:path';
|
|
29
30
|
|
|
30
31
|
// ─── Inline type definitions (mirrors packages/domain/src/stage-registry/) ─
|
|
@@ -203,6 +204,107 @@ export interface TaskSignal {
|
|
|
203
204
|
feature_id?: string | null;
|
|
204
205
|
hasCheckpoint?: boolean;
|
|
205
206
|
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Result of routing-checkpoint inspection (task 0711 R3).
|
|
210
|
+
*
|
|
211
|
+
* `usable` feeds `TaskSignal.hasCheckpoint`: only a checkpoint that parses as
|
|
212
|
+
* the canonical frontmatter contract, matches the task (owner identity), is
|
|
213
|
+
* non-terminal, and is not commit-drifted may route A4 (wip + checkpoint →
|
|
214
|
+
* continue). Anything else is reported with its reason and IGNORED — routing
|
|
215
|
+
* falls through to the non-checkpoint row (A5); it is never silently trusted.
|
|
216
|
+
*
|
|
217
|
+
* Lean inline mirror of `packages/app/src/workflow/checkpoint-contract.ts`
|
|
218
|
+
* (the plugin installs into foreign repos and cannot import workspace
|
|
219
|
+
* packages — same discipline as the domain-type mirrors above); the parity
|
|
220
|
+
* test pins the two together.
|
|
221
|
+
*/
|
|
222
|
+
export interface RoutingCheckpointInspection {
|
|
223
|
+
usable: boolean;
|
|
224
|
+
reason?: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface RoutingCheckpointOptions {
|
|
228
|
+
/** Current repository HEAD; when provided, a mismatched/absent `source_commit` is drift. */
|
|
229
|
+
headCommit?: string;
|
|
230
|
+
/** Existence probe for referenced artifacts; default `existsSync`. */
|
|
231
|
+
artifactExists?: (path: string) => boolean;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Inspect `.spur/memory/sessions/<wbs>-checkpoint.md` for resume routing
|
|
236
|
+
* (task 0711 R3). A missing file yields `{ usable: false, reason: 'absent' }`
|
|
237
|
+
* — the normal case, indistinguishable for routing from a rejected one.
|
|
238
|
+
*/
|
|
239
|
+
export function inspectRoutingCheckpoint(
|
|
240
|
+
sessionsDir: string,
|
|
241
|
+
wbs: string,
|
|
242
|
+
opts: RoutingCheckpointOptions = {},
|
|
243
|
+
): RoutingCheckpointInspection {
|
|
244
|
+
let raw: string;
|
|
245
|
+
try {
|
|
246
|
+
raw = readFileSync(join(sessionsDir, `${wbs}-checkpoint.md`), 'utf-8');
|
|
247
|
+
} catch {
|
|
248
|
+
return { usable: false, reason: 'absent' };
|
|
249
|
+
}
|
|
250
|
+
if (!raw.startsWith('---')) return { usable: false, reason: 'malformed: missing frontmatter' };
|
|
251
|
+
const end = raw.indexOf('\n---', 3);
|
|
252
|
+
if (end < 0) return { usable: false, reason: 'malformed: unterminated frontmatter' };
|
|
253
|
+
|
|
254
|
+
const scalars = new Map<string, string>();
|
|
255
|
+
const artifacts: string[] = [];
|
|
256
|
+
let inArtifacts = false;
|
|
257
|
+
for (const rawLine of raw.slice(4, end).split('\n')) {
|
|
258
|
+
const line = rawLine.trim();
|
|
259
|
+
if (line.startsWith('- ')) {
|
|
260
|
+
if (inArtifacts) artifacts.push(line.slice(2).trim().replace(/^"|"$/g, ''));
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
inArtifacts = false;
|
|
264
|
+
const sep = line.indexOf(':');
|
|
265
|
+
if (sep <= 0) continue;
|
|
266
|
+
const key = line.slice(0, sep).trim();
|
|
267
|
+
const value = line
|
|
268
|
+
.slice(sep + 1)
|
|
269
|
+
.trim()
|
|
270
|
+
.replace(/^"|"$/g, '');
|
|
271
|
+
if (key === 'artifacts') {
|
|
272
|
+
inArtifacts = true;
|
|
273
|
+
if (value !== '') {
|
|
274
|
+
artifacts.push(
|
|
275
|
+
...value
|
|
276
|
+
.split(',')
|
|
277
|
+
.map((p) => p.trim().replace(/^"|"$/g, ''))
|
|
278
|
+
.filter((p) => p !== ''),
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
scalars.set(key, value);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (scalars.get('schema_version') !== '1') return { usable: false, reason: 'malformed: schema_version' };
|
|
287
|
+
if (scalars.get('task_wbs') !== wbs) {
|
|
288
|
+
return { usable: false, reason: `owner-mismatch: task_wbs=${scalars.get('task_wbs')} != ${wbs}` };
|
|
289
|
+
}
|
|
290
|
+
const status = scalars.get('status') ?? '';
|
|
291
|
+
if (['done', 'failed', 'cancelled', 'skipped'].includes(status)) {
|
|
292
|
+
return { usable: false, reason: `terminal: status=${status}` };
|
|
293
|
+
}
|
|
294
|
+
const head = opts.headCommit;
|
|
295
|
+
if (head !== undefined) {
|
|
296
|
+
const commit = scalars.get('source_commit') ?? '';
|
|
297
|
+
if (commit === '' || commit !== head) {
|
|
298
|
+
return { usable: false, reason: `commit-drift: checkpoint@${commit.slice(0, 12) || 'none'} != HEAD` };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const probe = opts.artifactExists ?? existsSync;
|
|
302
|
+
for (const artifact of artifacts) {
|
|
303
|
+
const p = artifact.trim().replace(/^"|"$/g, '');
|
|
304
|
+
if (p !== '' && !probe(p)) return { usable: false, reason: `missing-artifact: ${p}` };
|
|
305
|
+
}
|
|
306
|
+
return { usable: true };
|
|
307
|
+
}
|
|
206
308
|
export interface FeatureSignal {
|
|
207
309
|
id: string;
|
|
208
310
|
status: FeatureStatus;
|
|
@@ -894,6 +996,8 @@ const TABLE_C: TableCRow[] = [
|
|
|
894
996
|
},
|
|
895
997
|
{
|
|
896
998
|
// C4 — rule findings: requires spur rule run
|
|
999
|
+
// SAFETY: sentinel, not a string — this row never dispatches; null is the
|
|
1000
|
+
// intentional HITL-stop placeholder that formatStageResult renders as no-command.
|
|
897
1001
|
redirectDispatch: null as unknown as string, // HITL stop
|
|
898
1002
|
rowId: 'C4',
|
|
899
1003
|
probeRows: ['A3', 'A5', 'A6'],
|
|
@@ -1315,6 +1419,17 @@ export function formatStageResult(result: StageResolution): string[] {
|
|
|
1315
1419
|
return lines;
|
|
1316
1420
|
}
|
|
1317
1421
|
|
|
1422
|
+
/** Current repository HEAD, best-effort — null outside a work tree or without git. */
|
|
1423
|
+
function currentHeadCommit(): string | null {
|
|
1424
|
+
try {
|
|
1425
|
+
const out = execSync('git rev-parse HEAD', { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
1426
|
+
const commit = out.trim();
|
|
1427
|
+
return commit.length > 0 ? commit : null;
|
|
1428
|
+
} catch {
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1318
1433
|
export function runCli(argv: string[], opts?: { resolve?: (input: ResolutionInput) => StageResolution }): CliResult {
|
|
1319
1434
|
const parsed = parseCliArgs(argv);
|
|
1320
1435
|
const resolve = opts?.resolve ?? resolveStage;
|
|
@@ -1337,6 +1452,30 @@ export function runCli(argv: string[], opts?: { resolve?: (input: ResolutionInpu
|
|
|
1337
1452
|
};
|
|
1338
1453
|
}
|
|
1339
1454
|
|
|
1455
|
+
// Routing checkpoint (0711 R3): a canonical checkpoint at
|
|
1456
|
+
// .spur/memory/sessions/<wbs>-checkpoint.md that survives inspection (owner,
|
|
1457
|
+
// non-terminal, at HEAD, artifacts present) enables the A4 resume row for wip
|
|
1458
|
+
// tasks; anything else is REPORTED and ignored — routing falls through to the
|
|
1459
|
+
// non-checkpoint row. Best-effort and fail-open: git/fs failures leave the
|
|
1460
|
+
// checkpoint unconsidered.
|
|
1461
|
+
let checkpointNote: string | undefined;
|
|
1462
|
+
let hasCheckpoint: boolean | undefined;
|
|
1463
|
+
if (parsed.wbs) {
|
|
1464
|
+
try {
|
|
1465
|
+
const inspection = inspectRoutingCheckpoint(
|
|
1466
|
+
join(process.cwd(), '.spur', 'memory', 'sessions'),
|
|
1467
|
+
parsed.wbs,
|
|
1468
|
+
{ headCommit: currentHeadCommit() ?? undefined },
|
|
1469
|
+
);
|
|
1470
|
+
hasCheckpoint = inspection.usable;
|
|
1471
|
+
if (!inspection.usable && inspection.reason !== 'absent') {
|
|
1472
|
+
checkpointNote = `note: routing checkpoint ignored — ${inspection.reason}`;
|
|
1473
|
+
}
|
|
1474
|
+
} catch {
|
|
1475
|
+
hasCheckpoint = undefined;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1340
1479
|
// Build resolution input from CLI args (note: no live corpus access in CLI mode)
|
|
1341
1480
|
const input: ResolutionInput = {
|
|
1342
1481
|
target: parsed.wbs ?? parsed.feature ?? '',
|
|
@@ -1345,13 +1484,16 @@ export function runCli(argv: string[], opts?: { resolve?: (input: ResolutionInpu
|
|
|
1345
1484
|
once: parsed.once,
|
|
1346
1485
|
auto: parsed.auto,
|
|
1347
1486
|
fullMode: parsed.full,
|
|
1348
|
-
task: parsed.wbs
|
|
1487
|
+
task: parsed.wbs
|
|
1488
|
+
? { wbs: parsed.wbs, status: parsed.taskStatus ?? 'unknown', dependencies: [], hasCheckpoint }
|
|
1489
|
+
: undefined,
|
|
1349
1490
|
feature: parsed.feature ? { id: parsed.feature, status: 'active', tasks: [] } : undefined,
|
|
1350
1491
|
};
|
|
1351
1492
|
|
|
1352
1493
|
try {
|
|
1353
1494
|
const result = resolve(input);
|
|
1354
1495
|
const lines = formatStageResult(result);
|
|
1496
|
+
if (checkpointNote) lines.push(checkpointNote);
|
|
1355
1497
|
return { exitCode: result.reasonKind === 'dispatch' ? 0 : 2, stdout: `${lines.join('\n')}\n`, stderr: '' };
|
|
1356
1498
|
} catch (e) {
|
|
1357
1499
|
return { exitCode: 1, stdout: '', stderr: `error: ${(e as Error).message}` };
|