@gobing-ai/spur 0.3.85 → 0.3.87

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 (139) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +0 -25
  3. package/config/plugin-scripts.json +4 -5
  4. package/config/rules/boundary/env-var-hygiene.yaml +41 -0
  5. package/config/templates/feature/default.md +2 -0
  6. package/config/templates/task/brainstorm.md +2 -2
  7. package/config/templates/task/feature-impl.md +2 -2
  8. package/config/templates/task/issue.md +2 -2
  9. package/config/templates/task/meta.md +2 -2
  10. package/config/templates/task/review.md +2 -2
  11. package/config/templates/task/standard.md +2 -2
  12. package/config/transition-shims.json +1 -1
  13. package/config/workflow-candidates.json +19 -0
  14. package/config/workflows/feature-lifecycle.yaml +10 -2
  15. package/config/workflows/feature-verification.yaml +71 -0
  16. package/config/workflows/idea-pipeline.yaml +87 -39
  17. package/config/workflows/task-pipeline.yaml +43 -12
  18. package/config/workflows/wrapup-pipeline.yaml +70 -7
  19. package/package.json +9 -9
  20. package/plugins/sp/README.md +7 -7
  21. package/plugins/sp/commands/dev-idea.md +9 -2
  22. package/plugins/sp/commands/dev-refactor.md +33 -0
  23. package/plugins/sp/hooks/agent-hint.ts +5 -4
  24. package/plugins/sp/hooks/careful-guard.ts +3 -1
  25. package/plugins/sp/hooks/context-post-tool.ts +2 -1
  26. package/plugins/sp/hooks/context-session-start.ts +4 -3
  27. package/plugins/sp/hooks/context-session-stop.ts +2 -1
  28. package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
  29. package/plugins/sp/hooks/task-write-guard.ts +4 -3
  30. package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
  31. package/plugins/sp/plugin.json +1 -1
  32. package/plugins/sp/references/roles.md +1 -1
  33. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
  34. package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
  35. package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
  36. package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
  37. package/plugins/sp/scripts/idea-coverage-check.ts +168 -0
  38. package/plugins/sp/scripts/idea-handoff.mjs +6 -1
  39. package/plugins/sp/scripts/idea-handoff.ts +4 -1
  40. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +115 -4
  41. package/plugins/sp/scripts/inline-run-setup.ts +195 -2
  42. package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
  43. package/plugins/sp/scripts/pr-reviewing.ts +2 -1
  44. package/plugins/sp/scripts/quality-gate.mjs +8 -1
  45. package/plugins/sp/scripts/quality-gate.ts +2 -1
  46. package/plugins/sp/scripts/surface-drift-inventory.ts +1 -4
  47. package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
  48. package/plugins/sp/scripts/task-size-precheck.ts +6 -4
  49. package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
  50. package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
  51. package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
  52. package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
  53. package/plugins/sp/scripts/wrapup-steps.ts +2 -1
  54. package/plugins/sp/skills/brainstorm/SKILL.md +4 -0
  55. package/plugins/sp/skills/code-refactoring/SKILL.md +155 -0
  56. package/plugins/sp/skills/code-refactoring/references/finding-schema.md +74 -0
  57. package/plugins/sp/skills/code-refactoring/references/fix-ladder.md +52 -0
  58. package/plugins/sp/skills/code-refactoring/references/focus-detection.md +44 -0
  59. package/plugins/sp/skills/code-refactoring/references/refactor-finding.schema.json +95 -0
  60. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +23 -16
  61. package/plugins/sp/skills/spur-cli/references/features/acceptance-criteria.md +4 -2
  62. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  63. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +2 -2
  64. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +3 -3
  65. package/plugins/sp/skills/spur-cli/references/workflows/workflow-fit-and-tuning.md +1 -1
  66. package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
  67. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +24 -0
  68. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
  69. package/plugins/sp/skills/spur-dev/references/dev-operations.md +18 -2
  70. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +22 -3
  72. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
  73. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +11 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +91 -1
  75. package/plugins/sp/skills/taste-refactoring-api/SKILL.md +44 -1
  76. package/plugins/sp/skills/taste-refactoring-api/references/protocol-modes.md +31 -0
  77. package/plugins/sp/skills/taste-refactoring-architect/SKILL.md +43 -0
  78. package/plugins/sp/skills/taste-refactoring-tests/SKILL.md +42 -0
  79. package/plugins/sp/skills/taste-refactoring-ui/SKILL.md +43 -0
  80. package/schemas/state-machine-workflow.schema.json +5 -0
  81. package/schemas/task-batch.schema.json +2 -2
  82. package/schemas/transition-flow-workflow.schema.json +5 -0
  83. package/spur.js +21106 -20669
  84. package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
  85. package/web/_astro/{BoardApp.yW425dRZ.js → BoardApp.yBBcFXWP.js} +4 -4
  86. package/web/_astro/{TaskDetail.CGwJAinW.js → TaskDetail.DqFJbRFc.js} +1 -1
  87. package/web/_astro/{arc.B-qNXzSO.js → arc.DL-BpHoi.js} +1 -1
  88. package/web/_astro/{architectureDiagram-3BPJPVTR.Bdg-xiji.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
  89. package/web/_astro/{blockDiagram-GPEHLZMM.DFArg3Kp.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
  90. package/web/_astro/{c4Diagram-AAUBKEIU.BkOLcjyx.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
  91. package/web/_astro/channel.CX5453qQ.js +1 -0
  92. package/web/_astro/{chunk-2J33WTMH.DN3c20V4.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
  93. package/web/_astro/{chunk-4BX2VUAB.B64K0Ttd.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
  94. package/web/_astro/{chunk-55IACEB6.C-AJvNwa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
  95. package/web/_astro/{chunk-727SXJPM.DMkr46uS.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
  96. package/web/_astro/{chunk-AQP2D5EJ.4_WGbI64.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
  97. package/web/_astro/{chunk-FMBD7UC4.cbFW_lVE.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
  98. package/web/_astro/{chunk-ND2GUHAM.CDW_-2O1.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
  99. package/web/_astro/{chunk-QZHKN3VN.BoYOt_yg.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
  100. package/web/_astro/{classDiagram-4FO5ZUOK.BNT6XpHQ.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
  101. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BNT6XpHQ.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
  102. package/web/_astro/{cose-bilkent-S5V4N54A.CI1DQxR3.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
  103. package/web/_astro/{cynefin-OW5HDTMX.A8ARH61p.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
  104. package/web/_astro/{dagre-BM42HDAG.u28ZmqwR.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
  105. package/web/_astro/{diagram-2AECGRRQ.Dx8JcrGc.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
  106. package/web/_astro/{diagram-5GNKFQAL.BXJUG9GY.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
  107. package/web/_astro/{diagram-KO2AKTUF.BtlMuWqW.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
  108. package/web/_astro/{diagram-LMA3HP47.C4T7qaAy.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
  109. package/web/_astro/{diagram-OG6HWLK6.D5HECE10.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
  110. package/web/_astro/{erDiagram-TEJ5UH35.B_hMR3yw.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
  111. package/web/_astro/{flowDiagram-I6XJVG4X.BAmpcYl3.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
  112. package/web/_astro/{ganttDiagram-6RSMTGT7.DV1bSWK-.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
  113. package/web/_astro/{gitGraphDiagram-PVQCEYII.DxYKkKrJ.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
  114. package/web/_astro/{infoDiagram-5YYISTIA.BAWY2xMb.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
  115. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CZssRn6V.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
  116. package/web/_astro/{journeyDiagram-JHISSGLW.C--muARd.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
  117. package/web/_astro/{kanban-definition-UN3LZRKU.D_QCK4et.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
  118. package/web/_astro/{linear.DzrmTtZ0.js → linear.Nv_xOUjP.js} +1 -1
  119. package/web/_astro/{mermaid.core.Da03W3iu.js → mermaid.core.Bc4LqQgX.js} +4 -4
  120. package/web/_astro/{mindmap-definition-RKZ34NQL.6cy-8hR_.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
  121. package/web/_astro/{pieDiagram-4H26LBE5.C5rS1pdU.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
  122. package/web/_astro/{quadrantDiagram-W4KKPZXB.w56GZZ6Q.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
  123. package/web/_astro/{requirementDiagram-4Y6WPE33.CipX3Pwu.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
  124. package/web/_astro/{sankeyDiagram-5OEKKPKP.C0VVzgJm.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
  125. package/web/_astro/{sequenceDiagram-3UESZ5HK.BX2dUUbF.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
  126. package/web/_astro/{stateDiagram-AJRCARHV.ypCdgODQ.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
  127. package/web/_astro/{stateDiagram-v2-BHNVJYJU.In0baEtg.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
  128. package/web/_astro/{timeline-definition-PNZ67QCA.CJN4Vkvl.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
  129. package/web/_astro/{vennDiagram-CIIHVFJN.Cf8KkIPY.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
  130. package/web/_astro/{wardleyDiagram-YWT4CUSO.DCZBo9xw.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
  131. package/web/_astro/{xychartDiagram-2RQKCTM6.Cm4v_MiJ.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
  132. package/web/index.html +1 -1
  133. package/config/workflows/basic.yaml +0 -146
  134. package/config/workflows/docs-pipeline.yaml +0 -350
  135. package/config/workflows/feature-dev.yaml +0 -288
  136. package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
  137. package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
  138. package/web/_astro/BoardApp.CHenHFia.js +0 -1
  139. package/web/_astro/channel.CbDHK5UQ.js +0 -1
@@ -0,0 +1,155 @@
1
+ ---
2
+ name: code-refactoring
3
+ description: "Refactoring coordinator with a preservation contract: routes the four taste lenses (api, architect, tests, ui), normalizes findings to a shared schema and P1–P4 severities, gates applies, runs a fix ladder with revert-on-regression. Use for 'dev-refactor', 'safe refactor', 'refactor while keeping every feature'."
4
+ license: Apache-2.0
5
+ metadata:
6
+ author: spur
7
+ version: "1.0"
8
+ platforms: "claude-code,codex,openclaw,opencode,antigravity"
9
+ category: execution
10
+ interactions:
11
+ - technique
12
+ operations:
13
+ - refactor
14
+ openclaw:
15
+ emoji: "♻️"
16
+ see_also:
17
+ - sp:taste-refactoring-api
18
+ - sp:taste-refactoring-architect
19
+ - sp:taste-refactoring-tests
20
+ - sp:taste-refactoring-ui
21
+ - sp:code-simplification
22
+ - sp:code-review
23
+ ---
24
+
25
+ # code-refactoring — the lens-routed refactoring coordinator
26
+
27
+ Own everything a cheaper executor must get right **mechanically**: routing, finding schema,
28
+ severity normalization, preservation classification, gate policy, apply loop, artifacts. The four
29
+ taste lenses keep their principles and judgments; this skill turns their prose into gated,
30
+ checkable, reversible work.
31
+
32
+ The product is **analysis first, edits second**: with the default `--fix none` the run writes two
33
+ artifacts and performs no edit at all.
34
+
35
+ Backs `/sp:dev-refactor`. Authority: `docs/design/dev-refactor-command.md` (§2 phases, §4–§8
36
+ contracts, §11 invariants).
37
+
38
+ ## Inputs
39
+
40
+ | Input | Meaning | Default |
41
+ | --- | --- | --- |
42
+ | `--scope <path>` | Path bound; no edit may land outside it. | working tree (recent changes) |
43
+ | `--focus <api\|architect\|tests\|ui\|auto>` | Lens set; comma list allowed. | `auto` |
44
+ | `--fix <none\|blockers-first\|all>` | Apply policy. | `none` |
45
+ | `--check <cmd>` | Baseline and per-fix verification command. | project gate (`bun run spur-check` when present) |
46
+ | `--auto` | Skips **objective** gates only. | off |
47
+ | free-text description | Steering for the lenses (e.g. "pagination consistency"). | — |
48
+
49
+ ## Stop rules (design §11 — verbatim, non-negotiable)
50
+
51
+ 1. **No edit outside `--scope`; no edit before a green baseline.**
52
+ 2. **`cutting` / `breaking` findings are applied only after an explicit operator `yes`.**
53
+ 3. **Tests are never removed or weakened by an `auto` fix.**
54
+ 4. **Every finding carries `file:line` evidence inside scope.**
55
+ 5. **The command file contains no orchestration logic** — this skill owns the orchestration, the
56
+ command stays a thin wrapper.
57
+
58
+ Violating any stop rule ends the run as a failure; do not "finish" a partial apply.
59
+
60
+ ## Phase 0 — Resolve
61
+
62
+ 1. Resolve `--scope` to a concrete path; list the in-scope files.
63
+ 2. Resolve the lens set: if `--focus auto`, classify per
64
+ [focus-detection.md](./references/focus-detection.md) (ordered globs, first match per file,
65
+ union across files); otherwise use the given lens list.
66
+ 3. **Report the lens set before any lens runs** (one line, per focus-detection.md).
67
+ 4. Run the `--check` command for a **green baseline**. Red baseline = hard stop: write the report,
68
+ apply nothing, end the run.
69
+
70
+ ## Phase 1 — Analyze (dispatch lenses)
71
+
72
+ For each lens in the resolved set, dispatch the taste skill and collect native findings:
73
+
74
+ ```text
75
+ Skill(sp:taste-refactoring-tests) # if tests in lens set
76
+ Skill(sp:taste-refactoring-ui) # if ui in lens set
77
+ Skill(sp:taste-refactoring-api) # if api in lens set
78
+ Skill(sp:taste-refactoring-architect) # if architect in lens set
79
+ ```
80
+
81
+ Pass each lens the scope path and the free-text description. Read the lens's `## Spur contract`
82
+ section, then map its native findings into the shared schema
83
+ ([finding-schema.md](./references/finding-schema.md)): lens-native rung is kept in `rung`,
84
+ severity is translated through the §5 map, and every finding carries a **preserved-behavior
85
+ inventory** input from the lens (endpoints / modules / test-protected behaviors / UI controls in
86
+ scope) before any proposal.
87
+
88
+ Classification rules when mapping (enforced by the structural check):
89
+
90
+ - `preservation`: does the proposal remove a user-visible feature/test/endpoint/control
91
+ (`cutting`), change a contract or observable behavior for a consumer (`breaking`), or keep
92
+ behavior identical (`preserving`)?
93
+ - `fix_eligibility`: `auto` only for mechanical, behavior-preserving, checkable changes;
94
+ `confirm` when an operator answer is needed; `suggest` for report-only plans.
95
+ - A `cutting`/`breaking` finding is never below P2 and never `auto`.
96
+
97
+ ## Phase 2 — Merge
98
+
99
+ 1. Dedupe by `(evidence file, line span, rung)`: identical span + rung from two lenses is one
100
+ finding — keep the higher severity, keep the first lens's `id` (renumber gaps afterward).
101
+ 2. Rank P1 → P4 (then focus order tests, ui, api, architect).
102
+ 3. Re-assert scope: every `evidence[].file` is inside `--scope`; drop or fail findings that are
103
+ not (stop rule 4).
104
+
105
+ ## Phase 3 — Gate
106
+
107
+ | Gate | Class | `--auto` | Headless (`--agent auto\|name`) |
108
+ | --- | --- | --- | --- |
109
+ | Confirm resolved scope + lens set | objective | skipped | skipped |
110
+ | Confirm apply batch of `auto` findings | objective | skipped | skipped |
111
+ | Each `cutting` / `breaking` finding | **taste** | **pauses** | no pause: `status: deferred`, listed as SUGGEST in the report |
112
+ | Baseline check red | hard stop | stop, report | stop, report |
113
+
114
+ - **Objective gates** ask yes/no about facts (scope, lens set, batch contents); `--auto` answers
115
+ them from the resolved state.
116
+ - **Taste gates** — every `cutting` or `breaking` finding — **always pause for an explicit
117
+ operator answer** in an interactive session, regardless of `--auto`. Under a headless executor
118
+ there is no operator to ask: set `status: deferred` and list the finding as SUGGEST in the
119
+ report. A headless run never applies a cutting/breaking finding.
120
+ - A declined taste gate marks the finding `status: rejected` (operator "no") — never silently
121
+ dropped.
122
+
123
+ ## Phase 4 — Apply (fix ladder)
124
+
125
+ Execute `--fix` per [fix-ladder.md](./references/fix-ladder.md): green baseline first, one finding
126
+ at a time, re-run `--check` after each, revert only the failed finding's own edits (reverse-apply
127
+ its hunks — never a file-level checkout; see fix-ladder.md) and mark
128
+ `reverted`. `none` skips this phase entirely (both artifacts still written).
129
+
130
+ ## Phase 5 — Report
131
+
132
+ Run the structural check from [finding-schema.md](./references/finding-schema.md), then write both
133
+ artifacts:
134
+
135
+ 1. `.spur/run/<run-id>-refactor-findings.json` — the bare findings array (validated schema).
136
+ 2. `.spur/run/<run-id>-refactor-report.md` — containing:
137
+ - the resolved **lens set** (and per-file counts for `auto`),
138
+ - a **P1–P4 findings table** with `file:line`,
139
+ - a **preservation summary** (preserving / cutting / breaking counts),
140
+ - **applied / reverted / deferred** lists (plus rejected when taste gates declined findings).
141
+
142
+ `<run-id>` is the enclosing pipeline run id when invoked inside one, otherwise
143
+ `refactor-<yyyymmdd>-<hhmmss>`. `--fix none` still writes both artifacts.
144
+
145
+ ## Cheaper executor — minimum reading
146
+
147
+ A reduced-context executor may run this skill with exactly these files:
148
+
149
+ 1. this `SKILL.md` (phases, gates, stop rules),
150
+ 2. `references/finding-schema.md` (fields, severity map, structural check),
151
+ 3. `references/fix-ladder.md` (apply loop, revert rule),
152
+ 4. `references/focus-detection.md` (globs, report-before-run),
153
+ 5. each selected lens's `## Spur contract` section only.
154
+
155
+ Do not skip the structural check or the stop rules; everything else is compressible.
@@ -0,0 +1,74 @@
1
+ # Refactor finding schema
2
+
3
+ Every finding produced by a taste lens is normalized into one shared object before the coordinator
4
+ merges, gates, or applies anything. The findings artifact is a **bare JSON array** of these objects
5
+ at `.spur/run/<run-id>-refactor-findings.json`, machine-checked by
6
+ [`refactor-finding.schema.json`](./refactor-finding.schema.json) (JSON Schema draft-07).
7
+
8
+ Authority: `docs/design/dev-refactor-command.md` §4 (fields) and §5 (severity map).
9
+
10
+ ## Fields
11
+
12
+ | Field | Type | Values / notes |
13
+ | --- | --- | --- |
14
+ | `id` | string | `RF-<focus>-<nnn>` (e.g. `RF-api-001`) |
15
+ | `focus` | enum | `api` `architect` `tests` `ui` — the lens that produced the finding |
16
+ | `severity` | enum | `P1` `P2` `P3` `P4` (see the map below; `P0` does not exist in this schema) |
17
+ | `rung` | string | lens-native rung kept verbatim (`A0–A7`, `T0–T7`, api compatibility class, ui pass name) |
18
+ | `title` | string | one line |
19
+ | `evidence` | `{file, line}[]` | at least one `file:line` inside `--scope` |
20
+ | `preservation` | enum | `preserving` (behavior identical) · `cutting` (a user-visible feature/test/endpoint/control is removed) · `breaking` (contract or behavior changes for a consumer) |
21
+ | `fix_eligibility` | enum | `auto` (mechanical, behavior-preserving, checkable) · `confirm` (needs an operator answer) · `suggest` (report only) |
22
+ | `proposal` | string | what to change, imperative |
23
+ | `verify` | string | command or check that proves the fix (defaults to the `--check` command) |
24
+ | `status` | enum | `open` `applied` `reverted` `deferred` `rejected` |
25
+
26
+ Severity authority: `plugins/sp/agents/super-reviewer.md` (P1 blocker · P2 major · P3 minor ·
27
+ P4 advisory). The map below is the only place lens-native severities are translated.
28
+
29
+ ## Lens-native → P1–P4 severity map
30
+
31
+ | Lens | Native scale | → P1 (blocker) | → P2 (major) | → P3 (minor) | → P4 (advisory) |
32
+ | --- | --- | --- | --- | --- | --- |
33
+ | tests | P0–P3 | P0 | P1 | P2 | P3 |
34
+ | ui | P0–P3 | P0 | P1 | P2 | P3 |
35
+ | architect | A-ladder + 7-axis scores | any axis ≤1 with a correctness/safety consequence | axis ≤2 or A5–A7 seam problems | A3–A4 | A0–A2, ADR candidates, deferred questions |
36
+ | api | compatibility class | `breaking` change already shipped or contract ambiguity that corrupts data | `risky` inconsistency across ≥2 endpoints | `additive` cleanups | naming/docs |
37
+
38
+ Rule (structural, enforced by the check below): a `cutting` or `breaking` finding is **never below
39
+ P2** and **never `fix_eligibility: auto`**.
40
+
41
+ ## Structural check (no new dependency)
42
+
43
+ Run this documented `bun -e` snippet before writing the report — it asserts required keys and enum
44
+ membership and fails loudly on the two hard rules above (it must reject `severity: P0` and any
45
+ `cutting`/`breaking` finding marked `fix_eligibility: auto`):
46
+
47
+ ```bash
48
+ bun -e '
49
+ const f = require("fs");
50
+ const FINDINGS = JSON.parse(f.readFileSync(process.argv[process.argv.length - 1], "utf8"));
51
+ const REQ = ["id", "focus", "severity", "rung", "title", "evidence", "preservation", "fix_eligibility", "proposal", "verify", "status"];
52
+ const ENUM = {
53
+ focus: ["api", "architect", "tests", "ui"],
54
+ severity: ["P1", "P2", "P3", "P4"],
55
+ preservation: ["preserving", "cutting", "breaking"],
56
+ fix_eligibility: ["auto", "confirm", "suggest"],
57
+ status: ["open", "applied", "reverted", "deferred", "rejected"],
58
+ };
59
+ if (!Array.isArray(FINDINGS)) throw new Error("findings artifact must be a bare JSON array");
60
+ for (const x of FINDINGS) {
61
+ const id = x.id ?? "(no id)";
62
+ for (const k of REQ) if (!(k in x)) throw new Error(`${id}: missing required key ${k}`);
63
+ for (const [k, vals] of Object.entries(ENUM)) if (!vals.includes(x[k])) throw new Error(`${id}: ${k}=${JSON.stringify(x[k])} not in ${vals.join("|")}`);
64
+ if (!Array.isArray(x.evidence) || x.evidence.length < 1 || !x.evidence.every((e) => e && typeof e.file === "string" && e.file.length > 0 && Number.isInteger(e.line) && e.line >= 1)) throw new Error(`${id}: evidence must be a non-empty [{file, line}] array`);
65
+ if ((x.preservation === "cutting" || x.preservation === "breaking") && x.severity !== "P1" && x.severity !== "P2") throw new Error(`${id}: cutting/breaking is never below P2`);
66
+ if ((x.preservation === "cutting" || x.preservation === "breaking") && x.fix_eligibility === "auto") throw new Error(`${id}: cutting/breaking is never fix_eligibility auto`);
67
+ }
68
+ console.log(`refactor-findings: ${FINDINGS.length} finding(s) structurally valid`);
69
+ ' .spur/run/<run-id>-refactor-findings.json
70
+ ```
71
+
72
+ The coordinator runs this check after mapping and again after every status change, before the
73
+ report is written. A failed check is a hard stop — the artifact is not written and the run reports
74
+ the failure instead of applying anything.
@@ -0,0 +1,52 @@
1
+ # Fix ladder and eligibility
2
+
3
+ Authority: `docs/design/dev-refactor-command.md` §6. The ladder ranks what may be applied
4
+ mechanically versus what needs an operator answer. It reuses the repository severity meaning
5
+ (P1 blocker / P2 major — see [finding-schema.md](./finding-schema.md)) so `--fix blockers-first`
6
+ keeps the glossary sense.
7
+
8
+ ## Rungs
9
+
10
+ | Rung | Example | `preservation` | `fix_eligibility` |
11
+ | --- | --- | --- | --- |
12
+ | Rename / move / dedupe / inline with identical behavior | A3 consolidate, T3 fixture cleanup, ui token normalization | preserving | `auto` |
13
+ | Add missing test, contract field, a11y attribute | T4, api additive, ui P3 | preserving | `auto` |
14
+ | Remove dead code with zero references | A1 direct removal proven dead | preserving | `auto` only when a reference search finds no caller; else `confirm` |
15
+ | Remove a test, endpoint, control, or code path with callers | T1, A1/A2 live, api removal, ui control removal | cutting | `confirm` |
16
+ | Change a contract or observable behavior | api breaking, A5–A7 seam moves | breaking | `confirm` (or `suggest` when multi-task) |
17
+ | Architectural migration plan | A6–A7, ADR candidates | — | `suggest` |
18
+
19
+ Hard rules:
20
+
21
+ - An `auto` fix **never deletes or weakens a test** (no skipped assertions, no loosened
22
+ expectations, no deleted cases).
23
+ - `cutting` and `breaking` findings are **never `auto`** and **never below P2**.
24
+
25
+ ## Apply policies (`--fix`)
26
+
27
+ | Policy | Meaning |
28
+ | --- | --- |
29
+ | `none` (default) | Write both artifacts, perform no edit. |
30
+ | `blockers-first` | Apply P1/P2 findings with `fix_eligibility: auto`. |
31
+ | `all` | Apply every `auto` finding, then queue every `confirm` finding for the taste gate. |
32
+
33
+ ## Apply loop (mirrors `sp:code-simplification` / `dev-simplify`: test-after-each, revert on regression)
34
+
35
+ 1. **Green baseline first.** Run the `--check` command before any edit. A red baseline is a hard
36
+ stop: write the report, apply nothing.
37
+ 2. **One finding at a time.** Apply a single finding (highest severity first: P1 → P4), limited to
38
+ the finding's evidence files.
39
+ 3. **Re-run `--check`.**
40
+ - Passes → mark the finding `status: applied`, continue with the next.
41
+ - Fails → revert **only that finding's own edits**: reverse-apply the exact hunks the finding
42
+ introduced (keep the finding's diff; `git apply -R`, or re-edit the specific lines). Never
43
+ `git checkout -- <file>` a whole evidence file — a later finding may share that file with an
44
+ earlier **applied** finding, and a file-level checkout would revert that applied work too.
45
+ Mark the finding `status: reverted`, continue with the next. Never revert another finding's
46
+ work.
47
+ 4. After the last finding: re-run the structural check on the findings artifact, then write the
48
+ report (see `SKILL.md` phase 5).
49
+
50
+ `confirm` findings in the `all` policy follow the taste gate: applied only after an explicit
51
+ operator `yes` (see the gate matrix in `SKILL.md`). A declined finding is marked `status: rejected`
52
+ or `status: deferred` (headless) per the operator answer — never silently dropped.
@@ -0,0 +1,44 @@
1
+ # Focus auto-detection
2
+
3
+ Authority: `docs/design/dev-refactor-command.md` §8. Lens selection is **deterministic globs, not
4
+ model judgment**, so a misroute is auditable: re-running detection on the same tree yields the same
5
+ set.
6
+
7
+ ## Classification
8
+
9
+ First matching table row per file; the lens set is the **union** across all files in `--scope`.
10
+
11
+ | Order | Glob | Lens |
12
+ | --- | --- | --- |
13
+ | 1 | `**/tests/**`, `**/*.test.*`, `**/*.spec.*`, `**/__tests__/**` | tests |
14
+ | 2 | `apps/web/**`, `**/*.astro`, `**/*.tsx`, `**/*.jsx`, `**/*.css`, `**/*.vue` | ui |
15
+ | 3 | `packages/contracts/**`, `**/routes/**`, `**/openapi*`, `**/*.proto`, `**/*.graphql`, `apps/cli/src/commands/**`, `apps/server/src/**` | api |
16
+ | 4 | anything else (fallback) | architect |
17
+
18
+ Rules:
19
+
20
+ - Evaluate rows in order; a file matching an earlier row is never classified by a later one
21
+ (a `*.test.tsx` file is a **tests** file, not ui).
22
+ - Every file in scope resolves to exactly one lens; the scope set is the union of resolved lenses.
23
+ - An empty scope set (no files matched anything) collapses to the `architect` fallback for the
24
+ whole scope only when scope itself is non-empty; an empty scope is a resolve-phase error.
25
+
26
+ ## `--focus` flag
27
+
28
+ | Value | Behavior |
29
+ | --- | --- |
30
+ | `auto` (default) | Run detection above and use its lens set. |
31
+ | single lens (`api` \| `architect` \| `tests` \| `ui`) | Run only that lens. |
32
+ | comma list (`api,tests`) | Run exactly the listed lenses, in the operator's order. |
33
+
34
+ ## Report before run
35
+
36
+ The resolved lens set **must be reported before any lens runs** — one line naming the chosen
37
+ lenses and, for `auto`, the per-file counts that selected them, e.g.:
38
+
39
+ ```text
40
+ focus=auto → lenses: tests (14 files), api (3 files)
41
+ ```
42
+
43
+ Under `--auto` this line is still written (to the report and the session output); what `--auto`
44
+ skips is only the *interactive* confirmation of scope and lens set, never the report.
@@ -0,0 +1,95 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://spur.gobing.ai/schemas/refactor-finding.schema.json",
4
+ "title": "Refactor findings artifact (bare array of findings, design H13 §4)",
5
+ "description": "One JSON object per refactoring finding; the artifact written to .spur/run/<run-id>-refactor-findings.json is a bare array of these objects.",
6
+ "type": "array",
7
+ "items": {
8
+ "type": "object",
9
+ "additionalProperties": false,
10
+ "required": [
11
+ "id",
12
+ "focus",
13
+ "severity",
14
+ "rung",
15
+ "title",
16
+ "evidence",
17
+ "preservation",
18
+ "fix_eligibility",
19
+ "proposal",
20
+ "verify",
21
+ "status"
22
+ ],
23
+ "properties": {
24
+ "id": {
25
+ "type": "string",
26
+ "pattern": "^RF-(api|architect|tests|ui)-[0-9]{3}$",
27
+ "description": "RF-<focus>-<nnn>, e.g. RF-api-001."
28
+ },
29
+ "focus": {
30
+ "type": "string",
31
+ "enum": ["api", "architect", "tests", "ui"],
32
+ "description": "The lens that produced the finding."
33
+ },
34
+ "severity": {
35
+ "type": "string",
36
+ "enum": ["P1", "P2", "P3", "P4"],
37
+ "description": "Repository-authority severity after the lens-native → P1–P4 map (finding-schema.md §5). P0 is outside the map."
38
+ },
39
+ "rung": {
40
+ "type": "string",
41
+ "description": "Lens-native rung kept verbatim: architect A0–A7, tests T0–T7, api compatibility class, ui pass name."
42
+ },
43
+ "title": {
44
+ "type": "string",
45
+ "minLength": 1,
46
+ "description": "One line."
47
+ },
48
+ "evidence": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "items": {
52
+ "type": "object",
53
+ "additionalProperties": false,
54
+ "required": ["file", "line"],
55
+ "properties": {
56
+ "file": {
57
+ "type": "string",
58
+ "minLength": 1
59
+ },
60
+ "line": {
61
+ "type": "integer",
62
+ "minimum": 1
63
+ }
64
+ }
65
+ },
66
+ "description": "At least one file:line inside --scope."
67
+ },
68
+ "preservation": {
69
+ "type": "string",
70
+ "enum": ["preserving", "cutting", "breaking"],
71
+ "description": "preserving: behavior identical; cutting: a user-visible feature/test/endpoint/control is removed; breaking: contract or behavior changes for a consumer."
72
+ },
73
+ "fix_eligibility": {
74
+ "type": "string",
75
+ "enum": ["auto", "confirm", "suggest"],
76
+ "description": "auto: mechanical, behavior-preserving, checkable; confirm: needs an operator answer; suggest: report only."
77
+ },
78
+ "proposal": {
79
+ "type": "string",
80
+ "minLength": 1,
81
+ "description": "What to change, imperative."
82
+ },
83
+ "verify": {
84
+ "type": "string",
85
+ "minLength": 1,
86
+ "description": "Command or check that proves the fix; defaults to the --check command."
87
+ },
88
+ "status": {
89
+ "type": "string",
90
+ "enum": ["open", "applied", "reverted", "deferred", "rejected"],
91
+ "description": "Lifecycle of the finding through the gate/apply loop."
92
+ }
93
+ }
94
+ }
95
+ }
@@ -134,14 +134,13 @@ single run-on paragraph on render, even though they look like separate items in
134
134
  "requirements": "- [ ] R1. <text>\n- [ ] R2. <text>\n- [ ] R3. <text>"
135
135
  ```
136
136
 
137
- `R1. <text>\nR2. <text>` (no marker) is the trap. `spur task check` **accepts** it — the L3
138
- R-numbering rule matches the bare `Rn.` token — so nothing fails, and the defect only surfaces later
139
- as an unreadable paragraph in Board preview. Do not rely on `check` to catch this. Keep the `Rn.`
137
+ `R1. <text>\nR2. <text>` and `- R1 — <text>` are the traps: `spur task check` reports
138
+ `L3.requirements-checkbox` and a bare run renders as one paragraph in Board preview. Keep the `Rn.`
140
139
  (period) token inside the marker so R-numbering still resolves; see the canonical rule in
141
140
  `sp:spur-dev` → `references/planning-workflow.md`.
142
141
 
143
142
  Applies to the other body fields too: `plan` as an ordered list (`1. …\n2. …`), `acceptance_criteria`
144
- as a fenced ```` ```gherkin ```` block, and any enumeration inside `background` or `design` as a
143
+ as `- [ ] AC1 — <feature scenario title without its R-number>` bullets (see § Idea-pipeline emission), and any enumeration inside `background` or `design` as a
145
144
  `- ` list.
146
145
 
147
146
  ### Design at create (default) vs `--skip-design`
@@ -207,20 +206,22 @@ scenarios are numbered in the **feature's** namespace. Both appear in a task's
207
206
 
208
207
  The rule:
209
208
 
210
- - **Scenarios covering the task's own requirements carry the task-local R-prefix** —
211
- `Scenario: R3 — <observable outcome>`. Tasks declaring `ac_numbering: task-local` in frontmatter
212
- get these cross-checked by `spur task check` (`L3.ac-requirement-coverage`): a requirement with no
213
- scenario, or a scenario citing a requirement that does not exist, is reported.
214
- - **Scenarios carried verbatim from the feature (for DD-09 traceability) carry NO R-prefix** — copy
215
- the title text only. `normalizeTitle` (`packages/domain/src/bdd/coverage.ts:58`) strips `R\d+`
216
- before matching, so the prefix is invisible to feature coverage anyway; dropping it keeps the
217
- feature's number from being read as a task requirement id. Verified empirically: removing the
218
- prefix from a carried scenario left the feature's orphan count unchanged.
209
+ - **Task AC items are numbered `AC1, AC2, …` (task-local), never `R<n>`** — `R<n>` is the
210
+ Requirements namespace, and two R-numberings in one file is how they get confused.
211
+ - **Bind a scenario to the task requirement it covers with `(req: R<n>)`** —
212
+ `Scenario: AC1 — <observable outcome> (req: R3)` (semicolons for several: `R1; R2`). Tasks
213
+ declaring `ac_numbering: task-local` get these cross-checked by `spur task check`
214
+ (`L3.ac-requirement-coverage`): a requirement with no scenario, or a scenario citing a requirement
215
+ that does not exist, is reported.
216
+ - **Scenarios carried from the feature (for DD-09 traceability) copy the title text after the
217
+ feature's `R<n> —`** — `- [ ] AC2 — <title>`. `normalizeTitle`
218
+ (`packages/domain/src/bdd/coverage.ts`) strips both `AC\d+` and `R\d+` before matching, so the
219
+ prefix is invisible to feature coverage; the feature's number is never read as a task id.
219
220
 
220
221
  **Legacy tasks are exempt.** Most existing tasks predate this and copied feature AC wholesale,
221
222
  carrying the feature's numbers. The coverage check is opt-in precisely so they emit nothing —
222
- absent `ac_numbering`, only DD-09 applies. Opting an old task in is a pure prefix renumber; it cannot
223
- break traceability. New tasks get `ac_numbering: task-local` from the templates automatically;
223
+ absent `ac_numbering`, only DD-09 applies. Legacy `Scenario: R<n> —` titles still bind by prefix, so
224
+ opting an old task in cannot break traceability. New tasks get `ac_numbering: task-local` from the templates automatically;
224
225
  `spur task update <wbs> --ac-numbering task-local` opts in an existing one.
225
226
 
226
227
  Edge-case scenarios may map to tasks, merge into a sibling, or be deferred. Record deferrals
@@ -523,7 +524,7 @@ The payload is a top-level JSON **array** (no `tasks` wrapper):
523
524
  "requirements": "- [ ] R1. Accept a title and an optional description on POST /tasks.\n- [ ] R2. Reject an empty title with a 400 and a reason.\n- [ ] R3. Allocate the task file through the CLI-gated write path.",
524
525
  "design": "Approach: POST /tasks via existing TaskService.create.\nRejected: ad-hoc SQL in handler.\nInvariants: CLI-gated corpus writes only.",
525
526
  "plan": "1. Contract\n2. Handler\n3. Tests",
526
- "acceptance_criteria": "Scenario: create succeeds\n Given a valid title\n When POST /tasks\n Then a task file is allocated"
527
+ "acceptance_criteria": "- [ ] AC1 — User can create a task with required fields"
527
528
  },
528
529
  {
529
530
  "name": "Implement task listing endpoint",
@@ -559,6 +560,12 @@ fields and normal default planning fills them from your analysis; the per-task r
559
560
  batch-create still deepens them when a task needs more detail. Validate locally against the
560
561
  schema before emitting.
561
562
 
563
+ **Pass the deterministic task check.** Each `acceptance_criteria` bullet must be an exact feature
564
+ scenario title (L4.uncovered-task-scenario); add task-local checks as prose after the bullets, not
565
+ as extra bullets. No section body may use `HITL`, `approval`/`approved`, `merged`/`merge event`,
566
+ `content-gate`, `GATED`, or `capstone` as standalone words (L4.gate-language) — say "operator
567
+ answer" / "accepted" instead, and keep enum values out of that list too.
568
+
562
569
  **The order sidecar.** Also emit the private task-order sidecar at
563
570
  `.spur/run/<runId>-idea-task-order.json`: a JSON array (one entry per batch item) of
564
571
  `{ name: <exact batch item name>, depends_on_names: [<batch item names>] }` declaring
@@ -47,8 +47,10 @@ Every scenario/item carries an `R1, R2, …` prefix:
47
47
 
48
48
  - **Sequential within a feature**, starting at R1.
49
49
  - **Stable forever.** Never renumber once tasks exist — tasks match AC by **normalized scenario
50
- title** (the `R<n> —` prefix is stripped on comparison), so renumbering around a title is safe
51
- but *rewording* a title breaks the coverage edge.
50
+ title** (the `R<n> —` prefix is stripped on comparison, as is the task side's `AC<n> —`), so
51
+ renumbering around a title is safe but *rewording* a title breaks the coverage edge.
52
+ - **`R<n>` is the feature's namespace.** Task AC items are numbered `AC<n>` (task-local) and copy
53
+ the feature title after their own prefix; task Requirements own `R<n>.` inside the task.
52
54
  - **One R-number = one scenario.** Don't split one requirement across scenarios under a single
53
55
  R-number; don't merge two requirements into one scenario.
54
56
 
@@ -58,6 +58,9 @@ spur feature create "Planning layer" --parent H # → H<n>
58
58
  spur feature create "Task CLI" --parent H1 # → H1<n>
59
59
  ```
60
60
 
61
+ `create --json` returns `{ ref: { kind, id, filePath, folder }, content }` — read the id from
62
+ `.ref.id`, not a top-level `.id`.
63
+
61
64
  To restructure, use `move` — never hand-edit an ID. `move <id> --parent <new>` re-parents the
62
65
  subtree and **cascade-renames** every descendant; omit `--parent` to lift it to a top-level group.
63
66
 
@@ -184,7 +187,9 @@ spur feature check --strict --json # warnings → failures
184
187
  ```
185
188
 
186
189
  The 4-layer validator (frontmatter, AC syntax, children-limit/structure, L4 traceability) emits its
187
- verdict and findings as JSON. **Query this, don't re-derive it** — the rules live in the CLI, never
190
+ verdict and findings as a JSON **array**, one entry per feature (`jq '.[0].pass'`, `.[0].findings[].code`),
191
+ like `spur task check --json`. Gherkin AC must keep its `Feature:` line (`L3.ac-bdd-error`) and
192
+ every `Scenario:` title is the identity key tasks reference verbatim. **Query this, don't re-derive it** — the rules live in the CLI, never
188
193
  restated as prose here. This is what `sp:spur-dev`'s feature-check gate loop runs.
189
194
 
190
195
  ## Status sync - `sync`
@@ -137,8 +137,8 @@ runner (see [validation-and-extension.md](validation-and-extension.md)).
137
137
  Order matters for both guards and conditions: **the first that passes wins.** Put the discriminating
138
138
  guard before the unconditional fallback (`always` / no-guard edge). For multi-condition gates (doctor
139
139
  + task check, quality gate + attempt cap), prefer a **soft probe** shell that writes PASS|FAIL and
140
- always exits 0, then branch with ordered status-file guards — see shipped `basic.yaml` /
141
- `task-pipeline.yaml` (more reliable than `action-ok` alone when more than one condition decides the edge).
140
+ always exits 0, then branch with ordered status-file guards — see shipped
141
+ `task-pipeline.yaml` / `wrapup-pipeline.yaml` (more reliable than `action-ok` alone when more than one condition decides the edge).
142
142
 
143
143
  ## Template variables
144
144
 
@@ -28,9 +28,9 @@ Authored workflows default to a project-local directory, grouped by purpose:
28
28
  ```
29
29
 
30
30
  A `--file <path>` argument overrides the default. Keep one workflow per file, named for what it does
31
- (`approval.yaml`, `import-file.yaml`), not for its mode. The canonical example
32
- (`basic.yaml`) lives here; copy real schema shapes from it rather than from a
33
- half-remembered snippet.
31
+ (`approval.yaml`, `import-file.yaml`), not for its mode. Copy real schema shapes from a
32
+ retained definition such as `task-pipeline.yaml` rather than from a half-remembered
33
+ snippet.
34
34
 
35
35
  ## Sub-procedure: mode-selection gate
36
36
 
@@ -95,7 +95,7 @@ and orders capabilities, it does not contain them** (ADR-069).
95
95
  between them are one judgment step.
96
96
  - [ ] **Soft status-file probe over repeated probing.** Run the expensive check once in an action
97
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
98
+ guards that read that file. One subprocess instead of one per branch — the
99
99
  `task-pipeline.yaml` quality-gate idiom.
100
100
  - [ ] **Order guards cheapest-discriminating-first.** The first passing guard wins, so a `test -f`
101
101
  ahead of a `spur … --json` parse skips the expensive call on the common path.
@@ -62,7 +62,7 @@ ordered guards, and may loop back on itself (e.g. implement → check → fix
62
62
  | Node typing | none (states are untyped) | `type: action\|gate\|parallel\|decision` |
63
63
  | Required keys | `name, initialState, states, transitions` | `kind, name, initialNode, nodes, edges` |
64
64
  | `kind` field | optional (defaults to state-machine) | **required** — `kind: transition-flow` |
65
- | Canonical example | implement→check→fix loop (`basic.yaml` — soft status-file probe + bounded fixall) | read→validate→transform→write pipeline |
65
+ | Canonical example | implement→check→fix loop (`task-pipeline.yaml` — quality-gate hop with bounded fixall) | read→validate→transform→write pipeline |
66
66
 
67
67
  **Heuristic:** loops / retries / one-active-state → **state-machine**; pipeline / fan-out / action-per-node → **transition-flow**.
68
68
 
@@ -95,7 +95,7 @@ Use this skill to:
95
95
 
96
96
  The skill's logic divides by **whether the LLM adds value**:
97
97
 
98
- - **Direct CLI** (`validate`, `run`, `list`, `trace`, `continue`, `cancel`, `clean`) — deterministic,
98
+ - **Direct CLI** (`validate`, `run`, `list`, `trace`, `progress`, `continue`, `cancel`, `clean`) — deterministic,
99
99
  single-verb commands. Run them straight. A slash-command wrapper would only forward flags and add
100
100
  drift; **there is no command for these — use the CLI**. The skill still drives them in natural
101
101
  language (interpreting a failed validate, reading a run trace).
@@ -113,6 +113,7 @@ The skill's logic divides by **whether the LLM adds value**:
113
113
  | `clean` | `spur workflow clean` (CLI) | `[--older-than <min>] [--force] [--logs] [--dry-run]` | Bulk-finalize stale `running`/`pending` runs as failed **and** reclaim retained run logs older than `workflow.logRetentionDays` (30d default) |
114
114
  | `list` | `spur workflow list` (CLI) | — | Available workflow **YAML definition files** (not run records) |
115
115
  | `trace` | `spur workflow trace` (CLI) | `[run-id] [--workflow <n>] [--status <s>] [--since <iso>] [--last <n>] [--follow] [--poll <ms>] [--output]` | Run history list or per-run timeline |
116
+ | `progress` | `spur workflow progress` (CLI) | `<run-id>` | The `projectWorkflowProgress` projection for that run — current state, per-action attempts, candidate next transitions, diagnostics. Read-only; the verb renders, `packages/app` derives. Unknown run id exits 1 with `Run <id> not found.` |
116
117
  | `add` | agent procedure | `"<nl-description>" [--kind <state-machine\|transition-flow>] [--file <path>]` | **Mode chosen (confirmed)** → first reconciled against existing workflows (extend an existing flow rather than duplicate) → YAML authored in real schema shape → **validated AND dry-run** (reaches the expected terminal state) → [add](workflows/operations.md#add) |
117
118
  | `refine` | agent procedure | `<workflow-file> [--intent "<goal>"] [--dry-run]` | Smallest change meeting the intent, re-validated and re-dry-run; `--dry-run` emits a diff only → [refine](workflows/operations.md#refine) |
118
119
 
@@ -262,6 +263,7 @@ spur workflow cancel <run-id> [--json]
262
263
  spur workflow clean [--older-than <minutes>] [--force] [--logs] [--dry-run] [--json]
263
264
  spur workflow list [--json]
264
265
  spur workflow trace [run-id] [--workflow <name>] [--status <s>] [--since <iso>] [--last <n>] [--follow] [--poll <ms>] [--output] [--json]
266
+ spur workflow progress <run-id> [--json]
265
267
  ```
266
268
 
267
269
  ### `show` - project a definition
@@ -359,7 +361,7 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
359
361
  (transition-flow) wins. Order the specific case before the fallback. Simple shell checks can use
360
362
  `action-ok` before an unconditional retry edge; multi-condition gates prefer a **soft status-file
361
363
  probe** (always exit 0) with ordered shell guards for PASS / FAIL / exhausted (see
362
- `basic.yaml` and `task-pipeline.yaml` quality-gate hop).
364
+ `task-pipeline.yaml`'s quality-gate hop).
363
365
  4. **`env.allow` is an allowlist.** `${env.X}` resolves only if `X` is listed under `env.allow`;
364
366
  otherwise it resolves empty. A workflow that "loses" an environment value usually forgot to allow it.
365
367
  5. **Extensions are fail-closed.** The CLI loads YAML-declared extension modules itself
@@ -400,10 +402,9 @@ the workflow's actions (`shell`, custom runners) do that; this skill builds and
400
402
  custom action/guard runners, the trust-gated extension loader, and CLI-vs-library capability gaps.
401
403
  - `@gobing-ai/ts-dual-workflow-engine` README — authoritative library reference (both drivers,
402
404
  RunLifecycle, persistence, the full event map, every built-in capability).
403
- - `basic.yaml` — the canonical state-machine implement→soft-check→fixall loop
404
- (status-file branching + `qualityGateMaxFixAttempts`); copy real shapes from here.
405
- - `task-pipeline.yaml` — full production pipeline (precheck, quality gate, HITL,
406
- verify, record) when you need the complete reliability pattern set.
405
+ - `task-pipeline.yaml` — the canonical state-machine implement→soft-check→fixall loop and
406
+ full production pipeline (status-file branching, `qualityGateMaxFixAttempts`, quality gate,
407
+ HITL, verify, record); copy real shapes from here.
407
408
 
408
409
  ## Platform Notes
409
410