@gobing-ai/spur 0.3.48 → 0.3.49

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 (29) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +52 -4
  3. package/config/workflows/pr-review.yaml +338 -0
  4. package/package.json +8 -8
  5. package/plugins/sp/README.md +9 -6
  6. package/plugins/sp/commands/{dev-featurechange.md → dev-feature-change.md} +7 -10
  7. package/plugins/sp/commands/dev-find-issue.md +24 -19
  8. package/plugins/sp/commands/dev-find-next.md +3 -3
  9. package/plugins/sp/commands/dev-gtd.md +11 -12
  10. package/plugins/sp/commands/dev-history-load.md +63 -0
  11. package/plugins/sp/commands/dev-pr-review.md +39 -0
  12. package/plugins/sp/plugin.json +1 -1
  13. package/plugins/sp/references/roles.md +25 -12
  14. package/plugins/sp/scripts/history-load.ts +400 -0
  15. package/plugins/sp/scripts/pr-reviewing.ts +867 -0
  16. package/plugins/sp/scripts/validate-commands.ts +33 -2
  17. package/plugins/sp/skills/code-implementation/SKILL.md +9 -1
  18. package/plugins/sp/skills/code-verification/SKILL.md +27 -28
  19. package/plugins/sp/skills/issue-finding/SKILL.md +6 -5
  20. package/plugins/sp/skills/issue-finding/references/session-formats.md +4 -2
  21. package/plugins/sp/skills/next-feature/SKILL.md +6 -6
  22. package/plugins/sp/skills/next-feature/references/handoff-routing.md +5 -5
  23. package/plugins/sp/skills/next-feature/references/signal-derivation.md +7 -2
  24. package/plugins/sp/skills/pr-reviewing/SKILL.md +285 -0
  25. package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +5 -5
  26. package/plugins/sp/skills/spur-cli/references/features.md +1 -1
  27. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +14 -4
  28. package/schemas/spur-config.schema.json +20 -0
  29. package/spur.js +682 -222
@@ -8,7 +8,8 @@
8
8
  *
9
9
  * Gates:
10
10
  * (a) heading whitelist — H1 title + the per-contract ordered section headings
11
- * (b) frontmatter schema — description, argument-hint, allowed-tools; dev-only extras
11
+ * (b) frontmatter schema — description, argument-hint, allowed-tools; dev-only extras;
12
+ * real-YAML re-parse so malformed blocks cannot ship an empty description
12
13
  * (c) target resolution — sp:<skill> refs, workflow files, procedure anchors
13
14
  * (d) allowed-tools coherence — Skill present iff body contains Skill() call
14
15
  * (e) dev-command argument contract — syntax-only hint, Argument Flags table columns,
@@ -19,6 +20,7 @@
19
20
 
20
21
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
21
22
  import { join, resolve } from 'node:path';
23
+ import { parse as parseYaml } from 'yaml';
22
24
 
23
25
  // ─── Types ──────────────────────────────────────────────────────────────────
24
26
 
@@ -53,6 +55,8 @@ interface ParsedCommand {
53
55
  readonly description: string | undefined;
54
56
  readonly argumentHint: string | undefined;
55
57
  readonly allowedTools: string[] | undefined;
58
+ /** Set when the frontmatter fails a real YAML parse or yields an empty description. */
59
+ readonly frontmatterYamlProblem: string | undefined;
56
60
  readonly body: string;
57
61
  }
58
62
 
@@ -68,6 +72,30 @@ function parseCommand(filePath: string, name: string): ParsedCommand {
68
72
  const argumentHint = extractYamlField(fm, 'argument-hint');
69
73
  const allowedTools = extractYamlList(fm, 'allowed-tools');
70
74
 
75
+ // The regex extractions above feed the per-field gates, but they cannot see
76
+ // malformed YAML: `description: >-` followed by unindented keys reads as a
77
+ // present description to the regex while a real YAML parser folds the next
78
+ // keys into the block scalar — the bug that shipped an empty description to
79
+ // superskill install for dev-feature-change. Gate (b) re-checks with a real
80
+ // parse so that class cannot regress.
81
+ let frontmatterYamlProblem: string | undefined;
82
+ if (fm.trim() !== '') {
83
+ try {
84
+ const parsed: unknown = parseYaml(fm);
85
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
86
+ frontmatterYamlProblem = 'frontmatter is not a YAML mapping';
87
+ } else {
88
+ const parsedDescription = (parsed as Record<string, unknown>).description;
89
+ if (typeof parsedDescription !== 'string' || parsedDescription.trim() === '') {
90
+ frontmatterYamlProblem = 'frontmatter description is empty after YAML parsing';
91
+ }
92
+ }
93
+ } catch (error) {
94
+ const detail = error instanceof Error ? error.message.split('\n')[0] : String(error);
95
+ frontmatterYamlProblem = `frontmatter is not valid YAML: ${detail}`;
96
+ }
97
+ }
98
+
71
99
  const lines = body.split('\n');
72
100
  const title = lines[0]?.startsWith('# ') ? lines[0].slice(2).trim() : '';
73
101
 
@@ -90,7 +118,7 @@ function parseCommand(filePath: string, name: string): ParsedCommand {
90
118
  }
91
119
  }
92
120
 
93
- return { name, title, headings, description, argumentHint, allowedTools, body };
121
+ return { name, title, headings, description, argumentHint, allowedTools, frontmatterYamlProblem, body };
94
122
  }
95
123
 
96
124
  /** Extract a plain YAML string field (handles quoted and unquoted). */
@@ -212,6 +240,9 @@ function checkHeadingWhitelist(cmd: ParsedCommand): readonly Violation[] {
212
240
 
213
241
  function checkFrontmatterSchema(cmd: ParsedCommand): readonly Violation[] {
214
242
  const violations: Violation[] = [];
243
+ if (cmd.frontmatterYamlProblem) {
244
+ violations.push({ command: cmd.name, gate: 'b', message: cmd.frontmatterYamlProblem });
245
+ }
215
246
  if (!cmd.description) {
216
247
  violations.push({ command: cmd.name, gate: 'b', message: 'missing frontmatter description' });
217
248
  }
@@ -89,6 +89,14 @@ only burns wall clock and context budget.
89
89
  `bun test <file> --test-name-pattern "<test>"`, or `bunx tsc --noEmit` on a single package.
90
90
  - **NEVER run** `bun run test`, `bun run spur-check`, `bun run check`, or any other full-suite /
91
91
  project-gate command from inside implement. These belong to the pipeline's `test` hop.
92
+ - **Full-suite budget: at most 2 per task** (task 0436 R2) — counted across the whole task run
93
+ (implement probes + the pipeline `test` hop + verify/recheck), not per step. When a check fails,
94
+ run the narrow target (`bun test <file> --test-name-pattern <test>`) to green before any full
95
+ suite; reach for the second full run only when the narrow target cannot reproduce the failure.
96
+ - **Consolidate dogfood runs**: one combined real-data execution that exercises all scenarios,
97
+ not N near-identical `--dry-run`/real invocations of the same script. If you find yourself
98
+ rerunning the same dogfood command with one flag changed, stop and fold the variants into a
99
+ single run — repeated identical commands are loop-detector findings and pure cost.
92
100
  - If a targeted probe reveals a failure you cannot fix within implement scope, note it in
93
101
  `## Solution` and let the `test` hop's fixall handle it — do not pre-empt the gate.
94
102
 
@@ -146,7 +154,7 @@ reproduce → isolate → minimal fix → regression guard.
146
154
  ## Common Rationalizations
147
155
 
148
156
  | Rationalization | Reality |
149
- |---|---|
157
+ | --- | --- |
150
158
  | "The spec is clear — I don't need to read the callers." | Code that looks orthogonal is how regressions ship (R5). Read the exports you touch and their immediate callers before writing. |
151
159
  | "I'll add the tests in a follow-up." | Untested production code is unverified code. The task's test step is not optional; behavior ships with its test. |
152
160
  | "This abstraction will be useful later." | Speculative abstraction is complexity without a caller (R2). Build for the requirement in front of you; add the seam when the second use arrives. |
@@ -239,6 +239,11 @@ printf '...' > /tmp/<wbs>-testing.md
239
239
  spur task update <wbs> --section Testing --from-file /tmp/<wbs>-testing.md
240
240
  ```
241
241
 
242
+ > **Corrections: the answer file is the source of truth.** `spur task record` re-transcribes
243
+ > `## Testing` from the verdict artifact, overwriting `--section Testing` writes — direct section
244
+ > fixes are futile. Fix `.spur/run/<wbs>-verify-answer.txt` → `spur task verdict <wbs>
245
+ > --from-answer <file>` → re-record. `--section` is initial authorship only.
246
+
242
247
  > **Do not write `## Review` directly in verify mode.** The `## Review` section is owned by the
243
248
  > `review` step (`/sp:dev-review`), which dispatches `functional-review` + `code-verification`
244
249
  > review mode + `code-improvement`. The `record` step backfills `## Review` from the verdict
@@ -283,9 +288,7 @@ Verdict: PASS
283
288
  | P4 | — | — | No P1–P3 findings; verify verdict PASS |
284
289
  ```
285
290
 
286
- The per-requirement traceability table MUST use `| Req | Status | Evidence |` (exactly this header, no `R#`/`R`/`Requirement` variant, and no extra columns between Req and Status). The Acceptance Criteria table MUST use `| AC | Status | Evidence Type | Evidence |`.
287
-
288
- **MUST NOT:** use `| R# | ... |` as the sole id header without `Status` in column 2.
291
+ The per-requirement traceability table MUST use `| Req | Status | Evidence |` (exactly this header, no `R#`/`R`/`Requirement` variant — `Status` in column 2 — and no extra columns between Req and Status). The Acceptance Criteria table MUST use `| AC | Status | Evidence Type | Evidence |`.
289
292
  **MUST NOT:** place a `Severity` column between `Req` and `Status` in the authoring contract.
290
293
  The parser is tolerant of these variants (defense-in-depth), but the authoring contract is
291
294
  canonical.
@@ -348,8 +351,7 @@ mutation is discoverable from the tracked task file alone, without diffing untra
348
351
  ### Step 13 — Shippable readiness gate (feature-level)
349
352
 
350
353
  Per-task PASS is **not** the same as “this feature is ready to ship.” After Steps 11–12, when the
351
- gate is **active**, evaluate feature AC satisfaction via the existing CLI (do not invent a second
352
- framework).
354
+ gate is **active**, evaluate feature AC satisfaction via the existing CLI.
353
355
 
354
356
  **When active**
355
357
 
@@ -371,16 +373,17 @@ framework).
371
373
  `docs/.spur/run` or other nested `.spur` trees). Ephemeral scratch may use `/tmp` or
372
374
  `/private/tmp`. Requirement / AC row `id`s in the verdict MUST match feature scenario titles
373
375
  (or `AC-N` aliases) so satisfaction can mark MET.
374
- 2. Run:
376
+ 2. Run (0568 R6 monorepo-safe: SPUR_BIN env > local CLI > PATH):
375
377
 
376
378
  ```bash
377
- spur feature check <featureId> --json
378
- spur task list --feature <featureId> --json
379
+ SPUR_BIN="${SPUR_BIN:-$([ -f apps/cli/src/index.ts ] && echo 'bun apps/cli/src/index.ts' || echo spur)}"
380
+ $SPUR_BIN feature check <featureId> --json
381
+ $SPUR_BIN task list --feature <featureId> --json
379
382
  ```
380
383
 
381
384
  3. Classify **Shippable: PASS** only if **all** of:
382
385
  - No finding whose code/message indicates **linked but unverified** scenarios
383
- (`L4_SCENARIO_UNVERIFIED` / “linked but unverified”).
386
+ (`L4_SCENARIO_UNVERIFIED`).
384
387
  - No **orphan / uncovered** feature scenarios (`L4_ORPHAN_SCENARIOS`,
385
388
  `L4_UNCOVERED_FEATURE_SCENARIO` / no covering task).
386
389
  - No **incomplete** linked tasks: every task with this `feature_id` is `done` or `cancelled`
@@ -424,16 +427,14 @@ Full flag matrix and ops notes: [spur-dev/references/dev-operations.md](../spur-
424
427
 
425
428
  ### Step 14 — Report
426
429
 
427
- Show the per-task verdict, the per-requirement table, the gate outcome (cleared / blocked), and the
428
- **Shippable:** line from Step 13 when applicable. Under the pipeline the task verdict is consumed by
429
- the done-gate; for a direct `/sp:dev-verify` invocation the full report is the operator's summary.
430
+ Show the per-task verdict, per-requirement table, gate outcome (cleared/blocked), and the
431
+ **Shippable:** line from Step 13 when applicable. Under the pipeline the done-gate consumes the
432
+ verdict; for a direct `/sp:dev-verify` invocation the full report is the operator's summary.
430
433
 
431
- **`--next` on an already-terminal task (no-op surfacing).** When `--next` is invoked on a task
432
- already at `done` or `cancelled`, the transition cannot fire. The verify report line MUST state the
433
- no-op itself (e.g. `--next: no-op - task already terminal (<status>)`) rather than relying solely
434
- on the CLI print (documented in `dev-verify.md`'s `--next` chain section). The CLI print is the
435
- machine signal; the report line is the operator-visible summary - both must agree so a terminal-task
436
- re-audit cannot be misread as a successful `testing -> done` transition.
434
+ **`--next` on a terminal task (no-op surfacing).** The transition cannot fire; the verify report
435
+ line MUST state the no-op itself (e.g. `--next: no-op - task already terminal (<status>)`). The CLI
436
+ print is the machine signal, the report line the operator summary both must agree so a terminal
437
+ re-audit is never misread as a successful `testing -> done` (dev-verify.md `--next` chain).
437
438
 
438
439
  ---
439
440
 
@@ -513,14 +514,12 @@ Do **not** use this skill for:
513
514
  - **`sp:spur-dev`** — the execution-half umbrella that drives the pipeline this skill gates.
514
515
  - [references/code-improvement.md](references/code-improvement.md) — architecture-improvement lens
515
516
  for module depth, seam placement, locality, coupling, and testability.
516
- - **`sp:functional-review`** — a peer review skill for requirements traceability (R{n} → file:line
517
- evidence, per-requirement MET/PARTIAL/UNMET, `FunctionalVerdict`). When the `review` dimension
518
- needs functional traceability (not just SECUA), dispatch this skill; see
519
- [../functional-review/SKILL.md](../functional-review/SKILL.md).
520
- - **`sp:code-improvement`** — a peer review skill for architectural deepening (5 signals: shallow
521
- module, tight coupling, wrong seam, weak locality, poor test surface; severity
522
- blocker/major/minor/advisory). When review findings expose structural friction rather than a
523
- localized defect, dispatch this skill; see [../code-improvement/SKILL.md](../code-improvement/SKILL.md).
517
+ - **`sp:functional-review`** — requirements-traceability peer review (R{n} → file:line,
518
+ per-requirement MET/PARTIAL/UNMET). Dispatch when the `review` dimension needs functional
519
+ traceability, not just SECUA: [../functional-review/SKILL.md](../functional-review/SKILL.md).
520
+ - **`sp:code-improvement`** — architectural-deepening peer review (5 signals, severity
521
+ blocker/major/minor/advisory). Dispatch for structural friction rather than localized
522
+ defects: [../code-improvement/SKILL.md](../code-improvement/SKILL.md).
524
523
 
525
524
  ---
526
525
 
@@ -533,5 +532,5 @@ directly: `Skill(skill="sp:code-verification", args="verify <wbs> --fix all")`.
533
532
 
534
533
  ### Codex / OpenClaw / OpenCode / Antigravity
535
534
 
536
- Run `spur` CLI via Bash; parse `--json`. Invoke this skill directly for the verification logic the
537
- skill is the SSOT; the commands are thin wrappers.
535
+ Run `spur` CLI via Bash; parse `--json`. Invoke the skill directly the skill is the SSOT; the
536
+ commands are thin wrappers.
@@ -139,11 +139,12 @@ sessions (typed ETL via `spur history` — or raw JSONL under the three fallback
139
139
  **Primary path (typed sources):** `spur history report --mode forensics` (task 0555).
140
140
 
141
141
  ```bash
142
- spur history import --source <source> --json # checkpoint resume makes re-runs cheap
143
- spur history analyze --json # writes the versioned artifact; derived
144
- # variables are computed here (task 0554)
145
- spur history report --mode forensics # pure renderer of the artifact; never opens
146
- # the database; defaults to the latest pointer
142
+ # 0568 R4: SPUR_BIN env > local CLI > PATH stale PATH spur fails history import.
143
+ SPUR_BIN="${SPUR_BIN:-$([ -f apps/cli/src/index.ts ] && echo 'bun apps/cli/src/index.ts' || echo spur)}"
144
+
145
+ $SPUR_BIN history import --source <source> --json # checkpoint resume
146
+ $SPUR_BIN history analyze --json # writes versioned artifact (0554)
147
+ $SPUR_BIN history report --mode forensics # pure renderer; latest artifact pointer
147
148
  ```
148
149
 
149
150
  The forensics renderer emits **8 CLI-derivable sections**: Session Data Summary, Tool Breakdown,
@@ -84,8 +84,10 @@ each file, the session key is the JSONL filename stem (importer `sessionIdFromCo
84
84
  analyze per key:
85
85
 
86
86
  ```bash
87
- spur history import --source omp --file <absolute-file> --mode force-file --json
88
- spur history analyze --session <filename-stem> --json
87
+ # Monorepo-safe spur resolution (0568 R6): SPUR_BIN env > monorepo-local CLI > PATH.
88
+ SPUR_BIN="${SPUR_BIN:-$([ -f apps/cli/src/index.ts ] && echo 'bun apps/cli/src/index.ts' || echo spur)}"
89
+ $SPUR_BIN history import --source omp --file <absolute-file> --mode force-file --json
90
+ $SPUR_BIN history analyze --session <filename-stem> --json
89
91
  ```
90
92
 
91
93
  ETL owns token/cost/message/tool/loop/assistant-duration aggregates; raw JSONL stays authoritative
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: next-feature
3
- description: "Prompt-first feature frontier prioritizer — answers 'which feature should we work on now?' by deriving importance/urgency from corpus, git, and authority-doc evidence, and emits rank-distorting tree defects as proposals /sp:dev-featurechange consumes. Triggers: find next, which feature, feature ranking, frontier priority, what should I work on."
3
+ description: "Prompt-first feature frontier prioritizer — answers 'which feature should we work on now?' by deriving importance/urgency from corpus, git, and authority-doc evidence, and emits rank-distorting tree defects as proposals /sp:dev-feature-change consumes. Triggers: find next, which feature, feature ranking, frontier priority, what should I work on."
4
4
  license: Apache-2.0
5
5
  version: 1.0.0
6
6
  metadata:
@@ -30,7 +30,7 @@ A prompt-first prioritizer that answers **"which feature should we work on now?"
30
30
  `sp:next-router` deliberately does not answer (`routing-table.md` §0 step 1c: target omitted → not
31
31
  v1). It derives importance and urgency from evidence already in the corpus, ranks the actionable
32
32
  frontier in **tiers with per-candidate evidence**, and emits rank-distorting tree defects as
33
- **proposals** `/sp:dev-featurechange` consumes.
33
+ **proposals** `/sp:dev-feature-change` consumes.
34
34
 
35
35
  **Honesty contract:** prompt-first. The model applies the rubric; existing deterministic tools
36
36
  (`spur feature|task … --json`, `git`, `rg`) gather facts. No TypeScript analyzer, no numeric scores,
@@ -39,7 +39,7 @@ in this corpus it is 76% one value (0493 measurement).
39
39
 
40
40
  **Propose, never apply.** This skill performs no `spur feature move` and writes nothing under
41
41
  `docs/features/**`. The only path from a structure proposal to a changed tree is
42
- `/sp:dev-featurechange` (dry-run → confirm → apply). Ranking runs are read-only; the sole exception is
42
+ `/sp:dev-feature-change` (dry-run → confirm → apply). Ranking runs are read-only; the sole exception is
43
43
  `--task`, which after an **operator confirm** (interactive, or auto-accepted under `--auto`)
44
44
  dispatches `/sp:dev-plan` and `/sp:dev-refineall` — commands that write `docs/tasks*/` through their
45
45
  own gates. This skill still creates no tasks itself.
@@ -52,7 +52,7 @@ own gates. This skill still creates no tasks itself.
52
52
  **Do NOT use for:**
53
53
 
54
54
  - Advancing an already-chosen task or feature — that is `/sp:dev-next` (`sp:next-router`).
55
- - Applying tree changes — that is `/sp:dev-featurechange` (feature F31).
55
+ - Applying tree changes — that is `/sp:dev-feature-change` (feature F31).
56
56
  - Task-level ordering inside a feature — next-router's TABLE A owns that.
57
57
 
58
58
  ## Protocol
@@ -106,7 +106,7 @@ Run the steps in order. Each step's depth lives in its reference; this file is t
106
106
  - Ranking a feature whose actionability gate fails. Gate first, rank second.
107
107
  - Emitting a numeric score (WSJF/RICE arithmetic) from absent value/effort estimates.
108
108
  - Copying the B3 predicate into this skill. Cite it; read it at runtime.
109
- - Any `spur feature move`, or writing proposals anywhere `docs/features/**` — featurechange owns apply.
109
+ - Any `spur feature move`, or writing proposals anywhere `docs/features/**` — feature-change owns apply.
110
110
  - Decomposing a feature here, or calling `spur task create` / `spur task batch-create` under `--task`.
111
111
  Dispatch `/sp:dev-plan`; it owns decomposition and the batch-create schema gate. Equally:
112
112
  dispatching under `--auto` without `--task` (there is no confirm to skip), auto-accepting a target
@@ -123,7 +123,7 @@ Run the steps in order. Each step's depth lives in its reference; this file is t
123
123
  | [references/signal-derivation.md](references/signal-derivation.md) | Sync precondition, B3 runtime citation, per-signal derivation commands, degenerate-spread rejection |
124
124
  | [references/ranking-rubric.md](references/ranking-rubric.md) | Tier definitions, tie-breaks, evidence-per-candidate output contract |
125
125
  | [references/proposal-contract.md](references/proposal-contract.md) | D1–D4 defect set, evidence bar, mapping-schema conformance, silence |
126
- | [references/handoff-routing.md](references/handoff-routing.md) | featurechange handoff, next-router seam, the `--task` tier→hop routing table and its confirm contract |
126
+ | [references/handoff-routing.md](references/handoff-routing.md) | feature-change handoff, next-router seam, the `--task` tier→hop routing table and its confirm contract |
127
127
 
128
128
  Grounding: tickets 0493 (measured signals), 0494 (reuse ledger), 0495 (defect contract) under
129
129
  feature H12.
@@ -1,4 +1,4 @@
1
- # Handoff routing — featurechange handoff, next-router seam, conditional dispatch
1
+ # Handoff routing — feature-change handoff, next-router seam, conditional dispatch
2
2
 
3
3
  ## The seam with `/sp:dev-next` (next-router)
4
4
 
@@ -8,7 +8,7 @@
8
8
  - When the operator picks a winner from the ranked report, the printed handoff line is:
9
9
  `/sp:dev-next <feature-id>`.
10
10
 
11
- ## The handoff to `/sp:dev-featurechange` (feature F31)
11
+ ## The handoff to `/sp:dev-feature-change` (feature F31)
12
12
 
13
13
  Defect proposals follow the 0495 Artifact C boundary, traced end to end:
14
14
 
@@ -16,9 +16,9 @@ Defect proposals follow the 0495 Artifact C boundary, traced end to end:
16
16
  | --- | --- | --- |
17
17
  | 1. Detect + emit proposal rows | this skill | No |
18
18
  | 2. Handoff | printed inline in the report (report reading of OQ1) | No |
19
- | 3. `--dry-run` | `/sp:dev-featurechange` | No |
19
+ | 3. `--dry-run` | `/sp:dev-feature-change` | No |
20
20
  | 4. Confirm | operator | — |
21
- | 5. `--apply` (`spur feature move`) | `/sp:dev-featurechange` only | **Yes — sole writer** |
21
+ | 5. `--apply` (`spur feature move`) | `/sp:dev-feature-change` only | **Yes — sole writer** |
22
22
 
23
23
  There is no path from this skill to a mutated tree that bypasses step 4. Proposal rows are printed
24
24
  inline in the default report; writing them into `docs/plans/feature-tree-restructure-map.md` as new
@@ -83,7 +83,7 @@ inside dispatched children remain governed by their own contracts (`--approve-ta
83
83
  ### What `--task` does not change
84
84
 
85
85
  The defect half is untouched: still no `spur feature move`, still nothing written under
86
- `docs/features/**`, still `/sp:dev-featurechange` as the sole applier of structure proposals. `--task`
86
+ `docs/features/**`, still `/sp:dev-feature-change` as the sole applier of structure proposals. `--task`
87
87
  adds one gated path to `docs/tasks*/`, through commands that own their own gates.
88
88
 
89
89
  ## Where outputs go
@@ -7,7 +7,10 @@ cannot be derived is reported as **unavailable**, never fabricated.
7
7
  ## §0 — Sync-first precondition (step zero)
8
8
 
9
9
  ```bash
10
- spur feature sync --all --dry-run --json
10
+ # Monorepo-safe spur resolution (0568 R6): SPUR_BIN env > monorepo-local CLI > PATH.
11
+ # Defined once here; §1+ reuse $SPUR_BIN — §0 runs first by construction.
12
+ SPUR_BIN="${SPUR_BIN:-$([ -f apps/cli/src/index.ts ] && echo 'bun apps/cli/src/index.ts' || echo spur)}"
13
+ $SPUR_BIN feature sync --all --dry-run --json
11
14
  ```
12
15
 
13
16
  Feature `status` is manual bookkeeping and drifts (0493: 24 of 25 rankable features would change
@@ -34,7 +37,7 @@ renamed, the fallback key is its content: "frontier = open ∧ unblocked".)
34
37
  Inputs per candidate feature:
35
38
 
36
39
  ```bash
37
- spur task list --feature <id> --json
40
+ $SPUR_BIN task list --feature <id> --json
38
41
  ```
39
42
 
40
43
  `task list --feature` is **active-folder-only**: it enumerates tasks in the active task folder
@@ -52,9 +55,11 @@ is not authoritative — a frontier task may be archived outside it. Run the fal
52
55
  the feature's row as an **anomaly hint** only: it may flag the feature without naming a WBS. The
53
56
  sync reason is never treated as a WBS source — no WBS is ever inferred from sync prose.
54
57
  2. Scan the whole corpus for linked tasks:
58
+
55
59
  ```bash
56
60
  rg -l '^feature_id: "?<id>"?$' docs/tasks*/
57
61
  ```
62
+
58
63
  Corpus ids are `[A-Z][0-9]+`-shaped, so `<id>` is regex-safe as-is; escape metacharacters if a
59
64
  non-conforming id ever appears.
60
65
  3. Parse the leading WBS from each matched basename; resolve every corpus-only WBS (not present in