@gobing-ai/spur 0.4.2 → 0.4.3

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 (133) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/decisions/maker-liveness.yaml +39 -0
  3. package/config/plugin-scripts.json +0 -4
  4. package/config/rules/README.md +1 -0
  5. package/config/rules/docs/sourced-api-claims.yaml +56 -12
  6. package/config/rules/strict/runtime-boundaries.yaml +7 -0
  7. package/config/script-placement-baseline.json +1 -19
  8. package/config/workflows/feature-lifecycle.yaml +46 -1
  9. package/config/workflows/feature-verification.yaml +11 -0
  10. package/package.json +9 -9
  11. package/plugins/sp/README.md +22 -16
  12. package/plugins/sp/agents/super-reviewer.md +43 -0
  13. package/plugins/sp/bunfig.toml +8 -5
  14. package/plugins/sp/hooks/careful-guard.ts +1 -1
  15. package/plugins/sp/hooks/context-post-tool.ts +7 -427
  16. package/plugins/sp/hooks/context-session-start.ts +5 -213
  17. package/plugins/sp/hooks/context-session-stop.ts +5 -191
  18. package/plugins/sp/hooks/pi/guard-extension.ts +6 -5
  19. package/plugins/sp/hooks/task-write-guard.ts +1 -1
  20. package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
  21. package/plugins/sp/lib/context-post-tool.ts +432 -0
  22. package/plugins/sp/lib/context-session-start.ts +214 -0
  23. package/plugins/sp/lib/context-session-stop.ts +196 -0
  24. package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
  25. package/plugins/sp/lib/dogfood-report.ts +278 -0
  26. package/plugins/sp/lib/idea-handoff.generated.mjs +3 -3
  27. package/plugins/sp/lib/inline-run-app.ts +40 -0
  28. package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
  29. package/plugins/sp/lib/inline-run.generated.mjs +1347 -90
  30. package/plugins/sp/lib/plan-projection.generated.d.mts +150 -0
  31. package/plugins/sp/lib/plan-projection.generated.mjs +591 -1
  32. package/plugins/sp/lib/quality-gate.generated.mjs +38 -25
  33. package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
  34. package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
  35. package/plugins/sp/lib/run-summary-core.ts +34 -370
  36. package/plugins/sp/lib/transcript.ts +37 -228
  37. package/plugins/sp/plugin.json +1 -1
  38. package/plugins/sp/scripts/batch-plan.mjs +586 -0
  39. package/plugins/sp/scripts/batch-preflight.mjs +23 -1
  40. package/plugins/sp/scripts/batch-preflight.ts +35 -295
  41. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
  42. package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
  43. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
  44. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
  45. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
  46. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
  47. package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
  48. package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
  49. package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
  50. package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
  51. package/plugins/sp/scripts/inline-run-setup.mjs +149 -122
  52. package/plugins/sp/scripts/inline-run-setup.ts +1 -21
  53. package/plugins/sp/scripts/quality-gate.mjs +14 -2
  54. package/plugins/sp/scripts/residual-scan.mjs +73 -6
  55. package/plugins/sp/scripts/residual-scan.ts +8 -5
  56. package/plugins/sp/scripts/run-summary.mjs +151 -97
  57. package/plugins/sp/scripts/run-summary.ts +7 -5
  58. package/plugins/sp/scripts/session-timeline.mjs +46 -19
  59. package/plugins/sp/scripts/task-diffstat.mjs +11 -1
  60. package/plugins/sp/scripts/task-diffstat.ts +8 -39
  61. package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
  62. package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
  63. package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
  64. package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
  65. package/plugins/sp/skills/next-feature/SKILL.md +1 -1
  66. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  67. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  68. package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
  69. package/plugins/sp/skills/spur-cli/references/decision.md +32 -7
  70. package/plugins/sp/skills/spur-cli/references/features.md +46 -5
  71. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  72. package/plugins/sp/skills/spur-dev/references/execution-batch.md +13 -3
  73. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
  74. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
  75. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +29 -12
  76. package/spur.js +80681 -79296
  77. package/web/_astro/{BoardApp.B3GQZvj8.js → BoardApp.5IIl2O-z.js} +1 -1
  78. package/web/_astro/{BoardApp.BT7L2kSb.js → BoardApp.Drnx-KeM.js} +150 -150
  79. package/web/_astro/{TaskDetail.Cxov1_N_.js → TaskDetail.Ci4LNn9S.js} +1 -1
  80. package/web/_astro/{arc.vaT7tPOB.js → arc.j2EVkYVd.js} +1 -1
  81. package/web/_astro/{architectureDiagram-3BPJPVTR.DRMmTjkN.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
  82. package/web/_astro/{blockDiagram-GPEHLZMM.cla0LO_q.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
  83. package/web/_astro/{c4Diagram-AAUBKEIU.D4YAIBKn.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
  84. package/web/_astro/channel.DmdDxGsq.js +1 -0
  85. package/web/_astro/{chunk-2J33WTMH.BrSJbMRZ.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
  86. package/web/_astro/{chunk-4BX2VUAB.CuLMgKF5.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
  87. package/web/_astro/{chunk-55IACEB6.zkvgu54F.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
  88. package/web/_astro/{chunk-727SXJPM.DxWkACh8.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
  89. package/web/_astro/{chunk-AQP2D5EJ.42XrmIlH.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
  90. package/web/_astro/{chunk-FMBD7UC4.Bao2wMK9.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
  91. package/web/_astro/{chunk-ND2GUHAM.BhydojD1.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
  92. package/web/_astro/{chunk-QZHKN3VN.CmZ82g-H.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
  93. package/web/_astro/{classDiagram-4FO5ZUOK.Blr83Gt_.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
  94. package/web/_astro/{classDiagram-v2-Q7XG4LA2.Blr83Gt_.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
  95. package/web/_astro/{cose-bilkent-S5V4N54A.qmfv0SPn.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
  96. package/web/_astro/{cynefin-OW5HDTMX.BtELhXPR.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
  97. package/web/_astro/{dagre-BM42HDAG.AWmaAvXo.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
  98. package/web/_astro/{diagram-2AECGRRQ.DdH34KDi.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
  99. package/web/_astro/{diagram-5GNKFQAL.BmNPix1k.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
  100. package/web/_astro/{diagram-KO2AKTUF.BNQd_Ppo.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
  101. package/web/_astro/{diagram-LMA3HP47.BAPgDi0e.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
  102. package/web/_astro/{diagram-OG6HWLK6.DZ4zyLdC.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
  103. package/web/_astro/{erDiagram-TEJ5UH35.Q1TySMmw.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
  104. package/web/_astro/{flowDiagram-I6XJVG4X.BXhFBicG.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
  105. package/web/_astro/{ganttDiagram-6RSMTGT7.vHSgcuo2.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
  106. package/web/_astro/{gitGraphDiagram-PVQCEYII.Bi1f73C7.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
  107. package/web/_astro/index.Do2lLdgo.css +1 -0
  108. package/web/_astro/{infoDiagram-5YYISTIA.DPvXZEWm.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
  109. package/web/_astro/{ishikawaDiagram-YF4QCWOH.Cz829fsV.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
  110. package/web/_astro/{journeyDiagram-JHISSGLW.enRnB9Me.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
  111. package/web/_astro/{kanban-definition-UN3LZRKU.DnTNk6d2.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
  112. package/web/_astro/{linear.BzGnBwNL.js → linear.CI6djNi7.js} +1 -1
  113. package/web/_astro/{mermaid.core.CSbTpIVz.js → mermaid.core.CuQPxS8b.js} +4 -4
  114. package/web/_astro/{mindmap-definition-RKZ34NQL.DGJDy8zT.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
  115. package/web/_astro/{pieDiagram-4H26LBE5.HSPdi5_8.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
  116. package/web/_astro/{quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
  117. package/web/_astro/{requirementDiagram-4Y6WPE33.CnBCtFwa.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
  118. package/web/_astro/{sankeyDiagram-5OEKKPKP.5M8VWAu8.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
  119. package/web/_astro/{sequenceDiagram-3UESZ5HK.DN41XN-U.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
  120. package/web/_astro/{stateDiagram-AJRCARHV.ErOO3W8j.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
  121. package/web/_astro/{stateDiagram-v2-BHNVJYJU.B_ZHICM-.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
  122. package/web/_astro/{timeline-definition-PNZ67QCA.CWdosr07.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
  123. package/web/_astro/{vennDiagram-CIIHVFJN.BHzYjNV5.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
  124. package/web/_astro/{wardleyDiagram-YWT4CUSO.h8x4IwPX.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
  125. package/web/_astro/{xychartDiagram-2RQKCTM6.RTapW2X8.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
  126. package/web/index.html +2 -2
  127. package/config/proportional-route-table.ts +0 -155
  128. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  129. package/web/_astro/channel.D4nG9YLf.js +0 -1
  130. package/web/_astro/index.BhSwN8yY.css +0 -1
  131. /package/plugins/sp/{hooks → lib}/context-owner.ts +0 -0
  132. /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
  133. /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: spur-cli-decision
3
- description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.22 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093."
3
+ description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers with per-maker readiness marks (registered, configured, recorded, probed). Backed by @gobing-ai/ts-ai-decision 0.5.22 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093; readiness marks and `--probe` by feature P11."
4
4
  see_also:
5
5
  - spur-cli
6
6
  ---
@@ -25,9 +25,10 @@ Decisions resolve first-wins across three layers:
25
25
  | `registered` | folders listed in config `decisions.paths` | Operator-added catalogs |
26
26
  | `shared` | `decisions/` under the bundled config root | Shipped catalogs (`config/decisions/` in-repo) |
27
27
 
28
- The bundled shared catalog ships `task-triage`, `failure-class`, and `review-failure-class` (all
29
- `choice` type, param `wbs`). Duplicate ids across layers keep the higher layer; duplicates inside
30
- one file are a load error.
28
+ The bundled shared catalog ships `task-triage`, `failure-class`, `review-failure-class`,
29
+ `gate-evidence`, `idea-recommendation`, `needs-design`, `anatomy-validation-verdict` and
30
+ `maker-liveness` (the last served only by `spur decision status --probe`; no workflow references
31
+ it). Duplicate ids across layers keep the higher layer; duplicates inside one file are a load error.
31
32
 
32
33
  **Config keys** (under `decisions:` in the global `~/.config/spur/config.yaml` or project `.spur/config.yaml`):
33
34
  `paths` (extra catalog folders forming the `registered` layer), `maker` (default DecisionMaker
@@ -41,7 +42,7 @@ config error, never a silent fallback (design §3.6).
41
42
  | `list` | Every visible decision: id, type, layer, catalog file, description | `--layer <layer>`, `--json` |
42
43
  | `show <id>` | One served contract: type, parameters, criteria, fallback, minConfidence, effective maker + source | `--json` |
43
44
  | `run <id>` | Serve one outcome (closed vocabulary; **never writes a resultFile**) | `--param <pairs...>`, `--evidence <files...>`, `--maker <name>`, `--json` |
44
- | `status` | Layer counts, registered makers, per-decision maker resolution, load errors | `--json` |
45
+ | `status` | Sectioned readiness report: one status line, errors, aligned `Makers` and `Decisions` tables (registration, configuration source, recorded evidence), `Catalog` block | `--probe`, `--reliability [--since <iso>]`, `--json` |
45
46
 
46
47
  All verbs support `--json` (bare payload) and `--json-envelope` (`{ok, data|error}` envelope,
47
48
  ADR-091). Failure output for `--json` commands uses the canonical error envelope only under
@@ -51,6 +52,25 @@ ADR-091). Failure output for `--json` commands uses the canonical error envelope
51
52
  (design §3.4 — load errors surface via `status`) except for an invalid `--layer` value (exit 1).
52
53
  Error envelopes use `NOT_FOUND` for an unknown id and `VALIDATION_FAILED` for every other caller mistake.
53
54
 
55
+ ## Readiness marks (`status`, feature P11)
56
+
57
+ The default `status` output separates four marks per maker and per decision
58
+ (`docs/design/decision-observability-and-adoption.md` §3.4.1):
59
+
60
+ | Mark | Column | Source |
61
+ | ---- | ------ | ------ |
62
+ | registered | `ST` glyph on the maker row | the maker registry holds the name |
63
+ | configured | `CONFIGURED BY` | `decisions.maker` / `decisions.makers.<id>` |
64
+ | recorded | `RECORDED` / `ACCEPTED` / `LAST SEEN` | the recorded `decision.end` ledger, matched on `(decisionId, maker)` |
65
+ | proven | `PROBE` (only with `--probe`) | one live liveness decision per maker |
66
+
67
+ `RECORDED` with `ACCEPTED 0` is the state that reads as healthy everywhere else: the maker is
68
+ registered and configured but has never once succeeded. `--probe` is the only path that calls a
69
+ maker, is bounded per driver, writes no decision log row and emits no event (`--probe` with
70
+ `--reliability` is refused). Parse the marks with `--json`: `makers[]`
71
+ (`registered`, `configuredBy`, `serves`, `evidence`), `perDecision[].evidence`, and `probes[]`
72
+ under `--probe`.
73
+
54
74
  **Exit contract for `run` (design §3.4):** any backend-observed outcome — low confidence, model
55
75
  error surfaced as fallback, declined — is still a **successful serve** (exit 0, envelope carries
56
76
  `source`/`confidence` so callers can see why). Exit 1 is reserved for caller mistakes caught
@@ -95,11 +115,16 @@ spur decision run task-triage --param wbs=1093 --json
95
115
  spur decision run failure-class --param wbs=1093 \
96
116
  --evidence .spur/run/<runId>-test-output.txt # redacted evidence -> instructions
97
117
  spur decision status --json | jq '.loadErrors' # audit catalog health
118
+ spur decision status # readiness marks: registered / configured / recorded
119
+ spur decision status --probe # prove each effective maker answers now
120
+ spur decision status --json | jq '.makers[] | {name, registered, recorded: .evidence.recorded}'
98
121
  ```
99
122
 
100
123
  ## References
101
124
 
102
- - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface)
125
+ - Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface),
126
+ `docs/design/decision-observability-and-adoption.md` (§3.4 reliability report, §3.4.1 readiness view)
103
127
  - Hub: `@gobing-ai/ts-ai-decision` 0.5.22 (`DecisionService`, catalog schema, maker registry)
104
- - App service: `packages/app/src/decision/decision-service.ts`
128
+ - App service: `packages/app/src/decision/decision-service.ts` (makers, `probeMaker`),
129
+ `packages/app/src/decision/decision-status-readiness.ts` (readiness marks, probe loop)
105
130
  - Shipped catalogs: `config/decisions/*.yaml`
@@ -81,9 +81,45 @@ mode needs its own required args).
81
81
  spur feature update H2 active
82
82
  ```
83
83
 
84
- Valid statuses: `backlog → active → verifying → blocked → done → cancelled`. The lifecycle engine
85
- enforces legal transitions; `verifying` is the gate where AC traceability must hold (L4).
86
- **One active goal** at a time is enforced by `check` (a corpus invariant).
84
+ Valid statuses: `backlog → active → verifying → blocked → done → cancelled`, plus the frozen
85
+ `deprecated`. The lifecycle engine enforces legal transitions; `verifying` is the gate where AC
86
+ traceability must hold (L4). **One active goal** at a time is enforced by `check` (a corpus invariant).
87
+
88
+ **`deprecated` — frozen, not terminal (ADR-136).** The state means *retained and frozen*: the record
89
+ stays readable and countable, but nothing may attach new work to it or advance it. It is entered from
90
+ `backlog`, `active`, `verifying`, `blocked` and `done`, and left only to `active` (un-deprecate,
91
+ mandatory History entry) or `cancelled` (removed) — every other edge out of `deprecated` is undeclared
92
+ and refused by the graph. The freeze, as shipped:
93
+
94
+ - **Task links are refused** — `task create`, `update --feature` and `batch-create` exit non-zero and
95
+ write nothing (no sibling hint; `--no-reopen` is irrelevant for a frozen parent):
96
+
97
+ ```
98
+ parent-feature-status: feature <id> is deprecated (frozen); new tasks cannot link to it. Run removal work under its own feature, or un-deprecate: spur feature update <id> active
99
+ ```
100
+
101
+ - **`L4.feature-deprecated`** — `task check` warns (does not error) for a *live* task under a
102
+ deprecated feature; a terminal task is the correct end state and gets no finding; `--strict`
103
+ elevates the warning as usual:
104
+
105
+ ```
106
+ Feature "<id>" is deprecated (frozen) — re-parent this live task (`spur task update <wbs> --feature <otherId>`) or cancel it.
107
+ ```
108
+
109
+ - **`update --section` still lands** (exit `0`), appending a warning to `warnings[]`:
110
+
111
+ ```
112
+ Feature <id> is deprecated (frozen); change it only as part of removal
113
+ ```
114
+
115
+ - **`sync` / `refresh` / `check --fix` never move it** — derivation is sticky and returns the no-op
116
+ proposal `{ from: 'deprecated', to: 'deprecated', reason: 'Feature is deprecated (frozen)' }`.
117
+
118
+ - **`advance` refuses** with exit `1`, naming the un-deprecate command:
119
+
120
+ ```
121
+ <id> is deprecated; un-deprecate first: spur feature update <id> active
122
+ ```
87
123
 
88
124
  **Scalar field set:**
89
125
 
@@ -113,7 +149,8 @@ spur feature advance H2 --to verifying
113
149
  ```
114
150
 
115
151
  Walks the legal forward path (`backlog → active → verifying → done`) hop-by-hop until `--to`
116
- (default `done`). No-op when already at the target.
152
+ (default `done`). No-op when already at the target; a `deprecated` feature is refused up front (see
153
+ the freeze block above).
117
154
 
118
155
  ## Acceptance criteria conventions
119
156
 
@@ -169,7 +206,8 @@ the AC coverage map. Habits that keep it green:
169
206
  Use feature hierarchy for user-facing capabilities and roadmap themes; keep technical-module mapping
170
207
  as prose or notes unless the product surface is developer infrastructure. Priority is a scalar field
171
208
  (`spur feature update <id> --field priority --value P1`), while status is lifecycle state
172
- (`backlog → active → verifying → blocked → done → cancelled`). Do not conflate the two.
209
+ (`backlog → active → verifying → blocked → done → cancelled`, plus the frozen `deprecated`). Do not
210
+ conflate the two.
173
211
 
174
212
  **Structure first:** [features/hierarchy-mece.md](features/hierarchy-mece.md) (MECE roots, when to
175
213
  extend vs create). **Then priority:** [features/roadmap-priority.md](features/roadmap-priority.md).
@@ -216,6 +254,9 @@ spur feature sync H2 --force # re-derive live: bypass replay +
216
254
  spur feature sync H2 --folder docs/custom-tasks --json # non-default tasks folder
217
255
  ```
218
256
 
257
+ - **`deprecated` is sticky:** derivation returns no proposal for it
258
+ (`reason: 'Feature is deprecated (frozen)'`), so neither `sync` nor `check --fix` moves a frozen
259
+ feature — see the freeze block in §Editing a feature.
219
260
  - **`[id]`** syncs one feature; **`--all`** syncs every feature with linked tasks. One of the two is
220
261
  required - exit `2` if neither is given.
221
262
  - **`--dry-run`** reports proposed transitions without applying. **`--force`** applies a *reopen*
@@ -126,7 +126,7 @@ success exits 0 printing
126
126
  `{"ok":true,"persisted":<n>,"skipped":[{"id":<run-id>,"reason":"id-exists"|"external-key-conflict"|"record-conflict:<file>"|"record-missing:<file>"|"cited-directory:<name>"|"cited-symlink:<name>"|"cited-non-file:<name>"}],"evidenceSkipped":[{"name":<file>,"reason":"foreign-divergent","newer":"invoking"|"worktree"}]}`
127
127
  (`evidenceSkipped` is omitted when empty; the same object is written to `<invoking-tree>/.spur/run/persist-out.json` for `persist-out-check` to read)
128
128
  — an `external-key-conflict` skip leaves the target run unchanged **and fails the pass (1049)**: the delegate exits 1 printing `{"ok":false,"error":…}` naming the skipped source run ids, because that (workflow, external key) identity already belongs to a different receiving run — the batch was not persisted, so reconcile the source worktree by hand (it stays the provenance owner of record) before any teardown: auditable reconciliation names the original archived DB snapshot and the skipped source run identities, verifies the archive by file hash and by merged-commit ancestry of the source branch, and treats any residual deletion as explicit operator-authorized cleanup under the strict canonical evidence rules above. Task 1149 R1 grades a terminal duplicate lifecycle row (`task-lifecycle`/`feature-lifecycle`) as `external-key-conflict-bookkeeping` (non-fatal), so guarded transitions driven inside the worktree do not block teardown; the receiving lifecycle row is reconciled automatically during persist-out. An `id-exists` replay repairs missing owned artifacts and task links without duplicating them. A
129
- `record-conflict:<file>` never overwrites a divergent invoking-tree record and causes the delegate to exit 1, retaining the worktree. A
129
+ `record-conflict:<file>` never overwrites a divergent invoking-tree record and causes the delegate to exit 1, retaining the worktree. Before reconciling one by hand, check **what** the invoking record is: when the run closed `done` inside a linked worktree the owning tree holds that run's **WT-0 landing obligation** (`landing: required branch=… path=… base=…`, see [execution-worktree-landing.md § WT-0](execution-worktree-landing.md#wt-0--markerless-worktree-runs)) — a 3-line record that is *not* a stub of the worktree's stage narrative, so copying the worktree record over it destroys the very obligation a later merge must rewrite. The reconciliation is therefore: discharge the obligation in place (`landing: merged <sha>` or `landing: retained`), and preserve the worktree narrative beside it (e.g. `<runId>.worktree-provenance.md`) rather than in place of it. A
130
130
  `record-missing:<file>` skip is a known `task-lifecycle`/`feature-lifecycle` row with no record file
131
131
  at all (its inserted DB row still counts in `persisted` — 0984 R5). Any failure
132
132
  exits 1 printing `{"ok":false,"error":<message>}` (a worktree DB run id that is not a single safe
@@ -606,15 +606,25 @@ The cheap rule gates fail fast when run first — the full-gate run is dominated
606
606
  suite, so a gate run that dies at `test-post-check` or `corpus-check` wastes most of its wall time.
607
607
  Before launching a full `spur-check-new`:
608
608
 
609
- 1. **Run the two rule gates first** — `bun run test-pre-check` (43 rules: `no-console-output`,
609
+ 1. **Run the static checks first** — `bun run lint` (biome over the whole tree plus every workspace
610
+ typecheck). They fail in ~20 s and are the two defects that most often kill a full launch: a
611
+ formatting miss in a touched test file, or a generated declaration surface that no longer lists a
612
+ new export. Measured 2026-10-12: two consecutive `spur-check` launches died here and each spent
613
+ its whole wall time before reaching the suite.
614
+ 2. **Then the two rule gates** — `bun run test-pre-check` (43 rules: `no-console-output`,
610
615
  `no-direct-process-spawn`, `cli-*`, `require-corresponding-test`) and `bun run test-post-check`
611
616
  (`every-export-has-tsdoc`, `coverage-gate`). They catch boundary/TSDoc violations in seconds.
612
- 2. **Promoted code must satisfy the boundary rules `scripts/` never enforced.** A command module
617
+ Two `coverage-gate` facts change how you call post-check: it **fails closed when
618
+ `.coverage/lcov.info` is absent**, which is the normal state of a fresh worktree, so run it after
619
+ the suite has written coverage or expect that one error instead of reading it as a finding; and a
620
+ targeted `bun test <file>` **overwrites** the tracefile, so post-check judges partial data if it
621
+ runs after one (the `coverage-gate` rule's own header documents both facts).
622
+ 3. **Promoted code must satisfy the boundary rules `scripts/` never enforced.** A command module
613
623
  moving from `scripts/` into `apps/cli/src` must route output through the `CommandOutput` seam (no
614
624
  `console.*`), spawn processes via `NodeProcessExecutor` (no `Bun.spawnSync`), get a
615
625
  `runtime-boundaries` fs-io exemption for sync reads (mirrors `task.ts`), and a non-command helper
616
626
  must not live in `apps/cli/src/commands/` (the noun scan treats every file there as a noun).
617
- 3. **Doc/TSDoc edits shift `file:line` anchors** cited by other tasks — the per-task gate surfaces
627
+ 4. **Doc/TSDoc edits shift `file:line` anchors** cited by other tasks — the per-task gate surfaces
618
628
  them as `L4.anchor-subject-mismatch` (0775: the corpus sweep retired; run `spur task check <wbs>`
619
629
  on touched tasks). Repoint the shifted citations (via `spur task update --section`)
620
630
 
@@ -16,6 +16,28 @@ When the batch completes with **no failed task**, fast-forward-merge the worktre
16
16
  base ref. The terminal action after the merge differs by mode (ownership rule: *the flag removes
17
17
  only what it created*):
18
18
 
19
+ **Duplicate-WBS sweep before the merge (observed 2026-10-12).** WBS allocation is per-tree — a
20
+ worktree's allocator reads only its own corpus and DB — so two trees cut from the same base both
21
+ allocate the same next number, and the collision surfaces only here (measured: the F42 batch's
22
+ residual task shipped as `1183` while the base ref had already given `1183` to another task, so the
23
+ file had to be re-filed before landing). Run this against the ref you are about to merge INTO:
24
+
25
+ ```bash
26
+ TARGET=<base-ref>; BRANCH=sp/<branch>
27
+ for f in $(git diff --diff-filter=A --name-only "$(git merge-base "$TARGET" "$BRANCH")..$BRANCH" -- docs \
28
+ | grep -E '^docs/tasks[0-9]*/'); do
29
+ wbs=$(basename "$f" | cut -c1-4)
30
+ git ls-tree -r --name-only "$TARGET" -- docs | grep -qE "/${wbs}_" && echo "COLLISION $wbs $f"
31
+ done
32
+ ```
33
+
34
+ A hit means one file must lose: keep the target's task, re-create the branch's task in the invoking
35
+ tree through `spur task create` (the allocator picks the next free number), and drop the branch's
36
+ file in the merge resolution — never land both, and never renumber by hand. Verified against a
37
+ fixture (flags a duplicate, silent on a free WBS). The pathspec form is load-bearing: `-- docs` plus
38
+ the `^docs/tasks[0-9]*/` filter, because plain `docs/tasks*` matches nothing under `ls-tree` here and
39
+ `:(glob)` magic is rejected by `ls-tree`/`diff` (it works for `git status`).
40
+
19
41
  #### Create mode — merge, remove, delete
20
42
 
21
43
  ```bash
@@ -180,6 +180,13 @@ main-repo hooks from inside the "isolated" tree. Scripts are skipped only at thi
180
180
  a normal clone keeps `prepare`. The worktree still gets a usable dependency tree — the install
181
181
  exists so the first `bun test` resolves workspace imports.
182
182
 
183
+ **Generated trees are still missing after the install.** A gitignored generated tree does not ride the
184
+ worktree, so any suite comparing the canonical source against its generated copy fails until the
185
+ generator runs. In this repo that is `apps/cli/config/` (from `bun run bundle:config`): without it
186
+ `plugins/sp/tests/command-flag-parity.test.ts` fails on the missing generated pipeline YAML under that
187
+ directory (observed 2026-10-12, A91 batch). Run the generator once after a create-mode install, and
188
+ again after a rebase or merge that touches the canonical workflow sources it is built from.
189
+
183
190
  #### Reuse mode (`--worktree <name>`)
184
191
 
185
192
  Creation is **skipped entirely**. `$BRANCH` is the resolved worktree's already-checked-out branch; a
@@ -374,6 +381,13 @@ same call, verifies the WT-3 identity, and never relies on a persistent `cd` or
374
381
  > concurrent writer happened to leave in the tree — the two incidents this guard exists for are
375
382
  > `a94f9f431` and `36f274590` — so a driver stages only through `commit-guard stage`.
376
383
 
384
+ > **`stage` takes files, never directories.** A directory argument is refused as `(unchanged since
385
+ > base)` and stages *nothing*, so a task whose diff lives in a moved tree must expand the changed
386
+ > paths (`git status --porcelain` plus the filter for the task's own files) instead of passing
387
+ > `plugins/sp/hooks` or `packages/app/tests/hooks`. Symptom observed on 2026-10-12 (A91 batch): the
388
+ > first commit captured only the pre-staged `git mv`/`git rm` renames and needed an amend to carry
389
+ > the rest of the task.
390
+
377
391
  The FF-only git merge carries only commits — uncommitted writes in the worktree would be left
378
392
  behind by the merge and then destroyed by create mode's `git worktree remove`. WT-3b exists so
379
393
  that can never happen.
@@ -441,9 +441,9 @@ Action semantics come from the YAML and the workflow action contract:
441
441
  recheck's `no-progress` guard honours it, so a cap-kill reads as a red gate the next time. Preserve
442
442
  the signal-induced receipt under a distinct name (`<wbs>-check-receipt.false-fail-<signal>.json`)
443
443
  and say so in the run log before re-running: never delete it silently, and never report a
444
- cap-killed attempt as a genuine gate failure. Measured this session: one foreground gate call
445
- (25.6 min host-wide lock wait + 7.4 min runtime) was SIGTERM-killed at the host cap and produced
446
- exactly that false FAIL receipt.
444
+ cap-killed attempt as a genuine gate failure. Measured 2026-10-10 (before the gate-lock removal):
445
+ one foreground gate call (25.6 min host-wide lock wait + 7.4 min runtime) was SIGTERM-killed at the
446
+ host cap and produced exactly that false FAIL receipt.
447
447
  - `note` — append the expanded message to the inline run log.
448
448
  - `doctor.probe` — run the declared Spur doctor once, persist its status file, and apply any
449
449
  `setVars` result (including a resolved executor) before the next action or state.
@@ -766,7 +766,7 @@ host-owned gate sat between the stages, or when the platform cannot address comp
766
766
  **Implement and test-fix workers never run the full gate (task 1142 R3).** Workers use the light tier as their iteration check (task 1142 R3) in exact order:
767
767
  1. the **changed-path matrix** (`code-implementation/SKILL.md` § Changed-path targeted checks) over the paths they touched;
768
768
  2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome, per-workspace typecheck, rule presets, related tests).
769
- Neither step takes the full-gate lock. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
769
+ Neither step takes the full gate. The pipeline's `test` and `test-recheck` hops remain the only full gate. Carry
770
770
  `--gate-log <path>` through to the child **verbatim**: never paraphrase it into "re-run the named
771
771
  gate command". That paraphrase is not cosmetic — it sent a worker into a full gate run instead of
772
772
  targeted iteration checks. The pipeline's next `test` (recheck)
@@ -835,6 +835,19 @@ The quality gate's `proofDigest` certifies a specific tree. Two rules protect it
835
835
  before a review dispatch is spent. The mismatch routes through the existing tripwire to `failed`;
836
836
  there is no new re-certify path.
837
837
 
838
+ **A host-driven pass that skips the `test` hop has no digest-bound receipt.** `proofDigest` is
839
+ captured by the `test` state's `proof.fingerprint` action, so hops driven out of band — an operator
840
+ implementing/verifying/recording by hand, or an audit of a task whose gate the host ran directly —
841
+ leave no `.spur/run/<wbs>-check-receipt.json`. The deciding artifacts are then
842
+ `.spur/run/<wbs>-test-gate.status` plus its gate log. Two consequences, in order of preference:
843
+
844
+ 1. **Drive the pipeline** for anything that must be certified: only a digest-bound full receipt
845
+ satisfies a review/verify/record boundary (`sp-spur-check`), and the `test` hop owns the capture.
846
+ 2. **When a host-driven pass is unavoidable, say so in the verdict** — name the status file and gate
847
+ log as the evidence and grade confidence `MEDIUM`. A `HIGH` claim needs re-runnable evidence from a
848
+ checked path (the `sp-code-verification` ladder); claiming a receipt that was never written is the
849
+ failure mode this note exists to prevent (observed across four A91 tasks, 2026-10-12).
850
+
838
851
  **A mismatch names the drifted paths (R5).** At gate entry the pipeline snapshots the non-corpus tree
839
852
  state (`git status --porcelain=v1 -uall` plus `git diff --name-only HEAD`, excluding
840
853
  `docs/tasks*` / `docs/features*`) into `.spur/run/<run-id>-gate-paths.txt`. On a mismatch the
@@ -909,21 +922,25 @@ verdict artifact exists (tasks 0617, 0619 — the clobbering spiral).
909
922
 
910
923
  ## Running the full gate (task 0872 / 1127 R7)
911
924
 
912
- The full gate serializes behind a host-wide lock and legitimately runs 10-25 minutes. Two rules keep
913
- it from becoming a hazard (both violated once on 2026-10-10, blocking every other session for ~11
914
- minutes and aborting the run's own verification):
925
+ The full gate legitimately runs 10-25 minutes. Two rules keep it from becoming a hazard (both
926
+ violated once on 2026-10-10, aborting the run's own verification):
915
927
 
916
928
  - **Never wrap it in an external `timeout`, and never treat its runtime as a hang.** An outer bound
917
- that fires mid-suite leaves the holder alive holding the lock while the caller believes it is gone.
918
- - **Run it detached and poll the log**, so losing the foreground call cannot lose the holder:
929
+ that fires mid-suite leaves a half-finished suite behind while the caller believes it is gone.
930
+ - **Run it detached and poll the log**, so losing the foreground call cannot lose the run:
919
931
 
920
932
  ```bash
921
933
  nohup bash -c 'bun run spur-check > /tmp/gate.log 2>&1; echo "EXIT=$?" >> /tmp/gate.log' &
922
934
  ```
923
935
 
924
- - **Never kill a gate holder whose pid is alive.** The waiter line `waiting for holder (… held Nms)`
925
- is printed only after `isProcessAlive(held.claim.pid)` succeeded, so a printed holder is a live one
926
- by construction; only the lock's own dead-pid reclaim is authorised to clear a claim.
936
+ - **Preserve a signal-induced receipt.** A cap-killed attempt leaves a red receipt that the next
937
+ recheck's `no-progress` guard honours, so it reads as a genuine failure unless the receipt is kept
938
+ under a distinct name and the run log says so.
939
+
940
+ > Historical (task 1127, removed in task 1173): these rules were once stated in terms of a host-wide
941
+ > `full-gate.lock`. The lock mechanism was deleted (`refactor(gate): remove gate-lock mechanism and
942
+ > simplify quality-gate`), so nothing serializes gate runs today — the guidance above stands on its
943
+ > own without it.
927
944
 
928
945
  ## Runtime interface contracts (from run evidence)
929
946