@gobing-ai/spur 0.3.45 → 0.3.46

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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "plugins": [
8
8
  {
9
- "version": "0.3.45",
9
+ "version": "0.3.46",
10
10
  "name": "sp",
11
11
  "source": "./plugins/sp",
12
12
  "description": "Spur - my harness toolkits"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.45",
3
+ "version": "0.3.46",
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.30",
57
- "@gobing-ai/ts-ai-runner": "^0.4.30",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.4.30",
59
- "@gobing-ai/ts-infra": "^0.4.30",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.4.30",
61
- "@gobing-ai/ts-rule-engine": "^0.4.30",
62
- "@gobing-ai/ts-runtime": "^0.4.30",
63
- "@gobing-ai/ts-utils": "^0.4.30",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.3.45",
3
+ "version": "0.3.46",
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"]
@@ -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. |
@@ -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
- | `--json` | Output machine-readable JSON. |
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. |
@@ -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
- For modules that bundle multiple actions/guards, use `loadWorkflowExtensionsIntoHost`. The trust gate
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 only) | ✅ (`registerAction`/`registerGuard`) |
121
- | Extension modules | | ✅ (`loadWorkflowExtensionsIntoHost`) |
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.** Custom action/guard *modules* require `allowExtensions: true`; a
306
- declared-but-not-allowed extension throws **before any import** never silently dropped. Inline
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": { "type": "string" },
10
- "kind": { "const": "state-machine" },
11
- "name": { "type": "string", "minLength": 1 },
12
- "version": { "type": "string" },
13
- "description": { "type": "string" },
14
- "initialState": { "type": "string", "minLength": 1 },
15
- "terminalStates": { "type": "array", "items": { "type": "string", "minLength": 1 } },
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": { "type": "string", "minLength": 1 },
19
- "description": "Subset of terminalStates whose reach finalizes the run as failed. Absent ⇒ every terminal is a success."
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": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
25
- "additionalProperties": { "type": "string" }
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": { "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" } }
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": { "type": "string", "minLength": 1 },
42
- "description": { "type": "string" },
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": { "type": "array", "items": { "$ref": "#/$defs/action" } },
48
- "onExit": { "type": "array", "items": { "$ref": "#/$defs/action" } }
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": { "type": "string", "minLength": 1 },
60
- "to": { "type": "string", "minLength": 1 },
61
- "description": { "type": "string" },
62
- "trigger": { "type": "string" },
63
- "guard": { "$ref": "#/$defs/guard" }
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": { "type": "string", "minLength": 1 },
75
- "options": { "type": "object", "additionalProperties": true }
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": { "type": "string", "minLength": 1 },
84
- "options": { "type": "object", "additionalProperties": true }
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
  }
@@ -6,24 +6,75 @@
6
6
  "additionalProperties": false,
7
7
  "required": ["kind", "name", "initialNode", "nodes", "edges"],
8
8
  "properties": {
9
- "$schema": { "type": "string" },
10
- "kind": { "const": "transition-flow" },
11
- "name": { "type": "string", "minLength": 1 },
12
- "version": { "type": "string" },
13
- "description": { "type": "string" },
14
- "initialNode": { "type": "string", "minLength": 1 },
15
- "terminalNodes": { "type": "array", "items": { "type": "string", "minLength": 1 } },
16
- "iterationBound": { "type": "integer", "exclusiveMinimum": 0 },
9
+ "$schema": {
10
+ "type": "string"
11
+ },
12
+ "kind": {
13
+ "const": "transition-flow"
14
+ },
15
+ "name": {
16
+ "type": "string",
17
+ "minLength": 1
18
+ },
19
+ "version": {
20
+ "type": "string"
21
+ },
22
+ "description": {
23
+ "type": "string"
24
+ },
25
+ "initialNode": {
26
+ "type": "string",
27
+ "minLength": 1
28
+ },
29
+ "terminalNodes": {
30
+ "type": "array",
31
+ "items": {
32
+ "type": "string",
33
+ "minLength": 1
34
+ }
35
+ },
36
+ "iterationBound": {
37
+ "type": "integer",
38
+ "exclusiveMinimum": 0
39
+ },
17
40
  "vars": {
18
41
  "type": "object",
19
- "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" },
20
- "additionalProperties": { "type": "string" }
42
+ "propertyNames": {
43
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
44
+ },
45
+ "additionalProperties": {
46
+ "type": "string"
47
+ }
21
48
  },
22
49
  "env": {
23
50
  "type": "object",
24
51
  "additionalProperties": false,
25
52
  "properties": {
26
- "allow": { "type": "array", "items": { "type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" } }
53
+ "allow": {
54
+ "type": "array",
55
+ "items": {
56
+ "type": "string",
57
+ "pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
58
+ }
59
+ }
60
+ }
61
+ },
62
+ "extensions": {
63
+ "type": "object",
64
+ "additionalProperties": false,
65
+ "properties": {
66
+ "actions": {
67
+ "type": "array",
68
+ "items": {
69
+ "$ref": "#/$defs/relativeExtensionPath"
70
+ }
71
+ },
72
+ "guards": {
73
+ "type": "array",
74
+ "items": {
75
+ "$ref": "#/$defs/relativeExtensionPath"
76
+ }
77
+ }
27
78
  }
28
79
  },
29
80
  "nodes": {
@@ -33,10 +84,19 @@
33
84
  "additionalProperties": false,
34
85
  "required": ["id"],
35
86
  "properties": {
36
- "id": { "type": "string", "minLength": 1 },
37
- "description": { "type": "string" },
38
- "type": { "enum": ["action", "gate", "parallel", "decision"] },
39
- "action": { "$ref": "#/$defs/action" }
87
+ "id": {
88
+ "type": "string",
89
+ "minLength": 1
90
+ },
91
+ "description": {
92
+ "type": "string"
93
+ },
94
+ "type": {
95
+ "enum": ["action", "gate", "parallel", "decision"]
96
+ },
97
+ "action": {
98
+ "$ref": "#/$defs/action"
99
+ }
40
100
  }
41
101
  }
42
102
  },
@@ -47,10 +107,20 @@
47
107
  "additionalProperties": false,
48
108
  "required": ["from", "to"],
49
109
  "properties": {
50
- "from": { "type": "string", "minLength": 1 },
51
- "to": { "type": "string", "minLength": 1 },
52
- "description": { "type": "string" },
53
- "condition": { "$ref": "#/$defs/guard" }
110
+ "from": {
111
+ "type": "string",
112
+ "minLength": 1
113
+ },
114
+ "to": {
115
+ "type": "string",
116
+ "minLength": 1
117
+ },
118
+ "description": {
119
+ "type": "string"
120
+ },
121
+ "condition": {
122
+ "$ref": "#/$defs/guard"
123
+ }
54
124
  }
55
125
  }
56
126
  }
@@ -61,8 +131,14 @@
61
131
  "additionalProperties": false,
62
132
  "required": ["kind"],
63
133
  "properties": {
64
- "kind": { "type": "string", "minLength": 1 },
65
- "options": { "type": "object", "additionalProperties": true }
134
+ "kind": {
135
+ "type": "string",
136
+ "minLength": 1
137
+ },
138
+ "options": {
139
+ "type": "object",
140
+ "additionalProperties": true
141
+ }
66
142
  }
67
143
  },
68
144
  "guard": {
@@ -70,8 +146,32 @@
70
146
  "additionalProperties": false,
71
147
  "required": ["kind"],
72
148
  "properties": {
73
- "kind": { "type": "string", "minLength": 1 },
74
- "options": { "type": "object", "additionalProperties": true }
149
+ "kind": {
150
+ "type": "string",
151
+ "minLength": 1
152
+ },
153
+ "options": {
154
+ "type": "object",
155
+ "additionalProperties": true
156
+ }
157
+ }
158
+ },
159
+ "relativeExtensionPath": {
160
+ "type": "string",
161
+ "minLength": 1,
162
+ "description": "Relative module path; absolute paths and '..' traversal are forbidden.",
163
+ "not": {
164
+ "anyOf": [
165
+ {
166
+ "pattern": "^[/\\\\]"
167
+ },
168
+ {
169
+ "pattern": "^[A-Za-z]:[/\\\\]"
170
+ },
171
+ {
172
+ "pattern": "(^|[/\\\\])\\.\\.([/\\\\]|$)"
173
+ }
174
+ ]
75
175
  }
76
176
  }
77
177
  }