@gobing-ai/spur 0.3.45 → 0.3.47
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/templates/docs/99_PROJECT_CONSTITUTION.md +16 -4
- package/package.json +9 -9
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
- package/plugins/sp/skills/issue-finding/references/session-formats.md +9 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +35 -2
- package/plugins/sp/skills/spur-cli/references/features.md +3 -0
- package/plugins/sp/skills/spur-cli/references/message.md +15 -4
- package/plugins/sp/skills/spur-cli/references/tasks.md +3 -0
- package/plugins/sp/skills/spur-cli/references/workflows/validation-and-extension.md +18 -3
- package/plugins/sp/skills/spur-cli/references/workflows.md +8 -2
- package/schemas/state-machine-workflow.schema.json +137 -26
- package/schemas/transition-flow-workflow.schema.json +123 -23
- package/spur.js +1392 -388
|
@@ -3,7 +3,7 @@ name: Project Constitution
|
|
|
3
3
|
doc: 99_PROJECT_CONSTITUTION
|
|
4
4
|
owns: PROCESS — how the key files are maintained
|
|
5
5
|
authority: authoritative-on-process
|
|
6
|
-
version: 1.
|
|
6
|
+
version: 1.4.0
|
|
7
7
|
created_at: {{init-date}}
|
|
8
8
|
updated_at: {{init-date}}
|
|
9
9
|
edit_rules: 99 §6.8
|
|
@@ -148,6 +148,10 @@ Rules:
|
|
|
148
148
|
same change.
|
|
149
149
|
- `AGENTS.md` may **add** project facts; it may never **contradict** the numbered docs. On
|
|
150
150
|
contradiction, the numbered doc wins — fix `AGENTS.md`.
|
|
151
|
+
- In a monorepo, subdirectory `AGENTS.md` files merge with the root: the agent reads the root
|
|
152
|
+
first, then the file for the package it is working in. Each level carries only its own scope —
|
|
153
|
+
the root holds what spans packages, a package file holds what is true of that package alone.
|
|
154
|
+
Never restate one level's facts at another.
|
|
151
155
|
|
|
152
156
|
### 4.5 Index + satellite docs (`04`/`05` and their folders)
|
|
153
157
|
|
|
@@ -344,11 +348,19 @@ with raw file writes.
|
|
|
344
348
|
1. Factual blocks that mirror code — the command surface, the workspace layout, tool versions —
|
|
345
349
|
are **regenerated from code**, never edited from memory. Verify with the actual registrations
|
|
346
350
|
(e.g. list the CLI's registered nouns/verbs) before writing the block.
|
|
347
|
-
2.
|
|
351
|
+
2. File structure is the most perishable thing you can write down: paths move, and a stale path
|
|
352
|
+
sends an agent confidently to a file that is not there. Prefer capabilities and domain
|
|
353
|
+
vocabulary — which outlive layout — over directory listings; where a concrete path is genuinely
|
|
354
|
+
needed, regenerate it from code per rule 1.
|
|
355
|
+
3. Keep it lean: link to the owning doc instead of restating its facts. `AGENTS.md` repeats only
|
|
348
356
|
what an agent needs in the first 30 seconds of a session.
|
|
349
|
-
|
|
357
|
+
4. Keep the instruction count inside a budget: roughly 150–200 instructions, beyond which an agent
|
|
358
|
+
attends to them unevenly and the marginal rule buys nothing (MEDIUM confidence — secondary
|
|
359
|
+
citation, not measured here). Over budget, cut the rule or move it to its owning doc and link
|
|
360
|
+
per rule 3.
|
|
361
|
+
5. Surfaces that are decided-but-unbuilt are flagged as planned with their ADR pointer, and
|
|
350
362
|
marked "do not invoke as if they exist".
|
|
351
|
-
|
|
363
|
+
6. Re-synced whenever this file changes the map or process (§4.4).
|
|
352
364
|
|
|
353
365
|
### 6.8 This file (`99`)
|
|
354
366
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.47",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@commander-js/extra-typings": "^14.0.0",
|
|
56
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
57
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
58
|
-
"@gobing-ai/ts-dual-workflow-engine": "^0.4.
|
|
59
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
60
|
-
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.
|
|
61
|
-
"@gobing-ai/ts-rule-engine": "^0.4.
|
|
62
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
63
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
56
|
+
"@gobing-ai/ts-db": "^0.4.31",
|
|
57
|
+
"@gobing-ai/ts-ai-runner": "^0.4.31",
|
|
58
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.31",
|
|
59
|
+
"@gobing-ai/ts-infra": "^0.4.31",
|
|
60
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.31",
|
|
61
|
+
"@gobing-ai/ts-rule-engine": "^0.4.31",
|
|
62
|
+
"@gobing-ai/ts-runtime": "^0.4.31",
|
|
63
|
+
"@gobing-ai/ts-utils": "^0.4.31",
|
|
64
64
|
"@types/bun": "1.3.14",
|
|
65
65
|
"@types/figlet": "^1.7.0",
|
|
66
66
|
"@types/node-notifier": "8.0.5",
|
package/plugins/sp/plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.47",
|
|
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"]
|
|
@@ -202,7 +202,7 @@ Before any ad-hoc verification SQL against `history_*` tables, follow the schema
|
|
|
202
202
|
| `test-loop` | Same test command run 3+ times with no source edit between runs | (identical runs − 1) × ~2 min |
|
|
203
203
|
| `guard` | 3+ `spur task check` calls for the same task before pass | (extra checks) × ~3 min |
|
|
204
204
|
| `compaction` | Compactions > 5 per session | count × ~2.5 min |
|
|
205
|
-
| `section-write` | `spur task update --section` calls >
|
|
205
|
+
| `section-write` | `spur task update --section` calls per task > 1.5× the canonical section count for the task's variant/status matrix entry (feature-impl ≈ 9 sections ⇒ flag > ~13 writes/task; one write per canonical section is correct behavior, not waste) | (extra writes) × ~2 min |
|
|
206
206
|
| `git-red-herring` | `git stash` / `git branch` / `git diff` between test failures | ~5–20 min per incident |
|
|
207
207
|
| `verbose-output` | Unfiltered test output flooding (e.g. bare `tail` without failure filter) | per-run × ~1500 tokens |
|
|
208
208
|
|
|
@@ -47,12 +47,18 @@ When parsing a line of JSONL, look for tool/function invocations under common sh
|
|
|
47
47
|
|
|
48
48
|
| Source family | Typical tool block type / path | Bash/command field |
|
|
49
49
|
|---------------|--------------------------------|--------------------|
|
|
50
|
-
| OMP / omp-agent | `message.content[]` entries with `type: "toolCall"` | `
|
|
50
|
+
| OMP / omp-agent | `message.content[]` entries with `type: "toolCall"` | `arguments.command` |
|
|
51
51
|
| Claude Code | `type: "tool_use"` (or nested message content) | `input.command` / `input` |
|
|
52
52
|
| Codex / others | Importer-normalized or vendor-specific; search for `command`, `tool_name`, `name` | best-effort |
|
|
53
53
|
|
|
54
54
|
Always record **what field path you used** in the inventory Notes so evidence is auditable.
|
|
55
55
|
|
|
56
|
+
**Fail-loud rule:** a zero tool-command count across a **non-empty** session set means the field map
|
|
57
|
+
is wrong, not that the sessions were idle. Report a probable field-map error instead of an
|
|
58
|
+
idle-session / no-waste finding — a parser that matches nothing must never produce a clean verdict
|
|
59
|
+
(0534 R3; the OMP shape is `arguments.command`, verified: a toolCall block's keys are
|
|
60
|
+
`['arguments','id','intent','name','partialArgs','streamIndex','type']`).
|
|
61
|
+
|
|
56
62
|
**Loop detection (all sources):** normalize the shell command string and count consecutive or
|
|
57
63
|
near-consecutive identical invocations (≥3) without an intervening source-file edit tool call.
|
|
58
64
|
|
|
@@ -63,7 +69,8 @@ OMP/agent session logs are JSONL under `~/.omp/agent/sessions/-<project>/`:
|
|
|
63
69
|
- Each line is a JSON object with a `type` field
|
|
64
70
|
- Key event types: `session`, `message`, `compaction`, `title`, `title_change`, `custom`
|
|
65
71
|
- Tool calls live in `message.content` as blocks with `type: "toolCall"` (**not** `tool_use`)
|
|
66
|
-
- Bash tool calls expose `
|
|
72
|
+
- Bash tool calls expose `arguments.command` (verified live against OMP JSONL: a toolCall block's
|
|
73
|
+
keys are `['arguments','id','intent','name','partialArgs','streamIndex','type']`)
|
|
67
74
|
- Subagent sessions live in subdirectories (e.g. `Run0376/`, `Refine0378/`)
|
|
68
75
|
- Subagents may have `*.log` beside the JSONL session file
|
|
69
76
|
- Session start: `session.timestamp`
|
|
@@ -23,6 +23,7 @@ that before using `run` for fan-out dispatch.
|
|
|
23
23
|
| ---- | ------- | --------- |
|
|
24
24
|
| `run <prompt>` | Execute a prompt or slash command via a coding agent | `--agent <name>` `--model <name>` `--mode <mode>` `--continue` `--cwd <path>` `--drain` `--json` |
|
|
25
25
|
| `loop` | Persistent self-draining inbox loop for a team member (supervisor-managed) | `--agent <id>` `--poll <ms>` |
|
|
26
|
+
| `wait <specId>` | Identity-pinned wait for an occupant run to reach a lifecycle state (G4 wave 2) | `--run <runId>` `--until <state>...` `--timeout <ms>` `--json` |
|
|
26
27
|
| `list` | List detected coding agents, or team agent specs with `--specs` | `--specs` `--json` |
|
|
27
28
|
| `doctor [agent]` | Check agent readiness | `--json` |
|
|
28
29
|
| `create <id>` | Write a team agent spec to `.spur/agents/<id>.yaml` | `--type` `--tags` `--model` `--autonomy` `--system-prompt` `--name` `--workspace` `--purpose` `--auto-start` `--no-identity-preamble` `--json` |
|
|
@@ -46,7 +47,7 @@ through a coding agent as an external process, producing a persisted run record
|
|
|
46
47
|
### Flags
|
|
47
48
|
|
|
48
49
|
| Flag | Purpose |
|
|
49
|
-
|
|
50
|
+
| ------ | --------- |
|
|
50
51
|
| `--agent <name>` | Agent name or `auto`. Selects which installed coding agent executes the prompt. |
|
|
51
52
|
| `--model <name>` | Agent model argument (e.g. `o3`, `sonnet`). Passed through to the agent's model flag. |
|
|
52
53
|
| `--mode <mode>` | Agent output mode: `text` or `json`. |
|
|
@@ -96,6 +97,38 @@ under supervision.
|
|
|
96
97
|
The loop runs until `SIGINT` / `SIGTERM`. Each iteration: check inbox -> if messages, drain each
|
|
97
98
|
into `run` with `--drain` -> else sleep for `--poll` ms.
|
|
98
99
|
|
|
100
|
+
## `wait` - identity-pinned occupant wait (G4 wave 2)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
spur agent wait reviewer # default --until idle
|
|
104
|
+
spur agent wait reviewer --run R3 --until invoke-exit
|
|
105
|
+
spur agent wait reviewer --until working --until invoke-exit --timeout 30000 --json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`wait` pins an occupant's identity (`specId` + `runId` + `generation`) from the snapshot at wait
|
|
109
|
+
start, then polls until the first satisfied `--until` (OR). `--run` pins an explicit run; default
|
|
110
|
+
is the spec's latest run. Replacement, generation bump, or disappearance fails fast; a non-working
|
|
111
|
+
occupant that makes no progress inside the stall budget fails `wait_stalled`.
|
|
112
|
+
|
|
113
|
+
### Flags
|
|
114
|
+
|
|
115
|
+
| Flag | Purpose |
|
|
116
|
+
| ------ | --------- |
|
|
117
|
+
| `--run <runId>` | Pin a specific run id (default: the spec's latest run). |
|
|
118
|
+
| `--until <state>` | Lifecycle state to wait for (repeatable OR): `idle` \| `working` \| `invoke-exit` \| `blocked`. Default `idle`. |
|
|
119
|
+
| `--timeout <ms>` | Caller deadline. Undefined = no deadline (stall budget still applies). |
|
|
120
|
+
| `--json` | `{ satisfied, pin }` on success; `{ error: { code, message } }` on failure. |
|
|
121
|
+
|
|
122
|
+
### Exit codes + error envelope
|
|
123
|
+
|
|
124
|
+
| Code | Exit | Meaning |
|
|
125
|
+
| ------ | ------ | --------- |
|
|
126
|
+
| `occupant_gone` | 1 | No occupant for the specId, or it disappeared mid-wait. |
|
|
127
|
+
| `run_replaced` | 1 | The pinned run was replaced or its generation bumped. |
|
|
128
|
+
| `wait_stalled` | 1 | Non-working occupant, no progress within `min(timeout, 5000)ms`. |
|
|
129
|
+
| `timeout` | 1 | Caller `--timeout` elapsed (or aborted via SIGINT). |
|
|
130
|
+
| `usage` | 2 | Invalid flags, or `--until blocked` as the sole target (no first-class signal in wave 2). |
|
|
131
|
+
|
|
99
132
|
## `list` - detected agents and team specs
|
|
100
133
|
|
|
101
134
|
```bash
|
|
@@ -132,7 +165,7 @@ agent loop` can self-drain its inbox.
|
|
|
132
165
|
### Flags
|
|
133
166
|
|
|
134
167
|
| Flag | Purpose |
|
|
135
|
-
|
|
168
|
+
| ------ | --------- |
|
|
136
169
|
| `--type <agent-type>` | Agent spec type (e.g. `claude`, `codex`, `omp`). |
|
|
137
170
|
| `--tags <a,b>` | Comma-separated team identity tags (e.g. `team:alpha,role:worker`). |
|
|
138
171
|
| `--model <name>` | Agent model argument. |
|
|
@@ -99,6 +99,9 @@ spur feature update H2 --section "Acceptance Criteria" --from-file /tmp/ac.md
|
|
|
99
99
|
`--section` **requires** `--from-file` (exit `2` otherwise). Replaces the whole named section body.
|
|
100
100
|
The `## Tasks` block is still rebuilt by `refresh` (files win for that region).
|
|
101
101
|
|
|
102
|
+
Valid section names without a failed write: `spur task sections <wbs> list` (the same pointer
|
|
103
|
+
`spur task update --help` carries).
|
|
104
|
+
|
|
102
105
|
**Advance** (multi-hop forward walk):
|
|
103
106
|
|
|
104
107
|
```bash
|
|
@@ -19,7 +19,7 @@ use it well*.
|
|
|
19
19
|
|
|
20
20
|
| Verb | Purpose | Key flags |
|
|
21
21
|
| ---- | ------- | --------- |
|
|
22
|
-
| `send <body>` | Enqueue a message for an agent | `--to <id>` `--from <id>` `--json` |
|
|
22
|
+
| `send <body>` | Enqueue a message for an agent | `--to <id>` `--from <id>` `--wait` `--until <state>` `--timeout <ms>` `--json` |
|
|
23
23
|
| `inbox` | List messages addressed to an agent | `--agent <id>` `--json` |
|
|
24
24
|
| `reply <msg-id> <body>` | Thread a reply to a message | `--json` |
|
|
25
25
|
| `watch` | Follow an agent inbox - surface new messages as they arrive | `--agent <id>` `--interval <ms>` `--json` |
|
|
@@ -33,18 +33,29 @@ invalid usage.
|
|
|
33
33
|
spur message send "Please review PR 42" --to reviewer
|
|
34
34
|
spur message send "Task 0040 is blocked" --to worker-1 --from operator
|
|
35
35
|
spur message send "Done" --to planner --json
|
|
36
|
+
spur message send "Review 0042" --to reviewer --wait --until invoke-exit --timeout 30000
|
|
36
37
|
```
|
|
37
38
|
|
|
38
39
|
Enqueues a durable message addressed to `--to <id>`. The recipient drains it on its next `agent run
|
|
39
40
|
--drain` or `agent loop` iteration. `--from` defaults to `operator`.
|
|
40
41
|
|
|
42
|
+
`--wait` snapshots the recipient occupant **before** enqueue, then waits on that pin in the same CLI
|
|
43
|
+
process (G4 wave 2 / ADR-057). Default `--until invoke-exit`. A later occupant cannot satisfy the
|
|
44
|
+
wait; enqueue is **not** rolled back if the wait later fails.
|
|
45
|
+
|
|
41
46
|
### Flags
|
|
42
47
|
|
|
43
48
|
| Flag | Purpose |
|
|
44
|
-
|
|
49
|
+
| ------ | --------- |
|
|
45
50
|
| `--to <id>` | **Required.** Recipient agent id. |
|
|
46
51
|
| `--from <id>` | Sender id (default: `operator`). |
|
|
47
|
-
| `--
|
|
52
|
+
| `--wait` | Block until the recipient reaches `--until` (snapshots occupant before send). |
|
|
53
|
+
| `--until <state>` | Wait target: `injected` \| `invoke-exit` (repeatable OR). Default `invoke-exit`. |
|
|
54
|
+
| `--timeout <ms>` | Caller deadline in milliseconds. |
|
|
55
|
+
| `--json` | Output machine-readable JSON (`{ msgId, toId, status, wait: { satisfied } }`). |
|
|
56
|
+
|
|
57
|
+
`--wait` failures use the same error codes as `agent wait`: `occupant_gone`, `run_replaced`,
|
|
58
|
+
`wait_stalled`, `timeout` (exit 1).
|
|
48
59
|
|
|
49
60
|
## `inbox` - list addressed messages
|
|
50
61
|
|
|
@@ -79,7 +90,7 @@ lines.
|
|
|
79
90
|
### Flags
|
|
80
91
|
|
|
81
92
|
| Flag | Purpose |
|
|
82
|
-
|
|
93
|
+
| ------ | --------- |
|
|
83
94
|
| `--agent <id>` | **Required.** Agent id to watch. |
|
|
84
95
|
| `--interval <ms>` | Poll interval in milliseconds (default: `2000`). Must be a positive integer; exit `2` otherwise. |
|
|
85
96
|
| `--json` | Output one JSON object per new message. |
|
|
@@ -24,6 +24,7 @@ Start from the WBS, not the corpus layout:
|
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
spur task show <wbs> --json # metadata + full content + filePath
|
|
27
|
+
spur task get <wbs> --json # alias of `show` — same output, no separate code path
|
|
27
28
|
spur task path <wbs> --json # absolute path only
|
|
28
29
|
```
|
|
29
30
|
|
|
@@ -148,6 +149,8 @@ spur task update 0040 --section Review --from-file /tmp/review.md
|
|
|
148
149
|
read from a file (this is what makes edits crash-safe and reviewable). Exit `2` otherwise.
|
|
149
150
|
- Section names match the DD-08 task headings: `Background`, `Acceptance Criteria`, `Plan`,
|
|
150
151
|
`Solution`, `Testing`, `Review`, `References`, `History`.
|
|
152
|
+
- Valid section names without a failed write: `spur task sections <wbs> list` (the same
|
|
153
|
+
pointer `task update --help` carries).
|
|
151
154
|
- The write replaces the **whole** named section body; assemble the full section in the temp file
|
|
152
155
|
first, then point `--from-file` at it.
|
|
153
156
|
|
|
@@ -76,7 +76,22 @@ Once registered, any definition can use `kind: send-email` (in `onEnter`/`onExit
|
|
|
76
76
|
|
|
77
77
|
## Extension loading (trust-gated)
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
A workflow YAML can declare extension modules next to the workflow file itself:
|
|
80
|
+
|
|
81
|
+
```yaml
|
|
82
|
+
name: ext-flow
|
|
83
|
+
kind: state-machine
|
|
84
|
+
extensions:
|
|
85
|
+
actions: ["./exts/audit.ts"] # default-exports { name, actions: [...] }
|
|
86
|
+
guards: ["./exts/flag.ts"] # default-exports { name, guards: [...] }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`spur workflow validate`, `run` (incl. `--dry-run`), and `continue` load YAML-declared extensions
|
|
90
|
+
onto the engine host before any step — the declaration itself is the `allowExtensions` gate (0533/D4).
|
|
91
|
+
Paths are relative to the workflow file; absolute paths and `..` traversal are rejected with no
|
|
92
|
+
import, and a missing or mis-shaped module fails the command before any step.
|
|
93
|
+
|
|
94
|
+
For library callers, use `loadWorkflowExtensionsIntoHost`. The trust gate
|
|
80
95
|
is **fail-closed**: `allowExtensions` defaults to `false`, and a declared-but-not-allowed extension
|
|
81
96
|
**throws before any import** — never silently dropped.
|
|
82
97
|
|
|
@@ -117,8 +132,8 @@ reach for the library (`@gobing-ai/ts-dual-workflow-engine`) directly when you n
|
|
|
117
132
|
| ---------- | --- | ------- |
|
|
118
133
|
| Validate / run / list definitions | ✅ | ✅ |
|
|
119
134
|
| Trace run history / continue HITL / cancel / clean orphans | ✅ | ✅ |
|
|
120
|
-
| Custom action/guard runners | ✅ (built-ins
|
|
121
|
-
| Extension modules |
|
|
135
|
+
| Custom action/guard runners | ✅ (built-ins + YAML extensions, 0533/D4) | ✅ (`registerAction`/`registerGuard`) |
|
|
136
|
+
| Extension modules | ✅ (YAML `extensions.actions`/`extensions.guards`, 0533/D4) | ✅ (`loadWorkflowExtensionsIntoHost`) |
|
|
122
137
|
| DB persistence + programmatic `listRuns()` | (via configured adapter + `trace`) | ✅ (`DbWorkflowPersistenceAdapter`) |
|
|
123
138
|
| Event-bus observability (progress bars, dashboards) | partial (CLI step reporter on sync human runs) | ✅ (`WorkflowEngineEvents` via `WorkflowRunOptions.events`) |
|
|
124
139
|
| OTel traces / structured logs | (emitted) | ✅ (`RunLifecycle`) |
|
|
@@ -281,6 +281,7 @@ redirecting `agent.run` stages (ADR-047).
|
|
|
281
281
|
both scopes (lists what would be removed, writes nothing). `--json` returns
|
|
282
282
|
`{ olderThanMinutes, dryRun, cleaned, logs: { retentionDays, dryRun, reclaimed, failures } }` (with
|
|
283
283
|
`--logs`, the reclamation object alone).
|
|
284
|
+
|
|
284
285
|
## Behavior
|
|
285
286
|
|
|
286
287
|
This skill behaves as an **author** (choose mode → write a correct definition → prove it runs) feeding
|
|
@@ -302,8 +303,11 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
|
|
|
302
303
|
`.spur/workflows/basic.yaml` and `task-pipeline.yaml` quality-gate hop).
|
|
303
304
|
4. **`env.allow` is an allowlist.** `${env.X}` resolves only if `X` is listed under `env.allow`;
|
|
304
305
|
otherwise it resolves empty. A workflow that "loses" an environment value usually forgot to allow it.
|
|
305
|
-
5. **Extensions are fail-closed.**
|
|
306
|
-
|
|
306
|
+
5. **Extensions are fail-closed.** The CLI loads YAML-declared extension modules itself
|
|
307
|
+
(`extensions.actions` / `extensions.guards`, resolved relative to the workflow file) — the
|
|
308
|
+
declaration is the gate (0533/D4). A declared-but-missing or mis-shaped module, an absolute
|
|
309
|
+
path, or `..` traversal throws **before any import** — never silently dropped. Library callers
|
|
310
|
+
using `loadWorkflowExtensionsIntoHost` must pass `allowExtensions: true` explicitly. Inline
|
|
307
311
|
`host.registerAction`/`registerGuard` need no flag; only the module loader is gated.
|
|
308
312
|
6. **A failed run does not throw.** Action/guard failures come back as `WorkflowRunResult` with
|
|
309
313
|
`status: 'failed'`, preserving the run record. Read the trace; don't expect an exception. (Definition
|
|
@@ -341,11 +345,13 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
|
|
|
341
345
|
## Platform Notes
|
|
342
346
|
|
|
343
347
|
### Claude Code
|
|
348
|
+
|
|
344
349
|
Run `spur workflow` via the Bash tool. During development the CLI entry is a `.ts` file that runs only
|
|
345
350
|
under Bun: `bun run apps/cli/src/index.ts workflow validate <file> --json`. The installed `spur` binary
|
|
346
351
|
works once built.
|
|
347
352
|
|
|
348
353
|
### Codex / OpenClaw / OpenCode / Antigravity
|
|
354
|
+
|
|
349
355
|
Run `spur workflow ...` via the Bash tool; parse `--json` output programmatically. Arguments are passed
|
|
350
356
|
directly on the command line.
|
|
351
357
|
|
|
@@ -6,29 +6,83 @@
|
|
|
6
6
|
"additionalProperties": false,
|
|
7
7
|
"required": ["name", "initialState", "states", "transitions"],
|
|
8
8
|
"properties": {
|
|
9
|
-
"$schema": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
9
|
+
"$schema": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
},
|
|
12
|
+
"kind": {
|
|
13
|
+
"const": "state-machine"
|
|
14
|
+
},
|
|
15
|
+
"name": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"minLength": 1
|
|
18
|
+
},
|
|
19
|
+
"version": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
},
|
|
22
|
+
"description": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"initialState": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"minLength": 1
|
|
28
|
+
},
|
|
29
|
+
"terminalStates": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"minLength": 1
|
|
34
|
+
}
|
|
35
|
+
},
|
|
16
36
|
"failureStates": {
|
|
17
37
|
"type": "array",
|
|
18
|
-
"items": {
|
|
19
|
-
|
|
38
|
+
"items": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"minLength": 1
|
|
41
|
+
},
|
|
42
|
+
"description": "Subset of terminalStates whose reach finalizes the run as failed. Absent \u21d2 every terminal is a success."
|
|
43
|
+
},
|
|
44
|
+
"iterationBound": {
|
|
45
|
+
"type": "integer",
|
|
46
|
+
"exclusiveMinimum": 0
|
|
20
47
|
},
|
|
21
|
-
"iterationBound": { "type": "integer", "exclusiveMinimum": 0 },
|
|
22
48
|
"vars": {
|
|
23
49
|
"type": "object",
|
|
24
|
-
"propertyNames": {
|
|
25
|
-
|
|
50
|
+
"propertyNames": {
|
|
51
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
52
|
+
},
|
|
53
|
+
"additionalProperties": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
}
|
|
26
56
|
},
|
|
27
57
|
"env": {
|
|
28
58
|
"type": "object",
|
|
29
59
|
"additionalProperties": false,
|
|
30
60
|
"properties": {
|
|
31
|
-
"allow": {
|
|
61
|
+
"allow": {
|
|
62
|
+
"type": "array",
|
|
63
|
+
"items": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"extensions": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"additionalProperties": false,
|
|
73
|
+
"properties": {
|
|
74
|
+
"actions": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"$ref": "#/$defs/relativeExtensionPath"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"guards": {
|
|
81
|
+
"type": "array",
|
|
82
|
+
"items": {
|
|
83
|
+
"$ref": "#/$defs/relativeExtensionPath"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
32
86
|
}
|
|
33
87
|
},
|
|
34
88
|
"states": {
|
|
@@ -38,14 +92,29 @@
|
|
|
38
92
|
"additionalProperties": false,
|
|
39
93
|
"required": ["id"],
|
|
40
94
|
"properties": {
|
|
41
|
-
"id": {
|
|
42
|
-
|
|
95
|
+
"id": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"minLength": 1
|
|
98
|
+
},
|
|
99
|
+
"description": {
|
|
100
|
+
"type": "string"
|
|
101
|
+
},
|
|
43
102
|
"pause": {
|
|
44
103
|
"type": "boolean",
|
|
45
104
|
"description": "When true, the engine pauses the run at this state for HITL (E3); resume with `spur workflow continue`."
|
|
46
105
|
},
|
|
47
|
-
"onEnter": {
|
|
48
|
-
|
|
106
|
+
"onEnter": {
|
|
107
|
+
"type": "array",
|
|
108
|
+
"items": {
|
|
109
|
+
"$ref": "#/$defs/action"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"onExit": {
|
|
113
|
+
"type": "array",
|
|
114
|
+
"items": {
|
|
115
|
+
"$ref": "#/$defs/action"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
49
118
|
}
|
|
50
119
|
}
|
|
51
120
|
},
|
|
@@ -56,11 +125,23 @@
|
|
|
56
125
|
"additionalProperties": false,
|
|
57
126
|
"required": ["from", "to"],
|
|
58
127
|
"properties": {
|
|
59
|
-
"from": {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
128
|
+
"from": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"minLength": 1
|
|
131
|
+
},
|
|
132
|
+
"to": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"minLength": 1
|
|
135
|
+
},
|
|
136
|
+
"description": {
|
|
137
|
+
"type": "string"
|
|
138
|
+
},
|
|
139
|
+
"trigger": {
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
"guard": {
|
|
143
|
+
"$ref": "#/$defs/guard"
|
|
144
|
+
}
|
|
64
145
|
}
|
|
65
146
|
}
|
|
66
147
|
}
|
|
@@ -71,8 +152,14 @@
|
|
|
71
152
|
"additionalProperties": false,
|
|
72
153
|
"required": ["kind"],
|
|
73
154
|
"properties": {
|
|
74
|
-
"kind": {
|
|
75
|
-
|
|
155
|
+
"kind": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"minLength": 1
|
|
158
|
+
},
|
|
159
|
+
"options": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"additionalProperties": true
|
|
162
|
+
}
|
|
76
163
|
}
|
|
77
164
|
},
|
|
78
165
|
"guard": {
|
|
@@ -80,8 +167,32 @@
|
|
|
80
167
|
"additionalProperties": false,
|
|
81
168
|
"required": ["kind"],
|
|
82
169
|
"properties": {
|
|
83
|
-
"kind": {
|
|
84
|
-
|
|
170
|
+
"kind": {
|
|
171
|
+
"type": "string",
|
|
172
|
+
"minLength": 1
|
|
173
|
+
},
|
|
174
|
+
"options": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"additionalProperties": true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"relativeExtensionPath": {
|
|
181
|
+
"type": "string",
|
|
182
|
+
"minLength": 1,
|
|
183
|
+
"description": "Relative module path; absolute paths and '..' traversal are forbidden.",
|
|
184
|
+
"not": {
|
|
185
|
+
"anyOf": [
|
|
186
|
+
{
|
|
187
|
+
"pattern": "^[/\\\\]"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"pattern": "^[A-Za-z]:[/\\\\]"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"pattern": "(^|[/\\\\])\\.\\.([/\\\\]|$)"
|
|
194
|
+
}
|
|
195
|
+
]
|
|
85
196
|
}
|
|
86
197
|
}
|
|
87
198
|
}
|