@gobing-ai/spur 0.3.42 → 0.3.43

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.
@@ -64,6 +64,30 @@ function normalizeArgs(raw: Args): Args {
64
64
  - If `--feature FOO` is present and `--tasks` is absent, treat the effective selector as `feature:FOO`.
65
65
  - If both are present, `--tasks` wins (with a one-line note in the batch report).
66
66
 
67
+ **Feature-derived strict preflight (R2, task 0510).** After normalization, if the **effective
68
+ selector** is `feature:<id>` (whether via `--tasks feature:<id>` or the `--feature <id>` sugar),
69
+ run a source-local strict feature check **once** before any task-list resolution, freeze,
70
+ dependency resolution, or worktree task execution:
71
+
72
+ ```bash
73
+ # monorepo source; installed projects use their resolved `spur` binary
74
+ bun run apps/cli/src/index.ts feature check <id> --strict --json
75
+ ```
76
+
77
+ - **Abort shape.** A non-zero check aborts the batch immediately: verdict `aborted`, zero attempted
78
+ tasks, and the structured feature findings (the `--json` finding list) reported verbatim. This is
79
+ the same abort vocabulary as cycle / unknown selector (Step 4/Step 5).
80
+ - **Exactly once.** The check runs once per batch, before `task list`; it is not re-run per task.
81
+ - **Non-feature exclusion.** Explicit WBS lists, status pseudo-lists, and `ready` selectors add no
82
+ feature check — only an effective `feature:<id>` selector is feature-derived. When explicit
83
+ `--tasks` overrides `--feature`, the effective selector is not feature-derived, so no check runs.
84
+ - **Why.** `FeatureCheckService` emits `L3.scope-delineation` (Scope lacking an In/Out split) as a
85
+ **warning**; feature-scoped batches never ran a strict check before freezing, so the late feature
86
+ transition became the first blocking check. A selector-local strict preflight catches a known
87
+ strict finding before any task pipeline action without changing corpus-wide severity.
88
+ - **Scope.** This preflight is advisory to severity policy: it does not alter `FeatureCheckService`,
89
+ `L3.scope-delineation` severity, `feature sync`, or batch-create.
90
+
67
91
  `--tasks <value>` (or the effective value after normalization) resolves to a frozen set of task WBS numbers. Resolution happens **once, at
68
92
  kickoff** — the driver never re-queries `spur task list` to recompute membership mid-batch (R2.1).
69
93
 
@@ -71,7 +95,7 @@ kickoff** — the driver never re-queries `spur task list` to recompute membersh
71
95
  |---|---|---|
72
96
  | Explicit WBS list | `^[0-9, ]+$` | Split on comma; validate each token is a 4-digit WBS; collect the explicit set. (R1.1) |
73
97
  | `feature:<id>` (via `--tasks` or `--feature <id>`) | literal `feature:` prefix or `--feature` flag | `spur task list --feature <id> --json`; collect `wbs` from each row. The `--feature` flag is sugar that becomes `--tasks feature:<id>` at the command layer. (R1.3) |
74
- | `ready` | literal `ready` | Resolve the union of `spur task list --status todo --json` + `spur task list --status backlog --json`, drop tasks with open children (R1.5, umbrella-parent exclusion below), then keep only tasks whose every `dependencies[]` entry resolves to `status == done` (via `spur task show <dep> --json`). Report each excluded task with its unmet dependency. (R1.4) |
98
+ | `ready` | literal `ready` | Resolve the union of `spur task list --status todo --json` + `spur task list --status backlog --json`, drop tasks with open children (R1.5, umbrella-parent exclusion below), then keep only tasks whose every `dependencies[]` entry resolves to `status == done` (via `spur task show <dep> --json | jq '{wbs, status, dependencies, feature_id}'` — R5 metadata-only). Report each excluded task with its unmet dependency. (R1.4) |
75
99
  | Status pseudo-list | `todo` \| `backlog` \| `wip` \| `blocked` \| `testing` | `spur task list --status <value> --json`; collect `wbs` from each row. (R1.2) |
76
100
  | *(else)* | no match | Error: "unknown selector `<value>`" — list the valid forms and halt before running anything. |
77
101
 
@@ -113,7 +137,7 @@ edge `A → B` means "A depends on B" (B must complete before A runs). Only edge
113
137
  ### 2.3 Out-of-set dependency resolution
114
138
 
115
139
  For each dependency edge to a task **outside** the frozen set, resolve its current status via
116
- `spur task show <dep-wbs> --json`:
140
+ `spur task show <dep-wbs> --json | jq '{wbs, status, dependencies, feature_id}'` (R5 metadata-only):
117
141
 
118
142
  - status `done` → edge satisfied, drop it from the graph (R2.5). The dependent is unblocked.
119
143
  - status ≠ `done` → mark the dependent **blocked**. Transitively mark its in-set descendants blocked
@@ -211,7 +235,7 @@ step.
211
235
  ```bash
212
236
  RUN=$(spur workflow run .spur/workflows/task-pipeline.yaml \
213
237
  --vars '{"wbs":"<wbs>","profile":"auto","agent":"claude"}' --async --json | jq -r '.runId')
214
- spur workflow trace "$RUN" --json # poll until status is terminal (done/failed)
238
+ spur workflow trace "$RUN" --json | jq '{runId, status, terminalState}' # poll until status is terminal (done/failed)
215
239
  ```
216
240
 
217
241
  ### 3.2 Flag → `--vars` passthrough (R4.2, R4.3)
@@ -262,6 +286,7 @@ in next-router; this only maps status → primary TABLE A hop for recovery.
262
286
 
263
287
  ### 3.3c Bounded feature-sync retry suppression (task 0411)
264
288
 
289
+
265
290
  During a batch, the per-task `record` step and the wrap-up `feature-transition` step each invoke
266
291
  feature status sync. When a feature is L4-gate-blocked (e.g. not all linked tasks are `done`), the
267
292
  identical blocked proposal repeats on every call with no intervening input change — in the H9
@@ -295,6 +320,42 @@ pipeline's `record` step and the wrap-up's `feature-transition` step. The driver
295
320
  The only observable difference is fewer redundant `feature sync` invocations and a one-line
296
321
  `feature-sync-bounded:` annotation on stderr when a duplicate is suppressed.
297
322
 
323
+ ### 3.4 Metadata-only host controller (R5, task 0510)
324
+
325
+ The batch **orchestrator** reads status, ordering, and terminal state — never task bodies or trace
326
+ output. Task `content`, section bodies (Solution/Testing/Review), and full workflow `output` are
327
+ stage/subagent data and must not enter the host context on the green path; the controller that
328
+ dispatches native subagents must not defeat that isolation by ingesting the very bodies the
329
+ subagents are meant to hold (task 0508's dispatch contract is preserved unchanged).
330
+
331
+ **Green-path projections — every controller-side read is projected to metadata:**
332
+
333
+ - `task show --json` reads pipe to `{wbs, status, dependencies, feature_id}` only:
334
+
335
+ ```bash
336
+ spur task show <wbs> --json | jq '{wbs, status, dependencies, feature_id}'
337
+ ```
338
+
339
+ Use this shape for out-of-set dependency resolution (Step 2.3), the `ready` selector's
340
+ dep-status lookups (Step 1), and any other controller-side `task show`. A status-only lookup may
341
+ narrow further (`| jq '.status'`), but never widen.
342
+
343
+ - Green-path trace observation projects to `{runId, status, terminalState}` only:
344
+
345
+ ```bash
346
+ spur workflow trace "$RUN" --json | jq '{runId, status, terminalState}'
347
+ ```
348
+
349
+ The controller decides continue/halt from `status`/`terminalState` (ADR-044: judge a run by
350
+ `status === 'done'`, never by string-matching a `finalState` name) plus the bounded verdict
351
+ artifact `.spur/run/<wbs>-verdict.json`. It never streams or re-reads a full trace merely to
352
+ summarize status.
353
+
354
+ **Failure-path reads are bounded.** On a failed/blocked task, request only the terminal error and
355
+ the minimal anchor set the batch report needs (e.g. the blocking finding line, the unmet-dep WBS,
356
+ the verdict line) — never the entire trace. If a fuller trace is needed for diagnosis, that read
357
+ belongs to a subagent or the operator, not to the batch controller's report loop.
358
+
298
359
  ## Step 4 — Failure policy (R3)
299
360
 
300
361
  ### 4.1 Stop-the-batch (default) (R3.1)
@@ -683,6 +744,8 @@ command doc so it does not read as a bug.
683
744
  | R5.1 (orchestrator boundary) | "Zero engine code" preamble + Step 3 |
684
745
  | R5.2 (structured batch report) | Step 5 |
685
746
  | 0411 (bounded feature-sync retry suppression) | Step 3.3c — wrapper lives in pipeline `record` + wrap-up `feature-transition`; driver unchanged |
747
+ | 0510 R2 (feature-derived strict preflight) | Step 1 — "Feature-derived strict preflight (R2, task 0510)" |
748
+ | 0510 R5 (metadata-only host controller) | Step 3.4 + projected `task show` / trace snippets in Step 1, 2.3, 3.1 |
686
749
 
687
750
  ## Parallel Execution
688
751
 
@@ -52,8 +52,10 @@ one thing and yields, so the **pipeline (not the agent) owns the loop**.
52
52
 
53
53
  Interactive omit/`inline` executes these model stages through the
54
54
  [inline pipeline driver](inline-pipeline-driver.md) in the host session and records stage/session
55
- provenance. `--agent <name|auto>`, parallel batches, and headless workflow invocation select the
56
- existing subprocess actions. Direct invocations of the same dev operations remain inline by
55
+ provenance. The host is the controller; eligible `agent.run` stages may dispatch once to a native
56
+ subagent (task 0508), with host fallback and no post-dispatch replay see the driver reference.
57
+ `--agent <name|auto>`, parallel batches, and headless workflow invocation select
58
+ the existing subprocess actions. Direct invocations of the same dev operations remain inline by
57
59
  default. See the [inline-default execution-surface contract](cross-cutting.md#inline-default-execution-surface).
58
60
 
59
61
  > **Single-run & parse discipline (suite run cost control).** Run full quality/test suites (`bun run check` / `spur-check`) at most ONCE per task iteration (task 0436 R2). Parse failure details from the single retained command output rather than re-running full suites repeatedly to inspect errors. Re-run targeted/narrow test files (e.g. `bun test <file> --test-name-pattern <pattern>`) while iterating on fixes, and re-run the full suite only when all targeted fixes pass.
@@ -47,7 +47,7 @@ The value table below is the C3a cross-file parity surface (kept in lockstep wit
47
47
 
48
48
  | Value | Who does the work | Derived surface |
49
49
  | ------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
50
- | `inline` (default when omitted) | Whoever is running this session (interactive) or `agent.default` (headless) | Interactive: inline (host session); headless: subprocess of `agent.default` |
50
+ | `inline` (default when omitted) | Whoever is running this session (interactive) or `agent.default` (headless) | Interactive: inline host-controlled, eligible model stages may use a native subagent (0508); headless: subprocess of `agent.default` |
51
51
  | `auto` | Tier-resolved from the stage's `min_tier` + `fallback` | Subprocess |
52
52
  | `<name>` | That coding agent or configured executor | Inline when it is the current session's agent; subprocess otherwise |
53
53
 
@@ -53,21 +53,53 @@ Action semantics come from the YAML and the workflow action contract:
53
53
  actions/guards.
54
54
  - `hitl.confirm` — under `profile=auto`, follow the YAML's auto-skip transition. Otherwise pause,
55
55
  surface the prompt, and resume from the same state with the operator's answer.
56
- - `agent.run` — execute the action's slash command through its backing skill in the current host
57
- session. Do not call `spur agent run` and do not re-enter `/sp:dev-run --mode full`. Preserve the
58
- YAML options: capture `answerFile`; assert `expectFile`; enforce `requireDiff` against a
59
- pre-action git snapshot, including the task-scope guard; honor declared error policy. `timeoutMs`
60
- is recorded as not applicable because the host session has no independent kill boundary.
61
-
62
- After every successful inline `agent.run` action append exactly:
56
+ - `agent.run` — execute the action's slash command, native-subagent-first (task 0508). Do not call
57
+ `spur agent run` and do not re-enter `/sp:dev-run --mode full`. Preserve the YAML options: capture
58
+ `answerFile`; assert `expectFile`; enforce `requireDiff` against a pre-action git snapshot,
59
+ including the task-scope guard; honor declared error policy. `timeoutMs` is recorded as not
60
+ applicable because the host session has no independent kill boundary.
61
+
62
+ **Native-subagent dispatch (R2 eligibility, evaluated before each action):**
63
+
64
+ 1. The invocation is one of the two interactive inline full-pipeline surfaces (`dev-run --mode full`
65
+ or sequential `dev-runall`, omit/`inline`).
66
+ 2. The YAML action kind is `agent.run` and its input is a pure slash command. Shell, note, file,
67
+ guard, and operator-interaction actions remain host-executed.
68
+ 3. The current state/action has no operator-confirmation action, `pause: true`, approve/taste/ask
69
+ decision, or other operator prompt.
70
+ 4. The platform exposes a native subagent that shares the working tree and has read, write, shell,
71
+ and Spur task/run-artifact access.
72
+
73
+ All four pass → dispatch. Any pre-dispatch failure → execute the stage **once** in the host session.
74
+ No token estimate, stage-size threshold, model heuristic, or configuration switch is added.
75
+
76
+ **Dispatch and join:** before dispatch, capture the same pre-action git snapshot used by
77
+ `requireDiff` enforcement. Send only: the stage id, the YAML's exact pure slash command, and
78
+ `execution surface already resolved: native subagent; do not dispatch this stage again`. The WBS/path
79
+ already carried by the slash command is the handoff — do not paste task/session transcripts or embed
80
+ machine-specific session paths. Dispatch exactly one native subagent and wait for it; the inline FSM
81
+ must not advance actions or guards concurrently (one writer at a time). After join, validate
82
+ `answerFile`, `expectFile`, `requireDiff`, task scope, and the action's error policy from the shared
83
+ filesystem — a subagent success message is not evidence. On success append exactly:
63
84
 
64
85
  ```text
65
- stage <id> executed inline in session <session-id>
86
+ stage <id> executed via subagent <agent-id> (host session <session-id>)
66
87
  ```
67
88
 
68
- to `.spur/run/<run-id>.log`, where `<id>` is the current YAML state id. Also log start/failure and
69
- the ignored timeout value so an inline run remains auditable without fabricating an
70
- `AgentRunTracedResult`.
89
+ Host fallback retains exactly `stage <id> executed inline in session <session-id>`. If launch fails
90
+ before the subagent starts, log the reason and use host fallback. If a started subagent fails or
91
+ leaves invalid artifacts, do **not** replay the stage in the host — follow the YAML error policy so
92
+ partial mutations are not duplicated.
93
+
94
+ **Host-owned interaction:** the host alone executes operator-confirmation actions, owns
95
+ `pause: true`, and surfaces approve/taste/ask decisions. A subagent that discovers missing authority
96
+ or an operator decision returns a blocker; the host pauses at the current state and presents it. The
97
+ subagent cannot approve, infer consent, or recursively invoke the full pipeline.
98
+
99
+ After every successful inline `agent.run` action append exactly one provenance line (inline or
100
+ subagent form above) to `.spur/run/<run-id>.log`, where `<id>` is the current YAML state id. Also
101
+ log start/failure and the ignored timeout value so an inline run remains auditable without
102
+ fabricating an `AgentRunTracedResult`.
71
103
 
72
104
  Transition guards are not advisory. Execute the declared guard exactly, in order, with the same
73
105
  resolved variables and artifacts. `--no-lifecycle` remains bookkeeping only; the YAML's task checks,