@gobing-ai/spur 0.3.63 → 0.3.64

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.
Files changed (36) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +3326 -3886
  3. package/config/workflows/history-anatomy.yaml +2 -2
  4. package/package.json +9 -9
  5. package/plugins/sp/README.md +6 -3
  6. package/plugins/sp/agents/expert-spur.md +20 -4
  7. package/plugins/sp/commands/dev-find-issue.md +1 -1
  8. package/plugins/sp/commands/dev-gitmsg.md +12 -6
  9. package/plugins/sp/commands/dev-gtd.md +8 -19
  10. package/plugins/sp/commands/dev-idea.md +1 -1
  11. package/plugins/sp/commands/dev-plan.md +1 -1
  12. package/plugins/sp/commands/dev-run.md +2 -2
  13. package/plugins/sp/commands/dev-runall.md +2 -2
  14. package/plugins/sp/commands/dev-wrap.md +5 -6
  15. package/plugins/sp/commands/dev-wrapall.md +5 -7
  16. package/plugins/sp/plugin.json +1 -1
  17. package/plugins/sp/scripts/history-anatomy-cache.mjs +3 -2
  18. package/plugins/sp/scripts/history-anatomy-cache.ts +7 -2
  19. package/plugins/sp/skills/dogfood-testing/SKILL.md +14 -1
  20. package/plugins/sp/skills/history-anatomy/references/report-contract.md +8 -0
  21. package/plugins/sp/skills/next-router/SKILL.md +4 -4
  22. package/plugins/sp/skills/pr-reviewing/SKILL.md +2 -3
  23. package/plugins/sp/skills/redesign-web-ui/SKILL.md +184 -0
  24. package/plugins/sp/skills/redesign-web-ui/references/audit-checklist.md +121 -0
  25. package/plugins/sp/skills/redesign-web-ui/references/upgrade-techniques.md +66 -0
  26. package/plugins/sp/skills/spur-cli/references/agent.md +1 -1
  27. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +5 -0
  28. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +20 -5
  29. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +230 -0
  30. package/plugins/sp/skills/spur-cli/references/workflows.md +26 -4
  31. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +22 -30
  32. package/plugins/sp/skills/spur-dev/references/dev-operations.md +62 -20
  33. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +1 -1
  34. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +18 -8
  35. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +7 -6
  36. package/spur.js +297 -214
@@ -0,0 +1,230 @@
1
+ ---
2
+ name: workflow-fit-and-tuning
3
+ description: Decide whether a process should be a spur workflow at all, tune an accepted one for latency and observability, keep its nodes inside the simplicity budget, and refactor across the boundary — promote a descriptive procedure into YAML, demote a YAML back to prose, or optimize one in place.
4
+ see_also:
5
+ - spur-cli
6
+ - operations
7
+ - authoring-workflows
8
+ ---
9
+
10
+ # Workflow Fit & Tuning
11
+
12
+ Three questions that sit **before and around** the mode gate:
13
+
14
+ 1. **Fit** — should this be a `spur workflow` at all, or a descriptive procedure / checklist?
15
+ 2. **Tuning** — given it is one, how is it configured for low latency and legible traces?
16
+ 3. **Refactor** — how do you move a process across that boundary in either direction?
17
+
18
+ **Polaris:** get things done with high efficiency, low latency, and observability. A workflow that
19
+ does not beat prose on all three is prose with a YAML tax.
20
+
21
+ ---
22
+
23
+ ## 1. Fit gate — workflow or prose?
24
+
25
+ Run this **before** the [mode-selection gate](operations.md#sub-procedure-mode-selection-gate). The
26
+ mode gate answers *which kind of workflow*; it presumes an answer to *whether* — and that
27
+ presumption is the more expensive one to get wrong.
28
+
29
+ ### What each side actually buys
30
+
31
+ | | `spur workflow` YAML | Descriptive procedure (skill reference, slash command, checklist) |
32
+ | --- | --- | --- |
33
+ | Buys you | Durable run record, resumable HITL pause, bounded retry loops, machine-readable terminal status, `trace`/`--follow`, `cancel`/`clean`, unattended `--async` | Judgment at every step, zero authoring ceremony, edits are one-line, no subprocess per step |
34
+ | Costs you | Authoring + `validate`/`dry-run` upkeep, one subprocess per action node, indirection an agent must read through, a definition that rots when the surface moves | Nothing is replayable, nothing is recorded, "what happened on that run" is unanswerable |
35
+ | Fails at | Steps whose outcome only a reader can judge | Anything that must run the same way twice, unattended |
36
+
37
+ ### The three-part test
38
+
39
+ A process earns a workflow only when **all three** hold:
40
+
41
+ - **Replay** — it runs repeatedly, unattended, or across different operators.
42
+ - **Branch** — at least one step routes on a *machine-checkable* predicate (exit code, JSON field,
43
+ file present), and the run may retry or gate on it.
44
+ - **Record** — someone will later need to answer "what happened on run X" from persisted evidence.
45
+
46
+ Three yes → author the workflow. Two → borderline: prefer prose plus one shell script, and revisit
47
+ when the run count justifies it. One or zero → **descriptive procedure**; say so and stop.
48
+
49
+ ### Signals
50
+
51
+ | Signal in the described process | Read as |
52
+ | --- | --- |
53
+ | "Retry until the gate passes", "loop until clean" | workflow — bounded loop is the engine's job |
54
+ | "Pause for approval, continue later / in another session" | workflow — HITL resume needs a run record |
55
+ | "Every night", "for each task in the batch", unattended | workflow — replay + record |
56
+ | "Then check whether it looks right and decide" | prose — the predicate is judgment, not an exit code |
57
+ | "It depends what the diff says" | prose — the branch has no machine-checkable condition |
58
+ | Runs once, then the situation has changed | prose — a checklist, not a definition |
59
+ | One operator, in-session, watching each step | prose — the trace has no second reader |
60
+
61
+ ### The two anti-patterns
62
+
63
+ - **Prose wearing YAML.** Every node is an `agent.run` with a raw prompt, edges are all
64
+ unconditional. The engine adds a process spawn per node and contributes no branching, no gate, no
65
+ retry. This is the ADR-069 R2 measure firing on *every* node — the advisory is telling you the
66
+ whole file is misplaced, not that eight prompts need polish. Demote it.
67
+ - **YAML wearing prose.** A skill reference that says "then run the checks, and if they fail fix and
68
+ re-run, up to three times" — a bounded retry loop written as a paragraph an agent re-interprets
69
+ every session, with no record of how many attempts actually happened. Promote it.
70
+
71
+ ### Hybrid is the usual answer
72
+
73
+ The boundary runs *through* most processes, not around them. Keep judgment in the skill or slash
74
+ command; put the deterministic gate loop in the workflow; let the workflow's judgment steps call the
75
+ command by name. That is the ADR-043 preference restated as an architecture: **the workflow selects
76
+ and orders capabilities, it does not contain them** (ADR-069).
77
+
78
+ ---
79
+
80
+ ## 2. Tuning — latency, efficiency, observability
81
+
82
+ ### Cost model — know what you are spending
83
+
84
+ | Node / element | Real cost | Consequence for design |
85
+ | --- | --- | --- |
86
+ | `agent.run` action | A full agent session: seconds to minutes, plus tokens | **The dominant cost.** Count them; every one you remove is the largest single win available |
87
+ | `shell` action | One `sh -c` subprocess, milliseconds | Cheap individually — but each node is also a persisted transition round-trip |
88
+ | Guard (`kind: shell`) | One subprocess **per evaluation**, re-run on every loop iteration | A loop with a bound of 5 pays its guards 5 times |
89
+ | Loop `iterationBound` | Worst case = bound x per-iteration cost | It is a **latency ceiling**, not only a runaway safety net |
90
+ | HITL pause | Unbounded wall-clock (waits on a human) | Never put one inside a loop body |
91
+
92
+ ### Latency checklist
93
+
94
+ - [ ] **Fewest `agent.run` nodes that still do the work.** Two adjacent judgment steps with no gate
95
+ between them are one judgment step.
96
+ - [ ] **Soft status-file probe over repeated probing.** Run the expensive check once in an action
97
+ that always exits 0 and writes its verdict to a run-scoped file; branch with ordered cheap
98
+ guards that read that file. One subprocess instead of one per branch — the `basic.yaml` and
99
+ `task-pipeline.yaml` quality-gate idiom.
100
+ - [ ] **Order guards cheapest-discriminating-first.** The first passing guard wins, so a `test -f`
101
+ ahead of a `spur … --json` parse skips the expensive call on the common path.
102
+ - [ ] **No guard recomputes what a prior node already wrote to disk.**
103
+ - [ ] **`iterationBound` from the budget, not from optimism.** Set it to the observed maximum plus
104
+ one; a bound of 10 on a 40-second gate is a seven-minute worst case nobody chose.
105
+ - [ ] **Fan out only genuinely independent branches** (`type: parallel`, transition-flow). Serial
106
+ nodes with no data dependency are pure dead latency.
107
+ - [ ] **`--async` when the caller does not need the terminal state inline**, then follow with
108
+ `spur workflow trace <run-id> --follow`.
109
+
110
+ ### Observability is an authoring decision, not a run flag
111
+
112
+ The flags (`--detail`, `--verbose`, `--trace-file`, `--follow`, `--output`) are catalogued in
113
+ [../workflows.md](../workflows.md); they only expose what the definition already made legible.
114
+
115
+ - [ ] **Name states/nodes after the outcome they establish, not the tool they invoke.**
116
+ `quality-gate-passed` reads at 3am; `run-script-2` does not.
117
+ - [ ] **`description` carries the WHY** of the workflow; a one-line comment carries the why of any
118
+ non-obvious guard order or `iterationBound`.
119
+ - [ ] **Every gate writes a machine-readable verdict to a run-scoped file.** It is the guard's input
120
+ *and* the post-mortem's evidence — one artifact, two consumers.
121
+ - [ ] **Declare `failureStates`** so a failed run reports `status: 'failed'` instead of finalizing as
122
+ a `done` run that landed somewhere bad (the 0425 reader contract).
123
+ - [ ] **`env.allow` lists exactly what is used** — no more; an over-broad allowlist is an
124
+ undocumented dependency.
125
+ - [ ] **While tuning:** `--detail full` plus `--trace-file`, and compare traces. **In production:**
126
+ default detail, retained run log.
127
+
128
+ ---
129
+
130
+ ## 3. Node simplicity budget
131
+
132
+ Simplicity is the operating constraint, and it is already measurable — `spur workflow validate`
133
+ reports it. Do not invent a second threshold; author to the one that is frozen (ADR-069, task 0614).
134
+
135
+ | Element | Budget | What breaching it means |
136
+ | --- | --- | --- |
137
+ | `shell` action `command` | **<= 5** non-comment units (split on newline and `;`) | >= 6 flags the composition advisory: the program holds reusable behavior that wants an owner |
138
+ | `agent.run` action `input` | A **slash command or skill invocation** | A raw prose prompt flags: the operation belongs behind a centralized command (ADR-043). Prompt length sets severity only |
139
+ | Transition guard | **One** boolean predicate | Guards are exempt from the shell measure by design. A guard needing five lines is a probe node in disguise — make it one |
140
+ | Node count | Every node earns its transition round-trip | A node that always runs immediately after another, with no guard between them, is one node |
141
+
142
+ **When a node breaches the budget, do not reformat to dodge the measure.** Joining five lines with
143
+ `&&` moves the complexity, not the ownership. Pick an owner from the five recorded options in
144
+ `docs/design/workflow-shell-ownership.md`: public `spur` verb (consent-gated), application service,
145
+ least-privilege built-in action kind, workflow-relative external extension, or a recorded
146
+ stays-shell exception with its reason in `config/workflow-composition-baseline.json`.
147
+
148
+ **Advisory posture is binding.** Composition findings never block a run, never change a `validate`
149
+ exit status, and are never a reason to hot-edit an executing pipeline. Surface them; fix on operator
150
+ acceptance.
151
+
152
+ ---
153
+
154
+ ## 4. Refactor — moving across the boundary
155
+
156
+ Three named directions. All three end in the shared
157
+ [validate-and-dry-run core](operations.md#sub-procedure-validate-and-dry-run) when a YAML definition
158
+ survives the change.
159
+
160
+ ### promote — descriptive procedure → workflow
161
+
162
+ Use only when the [fit gate](#the-three-part-test) clears all three parts. Steps:
163
+
164
+ 1. **Split the prose into spine and judgment.** The spine is every step whose outcome is
165
+ machine-checkable. Everything else stays judgment and does *not* become a node's inline prompt.
166
+ 2. **Name the terminals** — the success terminal, and every failure terminal that deserves its own
167
+ name. Declare them in `failureStates`.
168
+ 3. **Run the [mode-selection gate](operations.md#sub-procedure-mode-selection-gate)** — the spine's
169
+ shape decides it: retry loop → state-machine; forward pipeline → transition-flow.
170
+ 4. **Author one node per spine step.** Each judgment step becomes an `agent.run` whose `input`
171
+ *references the existing slash command or skill* — never a copy of the prose. If no such command
172
+ exists, create it first; a promotion that inlines prompts has produced anti-pattern one.
173
+ 5. **Verify** with validate-and-dry-run against the expected terminal.
174
+ 6. **Rewrite the descriptive doc as the entry point** — it now explains the WHY and delegates to
175
+ `spur workflow run`, rather than restating the steps. Two copies of the procedure is the drift
176
+ the promotion was supposed to end.
177
+
178
+ ### demote — workflow → descriptive procedure
179
+
180
+ Triggers (any two are sufficient; the first alone is sufficient):
181
+
182
+ - Every node is an `agent.run` with a raw prompt and every edge is unconditional.
183
+ - The definition has been edited more often than it has been run.
184
+ - The graph is a straight line — no guard, no gate, no loop.
185
+ - The run count over its lifetime is in the single digits and not growing.
186
+ - Its `iterationBound` has never been reached because nothing ever loops.
187
+
188
+ Steps:
189
+
190
+ 1. **Check for live dependents** — `spur workflow trace --workflow <name> --last 20 --json` for
191
+ recent runs, and grep the shipped surfaces for the filename. A definition another command invokes
192
+ is not demoted unilaterally.
193
+ 2. **Return each node's work to its owner** — judgment nodes to the command or skill they should
194
+ have been calling; a genuinely useful shell sequence to one script under its owning surface.
195
+ 3. **Rewrite the entry surface as the procedure**, in the order the graph ran.
196
+ 4. **Delete the YAML and its `config/workflow-composition-baseline.json` entries** in the same
197
+ change. A baseline entry whose action no longer exists fails the two-sided check.
198
+ 5. **Record the demotion** and its trigger, so the next author does not re-promote it by reflex.
199
+
200
+ ### optimize — refine an accepted workflow in place
201
+
202
+ A [refine](operations.md#refine) whose `--intent` is latency or legibility rather than correctness.
203
+ Dimensions, highest leverage first:
204
+
205
+ 1. **Remove or merge `agent.run` nodes** — the dominant cost, always the first pass.
206
+ 2. **Collapse probe-then-branch into a soft status-file probe** with ordered guards.
207
+ 3. **Reorder guards** cheapest-discriminating-first.
208
+ 4. **Lower `iterationBound`** to the observed maximum plus one.
209
+ 5. **Merge nodes that always run together** with no guard between them.
210
+ 6. **Move any `>= 6`-unit shell program to a recorded owner** (never by reformatting).
211
+ 7. **Rename states/nodes to outcomes** and add the missing `failureStates`.
212
+
213
+ **Measure, do not estimate.** Capture `spur workflow trace <run-id> --json` before and after and
214
+ compare wall-clock per state and the transition sequence. An optimization with no trace pair behind
215
+ it is a preference. Rules that still bind: the smallest change that meets the intent, no mode
216
+ switch inside a refine (that is a rewrite — hand it to `add`), and never edit a workflow that is
217
+ currently executing.
218
+
219
+ ---
220
+
221
+ ## Checklist
222
+
223
+ - [ ] Fit gate run **before** the mode gate; the prose-vs-workflow verdict stated with its reason.
224
+ - [ ] All three of replay / branch / record hold, or the answer was a descriptive procedure.
225
+ - [ ] Judgment steps reference an existing command; none inline a raw prompt.
226
+ - [ ] `agent.run` node count is the minimum the branching requires.
227
+ - [ ] `iterationBound` chosen from a latency budget; guards ordered cheapest-first.
228
+ - [ ] States/nodes named for outcomes; `failureStates` declared; gates write a verdict file.
229
+ - [ ] Every `shell` action within the <= 5-unit budget, or carrying a recorded owner/disposition.
230
+ - [ ] Refactors verified through validate-and-dry-run; optimizations backed by a before/after trace.
@@ -9,12 +9,24 @@ see_also:
9
9
 
10
10
  `spur workflow` runs declarative YAML workflows (powered by `@gobing-ai/ts-dual-workflow-engine`) that
11
11
  orchestrate a multi-step process — an implement→check→fix loop, an import→validate→transform→write
12
- pipeline, an approval gate. The engine executes **two distinct workflow kinds**, and the first act of
13
- any new workflow is choosing between them. Get the mode right and the rest of authoring follows the
12
+ pipeline, an approval gate. The engine executes **two distinct workflow kinds**, and choosing between
13
+ them is the first act of *authoring*. Get the mode right and the rest of authoring follows the
14
14
  schema; get it wrong and you fight the engine.
15
15
 
16
- Operating a workflow well is a full lifecycle — choose the mode, author the YAML, validate it, run it,
17
- read the trace, and refine. This skill covers all of it.
16
+ Operating a workflow well is a full lifecycle — decide it should be a workflow at all, choose the mode,
17
+ author the YAML, validate it, run it, read the trace, tune it, and refine. This skill covers all of it.
18
+
19
+ ## Decide it is a workflow at all — before the mode
20
+
21
+ The mode gate presumes the process belongs in YAML. That presumption is the more expensive one to get
22
+ wrong: a workflow whose nodes are all raw `agent.run` prompts is a descriptive procedure paying a
23
+ process spawn per step, and a bounded retry loop written as a paragraph in a skill reference is a
24
+ workflow nobody authored. Run the **fit gate** first — a process earns a workflow only when it
25
+ replays, branches on a machine-checkable predicate, **and** needs a durable per-run record.
26
+
27
+ Fit gate, cost model, latency/observability tuning, the node simplicity budget, and the
28
+ promote / demote / optimize refactor procedures:
29
+ [workflows/workflow-fit-and-tuning.md](workflows/workflow-fit-and-tuning.md).
18
30
 
19
31
  ## Choose the execution mode first
20
32
 
@@ -62,6 +74,9 @@ before authoring. Full procedure: [workflows/authoring-workflows.md](workflows/a
62
74
 
63
75
  Use this skill to:
64
76
 
77
+ - **Decide fit** — judge whether a process should be a workflow at all or stay a descriptive
78
+ procedure / checklist, and tune an accepted one for latency and legible traces.
79
+ → workflows/workflow-fit-and-tuning.md
65
80
  - **Author a workflow** — turn a described process into a validated, dry-run-verified YAML definition
66
81
  in the right mode. → authoring-workflows.md
67
82
  - **Validate before trusting** — schema + semantic-check a workflow file (references, terminal
@@ -70,6 +85,9 @@ Use this skill to:
70
85
  taken, terminal status).
71
86
  - **Refine an existing workflow** — fix a stuck guard, add a state/node, retune `iterationBound`,
72
87
  re-scope variables / `env.allow`, with the smallest change. → workflows/operations.md
88
+ - **Refactor across the prose/YAML boundary** — promote a descriptive procedure into a workflow,
89
+ demote a workflow back to prose, or optimize an accepted one for latency.
90
+ → workflows/workflow-fit-and-tuning.md
73
91
  - **Extend the engine** — register a custom action/guard runner or a trust-gated extension module when
74
92
  the built-ins (`note`, `shell`, `always`, `action-ok`) fall short. → workflows/validation-and-extension.md
75
93
 
@@ -362,6 +380,10 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
362
380
 
363
381
  ## Additional Resources
364
382
 
383
+ - [workflows/workflow-fit-and-tuning.md](workflows/workflow-fit-and-tuning.md) — the fit gate
384
+ (workflow vs. descriptive procedure), the per-node cost model, latency and observability tuning,
385
+ the node simplicity budget tied to the frozen ADR-069 measures, and the promote / demote /
386
+ optimize refactor procedures. Read before `add` and before any performance-motivated `refine`.
365
387
  - [workflows/operations.md](workflows/operations.md) — the operation procedures (validate/run/list/add/refine),
366
388
  the shared find-existing-workflow and validate-and-dry-run cores, and the mode-selection gate. The
367
389
  entry point for slash-command delegation.
@@ -36,21 +36,20 @@ answer. Everything below is a consequence of that sentence, not an additional ru
36
36
  **Default: execute the backing skill directly in the current coding-agent session.** Do not invoke
37
37
  `spur agent run` when no escalation trigger applies and the operator did not select subprocess via
38
38
  the `--agent` selector. Omitting `--agent` keeps the default — the backing skill runs in the
39
- current session, and eligible model stages may dispatch once to a native subagent (task 0508). The
40
- explicit value is useful in scripts and audit output but is not the default: explicit `inline`
41
- selects the zero-dispatch carve-out below.
39
+ current session, and eligible model stages may dispatch once to a native subagent (task 0508).
40
+ Explicit `inline` resolves identically (0687 R1: inline is the default selector; R2 generalized
41
+ native-subagent eligibility from omit-only to all inline resolutions).
42
42
 
43
- > **Explicit `--agent inline` is a hard host-session guarantee: all model-bearing work executes in
44
- > the invoking host session never a native subagent, never a subprocess, never a workflow hop.
45
- > The 0508 native-subagent eligibility applies to **omitted** `--agent` only. Headless surfaces
46
- > (`spur agent run`, workflow `agent.run`, serve-side dispatch) reject `inline` with the stable
47
- > special error (exit 2 at the CLI) and take no further action — no dispatch, no `agent.default`
48
- > fallback.**
43
+ > **`--agent inline` (explicit or omitted) is an honest selector, not a dispatch guarantee. On a
44
+ > host session the work runs in-session and eligible model stages may use a native subagent (0508,
45
+ > as generalized by 0687 R2). A headless dispatch surface (`spur agent run`, workflow `agent.run`,
46
+ > serve-side dispatch) cannot host a session; there AgentService substitutes tier resolution and
47
+ > warns once naming the resolved executor (0687 R3) — no rejection, no `exit 2`.**
49
48
 
50
49
  | Value | Who does the work | Derived surface |
51
50
  | --- | --- | --- |
52
51
  | `(omitted)` | The agent running this session | Host session — host-controlled; eligible model stages may use a native subagent (0508) |
53
- | `inline` | The agent running this session | Host session — hard guarantee: zero dispatch, never a subprocess, never a workflow hop; headless surfaces reject `inline` (exit 2, stable special error) |
52
+ | `inline` | The agent running this session | Inline host session — identical to omitted (0687 R1); on headless surfaces tier resolution substitutes with a warning (0687 R3) |
54
53
  | `auto` | The role the caller declared — this command's `role:` frontmatter or the workflow step's `role:` (Layer 1, `plugins/sp/references/roles.md`); with nothing declared, `agent.default`'s role (0542) | Subprocess — a tier-resolved executor pins a specific agent/model, which the host session cannot supply |
55
54
  | `<name>` (coding agent or configured executor) | That executor | Inline when it resolves to the current session's agent; subprocess otherwise |
56
55
 
@@ -58,15 +57,9 @@ The previous `--inline` and `--subprocess` flags (feature H82, task 0413) are co
58
57
  single selector: `--inline` → `--agent inline`, `--subprocess` → `--agent auto`. The old two-flag
59
58
  form is no longer part of the command surface.
60
59
 
61
- This is a prompt-runtime rule owned by the command wrapper and its backing skill, not a branch in
62
- `AgentService`: the current coding agent is already executing the command, so inline means continuing
63
- in that session. Threading an `inline` option through `AiRunner` would still start a subprocess and
64
- would therefore be a false implementation. On a headless surface (`spur agent run` / workflow
65
- `agent.run`) explicit `inline` is **rejected** (ADR-047 G5 amendment) with the stable special error —
66
- headless surfaces cannot host a session: `--agent inline requires a host session: this surface is
67
- headless and never dispatches inline runs (no fallback to agent.default). Use 'auto', a role, or an
68
- executor name.` (exit 2 at the CLI; the exported `AGENT_INLINE_HEADLESS_MESSAGE` in
69
- `agent-service`). No further action is taken — no dispatch, no `agent.default` fallback.
60
+ The explicit-rejection carve-out above was superseded by ADR-087 (task 0687): a headless surface
61
+ substitutes tier resolution with a warning instead of rejecting see the substitution blockquote
62
+ above and `resolveAgent` in `packages/app/src/services/agent-service.ts`.
70
63
 
71
64
  ### Objective triggers override the answer
72
65
 
@@ -81,9 +74,8 @@ cannot satisfy, so it wins regardless:
81
74
  | **Workspace or credential isolation required** | The work must not share the host workspace or credentials. | `trigger 4: workspace or credential isolation required` |
82
75
 
83
76
  A trigger selects subprocess when the selector is omitted, `auto`, or a name, and the applied trigger
84
- must be named in the dispatch or result. Explicit `--agent inline` is the hard host-session carve-out:
85
- a trigger requirement that cannot be satisfied in-session rejects with the stable special error rather
86
- than dispatching a subprocess. When the operator selected a non-current executor and no objective trigger
77
+ must be named in the dispatch or result. Inline selections (omitted or explicit they are now the
78
+ same value, 0687 R1/R2) satisfy trigger requirements in-session where possible. When the operator selected a non-current executor and no objective trigger
87
79
  applies, report `operator override` rather than inventing one of the four. The trigger vocabulary and
88
80
  evidence standard are owned by
89
81
  [dispatch-surface.md](../../parallel-execution/references/dispatch-surface.md). If none can be named
@@ -125,15 +117,15 @@ model stage (task 0508): an eligible `agent.run` stage — pure-slash input, non
125
117
  native subagent with shared-worktree read/write/shell capability — dispatches **once** to that
126
118
  native subagent and joins before the driver continues; any pre-dispatch eligibility failure falls
127
119
  back to one host execution, and a failure after dispatch follows the stage's error policy with no
128
- automatic host replay. Explicit `--agent inline` is the zero-dispatch carve-out: every model stage
129
- executes in the invoking host session — no native-subagent leg. Operator
120
+ automatic host replay. Inline resolution (omitted or explicit, 0687 R1/R2) keeps the native-subagent
121
+ leg for eligible stages. Operator
130
122
  confirmation actions, `pause: true`, and approve/taste/ask decisions stay host-owned. Each inline
131
123
  model stage appends `stage <id> executed inline in session <session-id>` to its run log; a
132
124
  subagent-dispatched stage appends `stage <id> executed via subagent <agent-id> (host session
133
125
  <session-id>)` instead. `dev-plan` remains a workflow subprocess, as do `dev-run`/`dev-runall` with
134
126
  `--agent auto` or a name, parallel batches, and every headless `spur workflow run` / `spur agent
135
127
  run`. `dev-run --mode implement` continues to run its single competency in-session under omitted
136
- `--agent` or explicit `--agent inline` (zero-dispatch).
128
+ `--agent` or explicit `--agent inline` (identical values, 0687 R1).
137
129
 
138
130
  ### Executor precedence chain (R7)
139
131
 
@@ -150,11 +142,11 @@ resolved in this order; first match wins:
150
142
 
151
143
  `--agent auto` tier-resolves an executor (stage `model_policy` → `agent.default` → tier priority)
152
144
  **before** merging, so it enters the chain at step 1 already resolved to a concrete name.
153
- On a headless workflow surface, explicit `--agent inline` is rejected with the stable special error
154
- the surface cannot host a session. Interactive task wrappers consume omitted `--agent` (0508
155
- eligibility) and explicit `inline` (zero-dispatch carve-out) before this chain and use the host
156
- driver. Omitting the flag on a headless surface forwards nothing, so the spawned step resolves to
157
- `agent.default` (step 2) or the YAML literal (step 3).
145
+ On a headless workflow surface, explicit `--agent inline` substitutes tier resolution with a warning
146
+ (0687 R3) instead of rejecting it resolves exactly like an omitted flag. Interactive task wrappers
147
+ consume both inline resolutions identically (0508 eligibility as generalized by 0687 R2) before this
148
+ chain and use the host driver. Omitting the flag on a headless surface forwards nothing, so the
149
+ spawned step resolves to `agent.default` (step 2) or the YAML literal (step 3).
158
150
 
159
151
  ### Implement-only executor override (R6)
160
152
 
@@ -74,7 +74,7 @@ each would be scope creep for one-liner procedures.
74
74
  | 6 | plan | `dev-plan` | `Skill()` | `sp:spur-dev` (`plan`) | `"<description>" [--feature <id>] [--parent <feature-id>] [--agent <inline\|auto\|name>] [--skip-design] [--auto] [--approve-taste]` |
75
75
  | 7 | docs | _(no thin wrapper)_ | `Skill()` | `sp:doc-evolve` | `"<change description>"` |
76
76
  | 8 | changelog | `dev-changelog` | `inline` | git log + conventional-commit grouping | `[--since <ref>] [--until <ref>] [--version <ver>]` |
77
- | 9 | gitmsg | `dev-gitmsg` | `inline` | per-file diff summarygroup → conventional commit | `[--commit] [--squash] [--scope <path>]` |
77
+ | 9 | gitmsg | `dev-gitmsg` | `inline` | bounded diff captureconcern grouping → conventional commit | `[--commit] [--squash] [--all] [--scope <path>]` |
78
78
  | 10 | fixall | `dev-fixall` | `inline` | lint + test fix loop | `[<validation-command>] [--max-retry <n>] [--scope <path>] [--gate-log <path>] [--findings <anchors>]` |
79
79
  | 11 | handover | `dev-handover` | `inline` | structured doc generation | `"<blocker description>"` |
80
80
  | 12 | brainstorm | `dev-brainstorm` | `Skill()` | `sp:brainstorm` (`dev-brainstorm`) | `<topic> [--depth <basic\|detailed\|comprehensive>] [--options <n>] [--agent <inline\|auto\|name>] [--skip-discovery] [--wayfind] [--task [<feature-id>]] [--feature [<parent-id>]] [--next]` |
@@ -140,12 +140,12 @@ must not be changed without updating the backing skill.
140
140
  ### 4. run
141
141
 
142
142
  - **Purpose:** Run a task through the execution pipeline (full) or execute a single pipeline step (implement).
143
- - **Inputs:** `<wbs>` (required). `--mode <full|implement>` selects the execution mode. `implement` invokes `sp:code-implementation` inline by default. Interactive `full` with omitted `--agent` or explicit `--agent inline` reads `task-pipeline.yaml` and drives its actions/guards in the host session — host-controlled and non-subprocess; **omitted** `--agent` keeps 0508 eligibility (eligible `agent.run` stages dispatch once to a native subagent, host fallback), while explicit `--agent inline` is the zero-dispatch carve-out (every stage executes in the invoking session). `--agent auto`, a name, or headless invocation launches the workflow subprocess. `--agent <inline|auto|name>` selects the execution surface (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips the HITL approve gate / confirmations and propagates down the `--next` chain. `--next` controls chaining only and never changes the mode; a pipeline implement stage must invoke `/sp:dev-run <wbs> --mode implement`. On implement success with `--next`, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`. On a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` section must state that explicitly and the verify verdict will record the scope. `--worktree [<name>]` runs the full pipeline inside an isolated git worktree (create or reuse; FF-merge on success, retain on failure) — the batch lifecycle in [execution-batch.md § Worktree isolation](execution-batch.md#worktree-isolation---worktree-name) applied to a batch of one; rejected with `--mode implement`. `--wrap` hands off to `/sp:dev-wrap <wbs>` after the main step; the `--agent` selector is preserved into that handoff when supplied (omission remains omission), and the wrap hop reports its own trigger-3 subprocess override per the wrap contract.
143
+ - **Inputs:** `<wbs>` (required). `--mode <full|implement>` selects the execution mode. `implement` invokes `sp:code-implementation` inline by default. Interactive `full` resolves the selector uniformly (task 0687): omit ≡ explicit `--agent inline`; full mode reads `task-pipeline.yaml` and drives its actions/guards in the host session — host-controlled and non-subprocess, with 0508 eligibility applying to the resolved-inline selector (eligible `agent.run` stages dispatch once to a native subagent, host fallback)). `--agent auto`, a name, or headless invocation launches the workflow subprocess. `--agent <inline|auto|name>` selects the execution surface (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips the HITL approve gate / confirmations and propagates down the `--next` chain. `--next` controls chaining only and never changes the mode; a pipeline implement stage must invoke `/sp:dev-run <wbs> --mode implement`. On implement success with `--next`, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`. On a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` section must state that explicitly and the verify verdict will record the scope. `--worktree [<name>]` runs the full pipeline inside an isolated git worktree (create or reuse; FF-merge on success, retain on failure) — the batch lifecycle in [execution-batch.md § Worktree isolation](execution-batch.md#worktree-isolation---worktree-name) applied to a batch of one; rejected with `--mode implement`. `--wrap` hands off to `/sp:dev-wrap <wbs>` after the main step; the `--agent` selector is preserved into that handoff when supplied (omission remains omission), and the wrap hop reports its own trigger-3 subprocess override per the wrap contract.
144
144
  - **Backing:** `sp:spur-dev` skill — `run` operation for the full pipeline (the spine drives it); `sp:code-implementation` competency skill for the implement step (the spine dispatches to it).
145
145
  - **Modes:**
146
146
  - **`full`** (default): Drive the full pipeline — precheck → implement → test → review → approve(HITL) → verify → record → done. Interactive omit/inline uses [inline-pipeline-driver.md](inline-pipeline-driver.md) (host-controlled; eligible stages may use a native subagent); explicit/headless executor selection invokes `spur workflow run task-pipeline.yaml --vars '{"wbs":"<wbs>"}'` (with `profile: auto` when `--auto`). Both monitor/surface HITL and preserve the YAML gates. `--next` never changes this mode.
147
147
  - **`implement`** (explicit `--mode implement` only): Execute only the implement step. Read the task's `## Requirements` / `## Design` / `## Plan`, write the code that satisfies them, author the `## Solution` change-map section (file:line + what/why per changed file) via `spur task update <wbs> --section Solution --from-file`. This is the implement step the pipeline calls — it is NOT the pipeline driver. With `--next`: on success, transition `todo → wip → testing` through the FSM (guards honored — no `--no-lifecycle`) + chain to `/sp:dev-verify <wbs> --auto --next`; on a guard failure, stop as review-pending. **Partial-deliverable rule:** if the task ships only part of its requirements (e.g. an R1/R2 split with the rest in a follow-up task), the `## Solution` and `## Review` sections MUST carry a `⚠️ PARTIAL` marker naming the deferred part and the follow-up WBS — see `plugins/sp/commands/dev-run.md` → "Section ownership".
148
- - **Delegation:** `Skill(skill="sp:spur-dev", args="run-inline $ARGUMENTS")` for interactive full omit/inline (explicit `inline` = zero-dispatch carve-out); `Skill(skill="sp:spur-dev", args="run $ARGUMENTS")` for explicit/headless full mode; `Skill(skill="sp:code-implementation", args="$ARGUMENTS")` for implement mode.
148
+ - **Delegation:** `Skill(skill="sp:spur-dev", args="run-inline $ARGUMENTS")` for interactive full omit/inline (resolved-inline semantics, task 0687); `Skill(skill="sp:spur-dev", args="run $ARGUMENTS")` for explicit/headless full mode; `Skill(skill="sp:code-implementation", args="$ARGUMENTS")` for implement mode.
149
149
 
150
150
  ### 5. refine
151
151
 
@@ -252,7 +252,7 @@ must not be changed without updating the backing skill.
252
252
  ### 6. plan
253
253
 
254
254
  - **Purpose:** Plan a feature from a description — intake → feature create → AC generation → feature check gate → decomposition → batch-create (with **Design by default**).
255
- - **Inputs:** `"<description>"` (required). `--feature <id>` links to an existing feature. `--parent <feature-id>` nests under a parent. The planning pipeline's `agent.run` stages always dispatch a subprocess; `--agent <inline|auto|name>` selector accepted — explicit `inline` is rejected on this headless surface with the stable special error (see [SSOT](cross-cutting.md#inline-default-execution-surface)). **Design package flags (unified with `/sp:dev-idea`):**
255
+ - **Inputs:** `"<description>"` (required). `--feature <id>` links to an existing feature. `--parent <feature-id>` nests under a parent. The planning pipeline's `agent.run` stages always dispatch a subprocess; `--agent <inline|auto|name>` selector accepted — `inline` substitutes tier resolution with a warning on this headless surface (task 0687) (see [SSOT](cross-cutting.md#inline-default-execution-surface)). **Design package flags (unified with `/sp:dev-idea`):**
256
256
  - **Default:** author task `design` on every batch item + feature satellite when the seam heuristic fires (**ties lean design**). There is **no** `--design` force flag.
257
257
  - `--skip-design` — skip feature satellite **and** omit task `design` fields (scaffold only; refine fills later). Sole design opt-out.
258
258
  - `--approve-taste` — with `--auto`, pre-clear design-approval taste pause when that gate is used (`design_approved=true`). Alias: `--design-approved`.
@@ -300,7 +300,7 @@ must not be changed without updating the backing skill.
300
300
  ### 14. wrap
301
301
 
302
302
  - **Purpose:** Wrap up a single completed task — capture learnings, record metrics, sync docs, and optionally advance the feature / clean up the branch.
303
- - **Inputs:** `<wbs>` (required, positional). `--agent <inline|auto|name>` names the wrap's model-bearing executor (default: `agent.default`); wrap is workflow-backed, so omitted `--agent` resolves to `agent.default` under objective trigger 3 (durable auditable run record required) while explicit `--agent inline` is rejected with the stable special error (headless workflow surface), `auto` tier-resolves an executor, and a name pins that executor into `vars.agent` (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips objective confirmations (the branch-cleanup HITL gate still pauses — irreversible). `--merge` triggers branch cleanup (irreversible HITL gate).
303
+ - **Inputs:** `<wbs>` (required, positional). `--agent <inline|auto|name>` names the wrap's model-bearing executor (default: `inline`, resolving identically when omitted; task 0687); wrap is workflow-backed (headless), so omit explicit `inline` (task 0687): tier substitution under objective trigger 3 (durable auditable run record required) plus one warning naming the substituted executor. `auto` tier-resolves an executor, and a name pins that executor into `vars.agent` (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips objective confirmations (the branch-cleanup HITL gate still pauses — irreversible). `--merge` triggers branch cleanup (irreversible HITL gate).
304
304
  - **Backing:** `spur workflow run wrapup-pipeline.yaml` — direct workflow invocation (no backing skill; the pipeline IS the procedure).
305
305
  - **Behavior:** Resolves the executor (`agent.default` for omit/`inline`, tier-resolved for `auto`, unchanged for a name), emits a pre-dispatch notice naming the subprocess override — `execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: <selector>`, `executor: <resolved>` — then builds `--vars '{"tasks":"[\"<wbs>\"]","agent":"<resolved>","profile":"interactive|auto","merge":"true|false"}'` and invokes the wrapup pipeline. The pipeline runs: task-resolve → doc-sync → learning-capture → metrics-record → (feature-transition) → (branch-cleanup) → done. Task statuses are NOT mutated. Branch cleanup is an irreversible HITL gate that always pauses, even under `--auto`.
306
306
  - **Vars string typing:** `tasks` is a JSON-encoded **string**, not a JSON array — `spur workflow run --vars` accepts only string values (`--vars values must be strings`); the pipeline's guards parse the string with `jq length`. `jq -nc` guarantees the shape:
@@ -316,9 +316,9 @@ must not be changed without updating the backing skill.
316
316
  ### 15. wrapall
317
317
 
318
318
  - **Purpose:** Wrap up a batch of completed tasks — capture learnings, record metrics, sync docs, advance a feature through legal lifecycle edges, and optionally clean up branches.
319
- - **Inputs:** `--since <iso-date>` filters done tasks by frontmatter `updated_at >= date` (v1 approximation). `--feature <id>` selects all tasks under a feature AND advances the feature through legal lifecycle edges (`backlog → active → verifying → done`, guards honored). `--status <s>` (default: `done`) filters by task status. `--agent <inline|auto|name>` names the wrap's model-bearing executor (default: `agent.default`); wrap is workflow-backed, so omitted `--agent` resolves to `agent.default` under objective trigger 3 (durable auditable run record required) while explicit `--agent inline` is rejected with the stable special error (headless workflow surface), `auto` tier-resolves an executor, and a name pins that executor into `vars.agent` (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips objective confirmations. `--merge` triggers branch cleanup (irreversible HITL gate).
319
+ - **Inputs:** `--since <iso-date>` filters done tasks by frontmatter `updated_at >= date` (v1 approximation). `--feature <id>` selects all tasks under a feature AND advances the feature through legal lifecycle edges (`backlog → active → verifying → done`, guards honored). `--status <s>` (default: `done`) filters by task status. `--agent <inline|auto|name>` names the wrap's model-bearing executor (default: `inline`, resolving identically when omitted; task 0687); wrap is workflow-backed (headless), so omit explicit `inline` (task 0687): tier substitution under objective trigger 3 (durable auditable run record required) plus one warning naming the substituted executor. `auto` tier-resolves an executor, and a name pins that executor into `vars.agent` (see [SSOT](cross-cutting.md#inline-default-execution-surface)). `--auto` skips objective confirmations. `--merge` triggers branch cleanup (irreversible HITL gate).
320
320
  - **Backing:** `spur workflow run wrapup-pipeline.yaml` — direct workflow invocation.
321
- - **Behavior:** Resolves the task list via `spur task list --json` (filtered by `--feature`, `--since`, `--status`), resolves the executor (`agent.default` for omitted `--agent`, tier-resolved for `auto`, unchanged for a name; explicit `--agent inline` is rejected with the stable special error), emits a pre-dispatch notice naming the subprocess override — `execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: <selector>`, `executor: <resolved>` — then builds `--vars '{"tasks":"[...]","feature":"<id>","agent":"<resolved>","profile":"interactive|auto","merge":"true|false"}'` and invokes the wrapup pipeline. The pipeline runs the same states as `wrap` but with the full task list and optional feature transition. Task statuses are NOT mutated. Feature transitions go through `spur feature update` so lifecycle guards apply. Branch cleanup is an irreversible HITL gate.
321
+ - **Behavior:** Resolves the task list via `spur task list --json` (filtered by `--feature`, `--since`, `--status`), resolves the executor (omission and explicit `--agent inline` tier-substitute identically with a warning per task 0687, `auto` tier-resolves, a name pins), emits a pre-dispatch notice naming the subprocess override — `execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: <selector>`, `executor: <resolved>` — then builds `--vars '{"tasks":"[...]","feature":"<id>","agent":"<resolved>","profile":"interactive|auto","merge":"true|false"}'` and invokes the wrapup pipeline. The pipeline runs the same states as `wrap` but with the full task list and optional feature transition. Task statuses are NOT mutated. Feature transitions go through `spur feature update` so lifecycle guards apply. Branch cleanup is an irreversible HITL gate.
322
322
  - **Vars string typing:** `tasks` is a JSON-encoded **string**, not a JSON array — `--vars` values must be strings (the CLI rejects raw arrays); `jq -nc` passes the array text through as a string value:
323
323
 
324
324
  ```bash
@@ -378,14 +378,43 @@ is the procedure. The backing is a combination of git CLI, `spur` CLI, and agent
378
378
 
379
379
  ### 9. gitmsg
380
380
 
381
- - **Purpose:** Generate conventional commit message(s) from staged changes via per-file summarization → concern grouping → one message per group.
382
- - **Inputs:** `--scope <path>` (default: all staged changes) — limits diff analysis to a path. `--commit` — execute the commit (off by default; refuses on a multi-group staging unless `--squash`). `--squash` — collapse all concerns into one combined message and let `--commit` proceed on a mixed staging.
383
- - **Backing:** `inline` — per-file diff summary + concern grouping + conventional commit formatting.
381
+ - **Purpose:** Generate conventional commit message(s) for the current change set one bounded diff capture → concern grouping → one message per concern; optionally commit.
382
+ - **Inputs:** `--scope <path>` (default: the whole change set) — path filter; an explicit `--scope` always wins over change-set auto-discovery. `--all` (default: off) widen the change set past the index to every change in the tree: unstaged **and untracked** files. Omitted, the change set is the index only, which keeps pre-commit semantics stable. `--commit` (default: off) — commit the change set, one commit per concern. `--squash` (default: off) — collapse every concern into one message and one commit; implies `--commit`.
383
+ - **Backing:** `inline` — bounded diff capture + concern grouping + conventional commit formatting.
384
384
  - **Behavior:**
385
- 1. Run `git diff --cached --stat` (add `-- <path>` when `--scope` is given) for the outline. If the diff is empty, report "no staged changes" and stop.
386
- 2. Capture the full diff to a temp file (`TEMP_FILE="/tmp/gitdiff_$(date +%s)"; git diff --cached > "$TEMP_FILE" 2>&1`) so analysis reads from disk, not a giant inline blob.
387
- 3. Read `$TEMP_FILE` and write **one sentence per changed file** — what changed and why, not a line count.
388
- 4. **Group the per-file sentences by concern.** For each group derive its commit type, scope, and message:
385
+
386
+ 1. **Gather once a single shell round trip.** No temp file, no second read, no follow-up `git` call for context:
387
+
388
+ ```bash
389
+ # default: RANGE=--cached (the index)
390
+ # --all: RANGE=HEAD (plus untracked, listed below)
391
+ git rev-parse --abbrev-ref HEAD --git-dir --git-common-dir
392
+ git diff $RANGE --stat $PATHSPEC
393
+ git diff $RANGE --name-status $PATHSPEC
394
+ git diff $RANGE -U0 $PATHSPEC ':(exclude)*.lock' ':(exclude)*lock.json' ':(exclude)*.lockb' | head -c 60000
395
+ # --all only:
396
+ git ls-files --others --exclude-standard $PATHSPEC
397
+ ```
398
+
399
+ `$PATHSPEC` is `-- <path>` when `--scope` is given, empty otherwise.
400
+
401
+ **Empty index, no `--all`** — the dead-end this command used to hand back. Reading is
402
+ not a mutation, so the two cases split there:
403
+ - **No committing flag** → re-gather once with `--all` semantics and say so
404
+ (`index empty — read the whole tree instead`). A message-only run has nothing to lose by
405
+ looking wider, and the operator gets an answer instead of an errand.
406
+ - **`--commit` / `--squash`** → stop. Print the worktree counts (`N tracked, M untracked`) and
407
+ the exact re-run line (`/sp:dev-gitmsg <the same flags> --all`). Committing
408
+ untracked files is never inferred from an empty index — that is the operator's call, and it
409
+ is now one paste away rather than a guess.
410
+
411
+ Empty under `--all`, or still empty after the widen → report `no changes in <scope>` and stop.
412
+
413
+ 2. **Stay inside the budget.** `-U0` (no context lines) plus the lockfile exclusions plus the 60 KB cap is the token contract — never re-run the diff with context to "see more", never paste diff hunks into the output. If the cap truncated the diff, say so and derive the message from `--stat` + `--name-status` alone; a large mechanical change rarely needs hunk detail to be typed and scoped correctly.
414
+
415
+ 3. **Summarize only what shapes the message.** One sentence — what changed and _why_, not a line count — for each file whose change is not obvious from its path and status. Skip the obvious ones (generated files, lockfiles, pure renames, `docs/**` under a `docs` group). Past ~12 interesting files, summarize per directory instead of per file.
416
+
417
+ 4. **Group by concern**, and for each group derive type, scope, message:
389
418
  - Type from the dominant change — `feat` (new functionality) · `fix` (bug fix) · `refactor` (restructuring, no behavior change) · `docs` (documentation only) · `chore` (build/config/tooling) · `perf` · `test` · `style`.
390
419
  - Scope from the affected module/package (`cli`, `domain`, `server`, `web`, `app`, …); `--scope` overrides.
391
420
  - Message:
@@ -393,16 +422,29 @@ is the procedure. The backing is a combination of git CLI, `spur` CLI, and agent
393
422
  ```
394
423
  <type>(<scope>): <summary>
395
424
 
396
- <body — optional bullets from the group's per-file sentences>
425
+ <body — why, only when the why is not obvious>
397
426
  ```
398
427
 
399
- Summary: imperative mood, ≤72 chars, lowercase first word, no period. Body: only when the change is non-obvious.
428
+ Summary: imperative mood, ≤72 chars, lowercase first word, no period. Body explains intent and consequence; it never restates the diff. Behavior changes and pure formatting/refactor churn are **different concerns** — never one group.
429
+
430
+ 5. **Resolve the message shape without a round trip to the operator.** `--squash` → one combined
431
+ message (dominant type/scope, one body bullet per group). Otherwise one message per group, in
432
+ dependency order (refactor before the feature that uses it); a single group is that same rule
433
+ with one group.
434
+
435
+ 6. **Report, then commit if asked.** Print the resolved message(s), a copy-paste `git commit` line per message, and a one-line context header: current branch, and `linked worktree` when `--git-dir` differs from `--git-common-dir` and `git rev-parse --show-superproject-working-tree` is empty (a non-empty result means submodule, not worktree). With neither `--commit` nor `--squash`, stop here — the operator commits.
436
+
437
+ 7. **Committing (`--commit` / `--squash`).** The two flags name outcomes, not dimensions: `--commit`
438
+ commits **by concern**, `--squash` commits **everything as one**. `--squash` implies `--commit`,
439
+ so `--commit --squash` is just `--squash`, never an error.
400
440
 
401
- 5. **Resolve groups:** one group emit its message; multiple groups (default) emit one message per group **plus a split recommendation** (stage per concern, re-run); `--squash` collapse to one combined message (dominant type/scope, per-file bullets).
402
- 6. Print the resolved message + a copy-paste `git commit -m` line. With `--commit`: execute it for a single group or under `--squash`; on a multi-group staging without `--squash`, **do not commit** — print the split guidance instead (one `git commit` can't honor per-group messages).
403
- 7. `rm "$TEMP_FILE"` once done no `/tmp` diff residue (the F5 cleanup discipline).
441
+ First scan the captured diff's added lines for credentials (`password`, `secret`, `api[_-]?key`, `token`, `BEGIN [A-Z ]*PRIVATE KEY`, long base64-looking literals). On a hit: print the offending `file:line`, commit nothing, stop. Otherwise:
442
+ - `--all` `git add -A $PATHSPEC` first (this is what stages the untracked files).
443
+ - `--squash`, or `--commit` on a single group `git commit -m "$MESSAGE"`.
444
+ - `--commit` on multiple groups → **commit each group in sequence**: record the full staged file list once, then per group `git reset -q -- <all staged paths>` → `git add -- <that group's paths>` → `git commit -m "<that group's message>"`. This is the split; it needs no re-run and no re-staging by the operator.
445
+ - **Partial-staging guard:** if any path appears in _both_ `git diff --cached --name-only` and `git diff --name-only` (a `git add -p` staging), the sequence above would silently widen those commits to the whole file. Do not split — report the affected paths and commit nothing unless `--squash` was given.
404
446
 
405
- - **Invariants:** Without `--commit`, never runs `git commit` — message only, the operator commits. With `--commit`, only commits when the staging is a single concern OR `--squash` was given a mixed staging without `--squash` is reported, never silently squashed. Never leave the temp diff file behind.
447
+ - **Invariants:** With neither `--commit` nor `--squash`, never runs `git commit` — message only. Neither flag commits across a credential hit, and `--commit` never splits across a partial staging. Without `--all` the change set is the index, so plain `/sp:dev-gitmsg` keeps pre-commit semantics; only `--all` reaches unstaged and untracked files, and only a committing run (`--commit` / `--squash`) stages them. The empty-index widen is read-only and always announced — a committing run never widens its own scope. An explicit `--scope <path>` always bounds the change set, with or without `--all`. One diff capture per run, bounded and context-free — never a second, wider read.
406
448
 
407
449
  ### 10. fixall
408
450
 
@@ -147,7 +147,7 @@ On the subprocess path, when `--agent <value>` is set (passed through from the t
147
147
  `--vars '{"wbs":"<wbs>","agent":"<value>","implementAgent":"<value>"}'`. The pipeline YAML reads
148
148
  `${vars.agent}` for review/verify/test-fix and `${vars.implementAgent}` for implement — setting
149
149
  both keys ensures the pinned executor reaches every hop. The full value-semantics contract (one
150
- rule, value table, objective triggers, and headless `inline` rejection (stable special error) per the ADR-047 G5 amendment)
150
+ rule, value table, objective triggers, and headless `inline` substitution (tier resolution + one warning) per ADR-087)
151
151
  lives in [cross-cutting.md](cross-cutting.md#inline-default-execution-surface) — the SSOT.
152
152
  This file documents only the **workflow-pipeline mechanics**: how the selector reaches `agent.run`
153
153
  steps. Precedence chain: `--agent` / explicit `--vars` → `agent.default` → YAML literal (see SSOT