@gobing-ai/spur 0.4.9 → 0.4.11

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 (89) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/rules/README.md +1 -1
  3. package/config/rules/boundary/env-var-hygiene.yaml +0 -1
  4. package/config/rules/boundary/sp-script-placement.yaml +3 -3
  5. package/config/rules/docs/sourced-api-claims.yaml +23 -96
  6. package/config/workflows/feature-verification.yaml +1 -9
  7. package/config/workflows/pr-review.yaml +72 -224
  8. package/config/workflows/task-pipeline.yaml +2 -2
  9. package/package.json +1 -1
  10. package/plugins/sp/agents/expert-spur.md +0 -1
  11. package/plugins/sp/commands/dev-changelog.md +1 -1
  12. package/plugins/sp/commands/dev-pr-review.md +27 -17
  13. package/plugins/sp/plugin.json +1 -1
  14. package/plugins/sp/skills/brainstorm/SKILL.md +12 -11
  15. package/plugins/sp/skills/brainstorm/agents/openai.yaml +1 -1
  16. package/plugins/sp/skills/brainstorm/examples/ideation-example.md +0 -4
  17. package/plugins/sp/skills/brainstorm/metadata.openclaw +1 -1
  18. package/plugins/sp/skills/brainstorm/references/workflows.md +4 -14
  19. package/plugins/sp/skills/code-verification/references/secu-review.md +7 -4
  20. package/plugins/sp/skills/history-anatomy/references/operations.md +3 -4
  21. package/plugins/sp/skills/history-anatomy/references/report-contract.md +2 -3
  22. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  23. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  24. package/plugins/sp/skills/parallel-execution/references/result-synthesis.md +9 -10
  25. package/plugins/sp/skills/pr-reviewing/SKILL.md +157 -210
  26. package/plugins/sp/skills/reverse-engineering/SKILL.md +4 -6
  27. package/plugins/sp/skills/session-review/SKILL.md +10 -26
  28. package/plugins/sp/skills/source-driven-development/SKILL.md +35 -31
  29. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  30. package/plugins/sp/skills/spur-cli/references/agent.md +5 -2
  31. package/plugins/sp/skills/spur-cli/references/builder.md +86 -13
  32. package/plugins/sp/skills/spur-cli/references/features.md +1 -1
  33. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +1 -1
  34. package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -6
  35. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +1 -1
  36. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +3 -2
  37. package/spur.js +1280 -417
  38. package/web/_astro/{BoardApp.CaNvQBD_.js → BoardApp.B5rTVNd_.js} +1 -1
  39. package/web/_astro/{BoardApp.C5jnkkKP.js → BoardApp.BMprmPRc.js} +141 -141
  40. package/web/_astro/{TaskDetail.DGKndhI8.js → TaskDetail.Ddsur8qd.js} +1 -1
  41. package/web/_astro/{arc.DHrwdg3G.js → arc.DLqVVBxP.js} +1 -1
  42. package/web/_astro/{architectureDiagram-3BPJPVTR.pvKpK1s5.js → architectureDiagram-3BPJPVTR.C-Pk3uAi.js} +1 -1
  43. package/web/_astro/{blockDiagram-GPEHLZMM.BQmRcZsS.js → blockDiagram-GPEHLZMM.BRO1i_Nm.js} +1 -1
  44. package/web/_astro/{c4Diagram-AAUBKEIU.CCLjHeH1.js → c4Diagram-AAUBKEIU.BU_cWu6e.js} +1 -1
  45. package/web/_astro/channel.Bs3Illhy.js +1 -0
  46. package/web/_astro/{chunk-2J33WTMH.DL6Yuoma.js → chunk-2J33WTMH.D2VXiQ2t.js} +1 -1
  47. package/web/_astro/{chunk-4BX2VUAB.CzlyEI7l.js → chunk-4BX2VUAB.BcHT772J.js} +1 -1
  48. package/web/_astro/{chunk-55IACEB6.B60EWxp1.js → chunk-55IACEB6.DhDtICmo.js} +1 -1
  49. package/web/_astro/{chunk-727SXJPM.c7v_fRLo.js → chunk-727SXJPM.BoKHp7gV.js} +1 -1
  50. package/web/_astro/{chunk-AQP2D5EJ.8BxLWdDc.js → chunk-AQP2D5EJ.Chu4JRpE.js} +1 -1
  51. package/web/_astro/{chunk-FMBD7UC4.DmKuxGFT.js → chunk-FMBD7UC4.wHdwKPlq.js} +1 -1
  52. package/web/_astro/{chunk-ND2GUHAM.1Zwd4mqV.js → chunk-ND2GUHAM.ffN-M1JB.js} +1 -1
  53. package/web/_astro/{chunk-QZHKN3VN.BvYLu-A1.js → chunk-QZHKN3VN.FRubwg4S.js} +1 -1
  54. package/web/_astro/{classDiagram-4FO5ZUOK.CEqRt0WU.js → classDiagram-4FO5ZUOK.c22tQsc_.js} +1 -1
  55. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CEqRt0WU.js → classDiagram-v2-Q7XG4LA2.c22tQsc_.js} +1 -1
  56. package/web/_astro/{cose-bilkent-S5V4N54A.C7JiUdj2.js → cose-bilkent-S5V4N54A.B9c6fiSj.js} +1 -1
  57. package/web/_astro/{cynefin-OW5HDTMX.B6mZ-1be.js → cynefin-OW5HDTMX.DrZsrUHl.js} +1 -1
  58. package/web/_astro/{dagre-BM42HDAG.BuryfDy6.js → dagre-BM42HDAG.DG1U4b3y.js} +1 -1
  59. package/web/_astro/{diagram-2AECGRRQ.dao39YQM.js → diagram-2AECGRRQ.CH15b-K3.js} +1 -1
  60. package/web/_astro/{diagram-5GNKFQAL.lnGZe4At.js → diagram-5GNKFQAL.BH1j7twR.js} +1 -1
  61. package/web/_astro/{diagram-KO2AKTUF.uWfhzEfT.js → diagram-KO2AKTUF.q2zCkbAj.js} +1 -1
  62. package/web/_astro/{diagram-LMA3HP47.ppq8-198.js → diagram-LMA3HP47.BLaaZnHV.js} +1 -1
  63. package/web/_astro/{diagram-OG6HWLK6.CKSCqDt5.js → diagram-OG6HWLK6.BcpRdQwI.js} +1 -1
  64. package/web/_astro/{erDiagram-TEJ5UH35.sFSf2yBN.js → erDiagram-TEJ5UH35.D7KBlqfM.js} +1 -1
  65. package/web/_astro/{flowDiagram-I6XJVG4X.BJQjKLbz.js → flowDiagram-I6XJVG4X.D_gmuRqg.js} +1 -1
  66. package/web/_astro/{ganttDiagram-6RSMTGT7.DAHKNj2N.js → ganttDiagram-6RSMTGT7.DzgWYniD.js} +1 -1
  67. package/web/_astro/{gitGraphDiagram-PVQCEYII.BlmzcW_O.js → gitGraphDiagram-PVQCEYII.576i3hm3.js} +1 -1
  68. package/web/_astro/index.DiC1a6lh.css +1 -0
  69. package/web/_astro/{infoDiagram-5YYISTIA.DGhjaqsi.js → infoDiagram-5YYISTIA.DMCclBiz.js} +1 -1
  70. package/web/_astro/{ishikawaDiagram-YF4QCWOH.gKIpgt6F.js → ishikawaDiagram-YF4QCWOH.9Kyca4SH.js} +1 -1
  71. package/web/_astro/{journeyDiagram-JHISSGLW.DG3tmP0R.js → journeyDiagram-JHISSGLW.chbdG-8q.js} +1 -1
  72. package/web/_astro/{kanban-definition-UN3LZRKU.CgHDSxkR.js → kanban-definition-UN3LZRKU.Bds54-e0.js} +1 -1
  73. package/web/_astro/{linear.BuGEpcuM.js → linear.D5ND9GN_.js} +1 -1
  74. package/web/_astro/{mermaid.core.DBVqkWBp.js → mermaid.core.D9N_PGbj.js} +4 -4
  75. package/web/_astro/{mindmap-definition-RKZ34NQL.D-kOcji9.js → mindmap-definition-RKZ34NQL.t0xop5vp.js} +1 -1
  76. package/web/_astro/{pieDiagram-4H26LBE5.BvGPwtZt.js → pieDiagram-4H26LBE5.DqgKh6hM.js} +1 -1
  77. package/web/_astro/{quadrantDiagram-W4KKPZXB.BKb_9FV6.js → quadrantDiagram-W4KKPZXB.DeuHVi2t.js} +1 -1
  78. package/web/_astro/{requirementDiagram-4Y6WPE33.Cx0fvD8v.js → requirementDiagram-4Y6WPE33.BdYFSG4W.js} +1 -1
  79. package/web/_astro/{sankeyDiagram-5OEKKPKP.fatULi0W.js → sankeyDiagram-5OEKKPKP.k1LPPETJ.js} +1 -1
  80. package/web/_astro/{sequenceDiagram-3UESZ5HK.ChHyKqco.js → sequenceDiagram-3UESZ5HK.DdyfpzoG.js} +1 -1
  81. package/web/_astro/{stateDiagram-AJRCARHV.BdUpKWyj.js → stateDiagram-AJRCARHV.DSEI8il0.js} +1 -1
  82. package/web/_astro/{stateDiagram-v2-BHNVJYJU.fmJxqIm5.js → stateDiagram-v2-BHNVJYJU.CoMnQRKo.js} +1 -1
  83. package/web/_astro/{timeline-definition-PNZ67QCA.BayDn5-V.js → timeline-definition-PNZ67QCA.DM1AHNm-.js} +1 -1
  84. package/web/_astro/{vennDiagram-CIIHVFJN.Bm2Aggwa.js → vennDiagram-CIIHVFJN.C-cJmVtl.js} +1 -1
  85. package/web/_astro/{wardleyDiagram-YWT4CUSO.DI75O-uG.js → wardleyDiagram-YWT4CUSO.D0xMzKPb.js} +1 -1
  86. package/web/_astro/{xychartDiagram-2RQKCTM6.DL90E7Q7.js → xychartDiagram-2RQKCTM6.BH5xJcEq.js} +1 -1
  87. package/web/index.html +2 -2
  88. package/web/_astro/channel.C7wG9RxA.js +0 -1
  89. package/web/_astro/index.BCY1JJHd.css +0 -1
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.4.9",
10
+ "version": "0.4.11",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -17,7 +17,7 @@ install or to ts-libs.
17
17
  | `surface` | `surface/` | CLI surface consistency (registerXxxCommand wiring, --json serialization) |
18
18
  | `migration` | `migration/` | Transitional helpers for the regex → `rg` evaluator move (`rg-dialect`) |
19
19
  | `ui` | `ui/` | Web UI seam boundaries (import seam, daisyUI class leak) |
20
- | `docs` | `docs/` | Sourced API/library claims in markdown — citation anchor + HIGH/MEDIUM/LOW level. Advisory (`info`), and deliberately **not** in a preset: see the file header for its calibration audit and the one-line promotion step. |
20
+ | `docs` | `docs/` | Sourced API/library claims in markdown — same-line citation anchor (no confidence level, ADR-138). Advisory (`info`), and deliberately **not** in a preset: see the file header for its calibration audit. |
21
21
 
22
22
  ## Presets
23
23
 
@@ -13,7 +13,6 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
13
13
  # gateway — because `superskill install` bundles them on targets with no
14
14
  # node_modules, where any `@gobing-ai/*` import fails ("Bundle failed", task
15
15
  # 0669). It mirrors the upstream semantics and is the one exempt file.
16
- # The `superskill script convert` `.mjs` twins remain self-contained by inlining.
17
16
  # - `apps/web` uses `import.meta.env` (Vite browser plane) — not process env, never matches.
18
17
  # - Which variables exist and who consumes them is documented in `.env.example`.
19
18
  # (The former per-var lease table moved there; the rule now enforces the funnel,
@@ -2,11 +2,11 @@ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
2
  # [boundary] ADR-130 script placement contract (task 1000). Runs the fast
3
3
  # --placement-only mode of the relocated script-contract-check on every
4
4
  # recommended-pre-check; SKIPs outside the Spur source repo (same pattern as
5
- # strict/rule-files-structural.yaml SKIP on missing yq). The full twin check
6
- # stays in spur-check-feature.
5
+ # strict/rule-files-structural.yaml SKIP on missing yq). The full hook-only
6
+ # layout check (`bun run script-contract-check`) stays in spur-check-feature.
7
7
  rules:
8
8
  - id: sp-script-placement
9
- description: "Plugins/sp scripts must honor the ADR-130 placement contract (glue budget, no db imports, no corpus parsing, no noun clash, ADR-129 hook layout); any finding fails (task 1213 retired the baseline ledger)."
9
+ description: "plugins/sp and scripts/commands must honor the ADR-130 placement contract (hook entry budget, no DB driver or corpus parsing in plugin scripts/hooks/lib, no raw DB driver in scripts/commands, no noun clash, ADR-129 hook layout); any finding fails, no baseline."
10
10
  severity: error
11
11
  evaluator:
12
12
  type: exit-code
@@ -1,51 +1,18 @@
1
1
  $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
- # Sourced-claims rules (docs corpus) — API/library claims in markdown must be
3
- # verifiable. A bare version string or a time-anchored API claim ("deprecated
4
- # since", "added in 0.5.x") on a line with no citation anchor (http(s) URL or a
5
- # repo path:line reference) is an unverifyable claim: readers cannot tell
6
- # measured fact from model memory. The deterministic scan is line-level and
7
- # heuristic — code fences and command examples can legitimately carry bare
8
- # versions, so severity is info (advisory visibility; the recommended-pre-check
9
- # gate runs --fail-on warning, so this rule surfaces findings for review without
10
- # reddening gates — changed-file enforcement happens in SECUA review).
2
+ # Sourced-claims rules (docs corpus) — an external API/library claim in markdown
3
+ # (a bare x.y.z version, or "deprecated since / added in / removed in <v>") must carry
4
+ # a same-line citation anchor: an http(s) URL or a repo path:line. Contract owned by
5
+ # sp-source-driven-development; changed-file enforcement is SECUA review.
11
6
  #
12
- # Calibration audit (2026-10-12, `spur rule run --file … --json`, unsuppressed):
13
- # the category was pulled from `recommended-pre-check` on 2026-10-08 for "finding
14
- # noise" — 593 findings. The audit split them 580 `cited-version-strings` /
15
- # 13 `cited-api-claims`, and 579 of the 580 were false positives on two shapes
16
- # that are not external claims at all: a document's own version
17
- # (`docs/00_ADR.md:5` → `version: 1.69.0`), a section number in a heading
18
- # (`docs/04_DESIGN.md:116` → `### 1.0.1 Shared option registry`) and a
19
- # `§`-numbered cross-reference (`docs/00_ADR.md:1118` → `§1.2.1`). All three are
20
- # excluded by the pattern below — the first two on the line's shape, the third on
21
- # the token itself. The remaining source was a stale exclude list that stopped at
22
- # `docs/tasks4/` while the active corpus folder is `docs/tasks5/`, which alone
23
- # produced 226 findings; it is now one `docs/tasks*/**` glob. 593 → 330 after those
24
- # two fixes, then 277 once the `§` shape was excluded too — the residual are
25
- # version-pinned package claims that genuinely lack a same-line anchor, and they
26
- # stay visible rather than suppressed.
7
+ # Severity is info and the category is deliberately NOT in any preset: the scan is
8
+ # line-level, fires inside code fences, and its residual is a review surface, not a
9
+ # gate (2026-10-12 audit: 593 → 277 findings after the shape/`§`/excludes fixes; the
10
+ # 2026-10-16 widening admitted only verbs whose shape vouches for a version).
11
+ # Re-measure with `spur rule run --file … --json` before widening the vocabulary.
27
12
  #
28
- # The semantic contract — every external API/library claim carries a source
29
- # citation (URL or path:line) AND a confidence level (HIGH/MEDIUM/LOW) — is
30
- # owned by the sp-source-driven-development skill and verified in SECUA review
31
- # (code-verification secu-review checklist). These rules are its deterministic
32
- # backstop: the three `cited-*` rules cover the citation half, and
33
- # `cited-api-claims-confidence` covers the level half.
34
- # `cited-high-confidence` covers the HONESTY half — the level is a claim like
35
- # any other, so the one level that asserts "verified this session" must show the
36
- # source it was verified against. MEDIUM (recalled, plausibly stale) and LOW
37
- # (memory only) require no anchor by contract and are deliberately not flagged.
38
- # All are severity `info`,
39
- # and the category is deliberately NOT in `recommended-pre-check` — it surfaces
40
- # findings for review without reddening gates. The 2026-10-12 audit below settled
41
- # the promotion question: the residual findings stay a review-surface concern
42
- # (SECUA checklist), not a gate one — do not add `- docs` to that preset.
43
- #
44
- # Fenced-code behavior (measured 2026-10-16, `spur rule run --file … --json` over a
45
- # probe doc): code fences are NOT skipped by the regex evaluator — every rule here
46
- # fires on a matching line inside a fence. The earlier note claiming fenced lines
47
- # were "out of regex reach" was wrong; fence noise is one more reason these stay
48
- # `info` and ad hoc rather than gated.
13
+ # Citation only — no HIGH/MEDIUM/LOW level rule (ADR-138, task 1221): a model can
14
+ # write HIGH as easily as a claim, so the level verified nothing. The anchor is the
15
+ # evidence; do not reintroduce a level half.
49
16
  include:
50
17
  - "docs/**/*.md"
51
18
  - "plugins/sp/skills/**/references/**/*.md"
@@ -58,6 +25,11 @@ exclude:
58
25
  # fallback strips the `*` down to `docs/tasks/` — which is why that form
59
26
  # silently excluded nothing (measured 2026-10-12).
60
27
  - "docs/tasks*/**"
28
+ # Dated working records — see the "Historical-record excludes" note above.
29
+ - "docs/report*/**"
30
+ - "docs/dogfood/**"
31
+ - "docs/plans/**"
32
+ - "docs/analysis/**"
61
33
  - "drizzle/**"
62
34
  - "**/node_modules/**"
63
35
  rules:
@@ -66,8 +38,8 @@ rules:
66
38
  A markdown line asserting a version string (x.y.z) without a citation
67
39
  anchor on the same line — http(s) URL or repo path reference like
68
40
  packages/app/src/foo.ts:123 — is unverifyable. Add the anchor, or drop
69
- the version if it is not load-bearing. Confidence level (HIGH/MEDIUM/LOW)
70
- is part of the claim contract; see sp-source-driven-development.
41
+ the version if it is not load-bearing; contract in
42
+ sp-source-driven-development.
71
43
  severity: info
72
44
  evaluator:
73
45
  type: regex
@@ -87,57 +59,12 @@ rules:
87
59
  - id: cited-api-claims
88
60
  description: >
89
61
  A markdown line making a time- or version-anchored external API claim
90
- ("deprecated since 1.2", "introduced in v3", "available since 0.9") must
91
- carry a citation anchor on the same line — URL or repo path:line. Add the
92
- source (URL or path:line) and the confidence level (HIGH/MEDIUM/LOW);
93
- contract in sp-source-driven-development.
94
- severity: info
95
- evaluator:
96
- type: regex
97
- config:
98
- mode: forbid
99
- pattern: "^(?!.*(?:https?://|(?:[\\w.-]+/)+[\\w.-]+:\\d+)).*\\b(?:deprecated (?:since|in|as of)|introduced in|added in|available since|stable since)\\b"
100
- - id: cited-api-claims-confidence
101
- description: >
102
- A markdown line making a time- or version-anchored external API claim
103
- ("deprecated since 1.2", "introduced in v3", "available since 0.9") must
104
- also state its confidence level — HIGH, MEDIUM or LOW — so a reader can
105
- tell a checked fact from model memory; contract in
106
- sp-source-driven-development. Pairs with `cited-api-claims`, which covers
107
- the citation half: a line missing both halves reports once per rule,
108
- deliberately, because each missing half is its own defect.
109
- severity: info
110
- evaluator:
111
- type: regex
112
- config:
113
- mode: forbid
114
- # Fires on a claim line carrying no uppercase level token. The verbs match
115
- # `cited-api-claims` exactly, so the two rules never disagree about which
116
- # lines are claims.
117
- pattern: "^(?!.*\\b(?:HIGH|MEDIUM|LOW)\\b).*\\b(?:deprecated (?:since|in|as of)|introduced in|added in|available since|stable since)\\b"
118
- - id: cited-high-confidence
119
- description: >
120
- A line stating HIGH confidence about an external API/library claim must
121
- carry a source citation anchor on the same line — URL or repo path:line.
122
- The claim contract (sp-source-driven-development) defines HIGH as
123
- "verified against the pinned version's docs/source THIS SESSION", so an
124
- uncited HIGH is the one level that cannot be honest: MEDIUM is recalled and
125
- plausibly stale, LOW is memory only, and both are explicitly allowed
126
- without a source (LOW must never be presented as fact — that stays a review
127
- judgment, not a regex). Scoped to the same claim verbs as
128
- `cited-api-claims`, so the two rules agree on which lines are claims: a
129
- decision record's own `Confidence:` grade (the brainstorm family) is a
130
- different contract and is deliberately out of scope. Pairs with
131
- `cited-api-claims-confidence`, which catches a claim line carrying no level
132
- at all.
62
+ ("deprecated since 1.2", "introduced in v3", "removed in 0.9") must carry
63
+ a citation anchor on the same line — URL or repo path:line; contract in
64
+ sp-source-driven-development.
133
65
  severity: info
134
66
  evaluator:
135
67
  type: regex
136
68
  config:
137
69
  mode: forbid
138
- # Fires when a claim line names HIGH but carries no anchor. The anchor
139
- # vocabulary is identical to its siblings (URL or `path:line`), so one
140
- # line cannot read as cited for one rule and uncited for another. The level
141
- # is asserted with a lookahead so either prose order matches — `(HIGH)` after
142
- # the claim (the usual form) and `HIGH — <claim>` both count.
143
- pattern: "^(?!.*(?:https?://|(?:[\\w.-]+/)+[\\w.-]+:\\d+))(?=.*\\bHIGH\\b).*\\b(?:deprecated (?:since|in|as of)|introduced in|added in|available since|stable since)\\b"
70
+ pattern: "^(?!.*(?:https?://|(?:[\\w.-]+/)+[\\w.-]+:\\d+)).*\\b(?:deprecated (?:since|in|as of)|introduced in|added in|available since|stable since|removed (?:in|since) [vV]?\\d|(?:since|as of) (?:[vV]\\d+\\.\\d+(?:\\.\\d+)?|\\d+\\.\\d+\\.\\d+))\\b"
@@ -52,14 +52,6 @@ vars:
52
52
  featureId: "X"
53
53
  spurBin: "spur"
54
54
  verificationCmd: "bun run spur-check-feature"
55
- # Operator-declared confidence level; empty derives it from the pass (HIGH when the selected
56
- # definition's own command exited 0 with an unchanged proof-input digest, MEDIUM under a caller
57
- # `--cmd` override, LOW otherwise). The completion boundary refuses a PASS carrying no level or
58
- # LOW unless the receipt records the acknowledgement below.
59
- confidence: ""
60
- # Operator acknowledgement of a LOW level. It rides the receipt, so the transition reads it from
61
- # the evidence rather than from whatever flags a later command happens to pass.
62
- ackLowConfidence: "false"
63
55
  __runId: ""
64
56
 
65
57
  states:
@@ -73,7 +65,7 @@ states:
73
65
  - kind: shell
74
66
  options:
75
67
  command: >-
76
- mkdir -p .spur/run; ACK=""; [ "$ackLowConfidence" = "true" ] && ACK="--ack-low-confidence"; $spurBin feature verify "$featureId" --run-id "$__runId" --confidence "$confidence" $ACK --no-logo || printf 'FAIL feature-verification failed closed — spur feature verify exited nonzero\n' > ".spur/run/$__runId-feature-verification.status"; exit 0
68
+ mkdir -p .spur/run; $spurBin feature verify "$featureId" --run-id "$__runId" --no-logo || printf 'FAIL feature-verification failed closed — spur feature verify exited nonzero\n' > ".spur/run/$__runId-feature-verification.status"; exit 0
77
69
 
78
70
  - id: done
79
71
  description: Terminal — repo-wide checks passed and the receipt is bound.
@@ -3,13 +3,14 @@
3
3
  # (initialState / states[].id / onEnter / top-level transitions), same family as
4
4
  # basic.yaml / task-pipeline.yaml. Seeded into projects by `spur init` (scaffold manifest).
5
5
  #
6
- # Shape: preflight → hygiene → precheck → push → ensure-pr → request → wait → collect → done
6
+ # Shape: check → precheck → open → review → done
7
7
  # (soft probes everywhere: red gates route to `failed`/`pending` via transitions,
8
8
  # never a raw lifecycle abort — same reliability contract as basic.yaml)
9
9
  #
10
- # This YAML is the SSOT for the review spine's state order and guards. Every state shells
11
- # out to `spur builder pr` (the tested deterministic core); no gh/git
12
- # logic lives in this file. All model-bearing work — finding triage, `fix` mode, `rules`
10
+ # This YAML is the SSOT for the review spine's state order and guards. Every state shells out to
11
+ # the four flat `spur builder pr-<verb>` verbs (task 1219); the op sequencing
12
+ # (preflight→hygiene, push→ensure, request→wait→collect) lives in `runPrVerb`, not in this file's
13
+ # shell, and no gh/git logic lives in this file. All model-bearing work — finding triage, `fix`
13
14
  # mode — stays in the sp:pr-reviewing skill and never enters this machine.
14
15
  #
15
16
  # Non-negotiable: the external review goes through the GitHub PR + an `@codex review`
@@ -21,7 +22,7 @@
21
22
  # submit: request and stop at `pending` (no wait)
22
23
  # rerun: request with --force even when HEAD is already reviewed
23
24
  # baseBranch — base for a newly created PR (default: existing PR base, else repo default)
24
- # focus — extra review focus appended to the @codex request
25
+ # focus — extra review focus appended to the @codex review request
25
26
  # noWait — "true" returns pending right after the request instead of polling
26
27
  # waitTimeoutSec — poll budget for Codex output (default 600)
27
28
  # waitIntervalSec — poll interval (default 30; do not poll aggressively)
@@ -34,9 +35,9 @@
34
35
  version: "1"
35
36
  name: pr-review
36
37
  kind: state-machine
37
- description: GitHub Codex PR-review spine — preflight → hygiene → precheck → push → ensure-pr → request → wait → collect
38
+ description: GitHub Codex PR-review spine — check → precheck → open → review
38
39
  iterationBound: 16
39
- initialState: preflight
40
+ initialState: check
40
41
  terminalStates:
41
42
  - done
42
43
  - pending
@@ -53,50 +54,27 @@ vars:
53
54
  spurBin: "spur"
54
55
  preReviewCmd: ""
55
56
  __runId: ""
56
- # Runtime-set by file.read.into-var (0771): the request record's requestedAt|head,
57
- # extracted once by `request` and read per state — no repeated JSON parsing in shell.
58
- prSince: ""
59
- prHead: ""
60
57
 
61
58
  states:
62
- - id: preflight
59
+ - id: check
63
60
  description: >
64
- Soft probe: git/gh/repo checks (detached HEAD, dirty tree, gh auth, GitHub remote,
65
- base-branch refusal — preflight refuses when the current branch IS the resolved base,
66
- before any push can publish it).
67
- Writes PASS|FAIL to .spur/run/${vars.__runId}-pr-preflight.status; always exit 0.
61
+ Soft probe: preflight (git/gh/repo checks, detached HEAD, dirty tree, base-branch refusal —
62
+ refused before any push can publish it) followed by the hygiene scan of base...HEAD
63
+ (secrets/.env/conflict markers BLOCK; debug residue WARNs). A preflight failure stops before
64
+ hygiene. Writes PASS|WARN|BLOCK|FAIL to .spur/run/${vars.__runId}-pr-check.status; always
65
+ exit 0, so a red gate routes through the transitions below.
68
66
  onEnter:
69
- # script-root retired for this workflow: actions call `$spurBin builder pr` directly (PR-7).
67
+ # script-root retired for this workflow: actions call `$spurBin builder pr-<verb>` directly.
70
68
  - kind: shell
71
69
  options:
72
- # (e) 0825: 10 lines — detached-HEAD/dirty/gh-auth/base-refusal probes must record
73
- # before any push can publish; split would let a half-probed run continue.
70
+ # (e) 0825: 7 lines — preflight+hygiene and their status fallback must land together
71
+ # (one verb now owns the whole probe; a split would half-probe the submission gate).
74
72
  command: >-
75
-
76
- STATUS_FILE=".spur/run/$__runId-pr-preflight.status" &&
77
- set +e &&
78
- $spurBin builder pr preflight --base "$baseBranch" --json > ".spur/run/$__runId-pr-context.json";
79
- rc=$?; set -e &&
80
- if [ "$rc" -eq 0 ]; then printf 'PASS\n' > "$STATUS_FILE"; else printf 'FAIL\n' > "$STATUS_FILE"; fi &&
81
- exit 0
82
-
83
- - id: hygiene
84
- description: >
85
- Submission sanity scan of base...HEAD: secrets/.env/conflict markers BLOCK the run;
86
- debug residue only WARNs. Not a replacement for the independent Codex review.
87
- onEnter:
88
- - kind: shell
89
- options:
90
- # (e) 0825: 9 lines — hygiene verdict + json capture land together; the status-file
91
- # fallback keeps a silent driver from passing the gate.
92
- command: >-
93
-
94
- STATUS_FILE=".spur/run/$__runId-pr-hygiene.status" &&
95
- set +e &&
96
- $spurBin builder pr hygiene --base "$baseBranch" --json
97
- --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-hygiene.json";
98
- rc=$?; set -e &&
99
- if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
73
+ STATUS_FILE=".spur/run/$__runId-pr-check.status";
74
+ mkdir -p .spur/run;
75
+ $spurBin builder pr-check --base "$baseBranch" --json --status-file "$STATUS_FILE"
76
+ > ".spur/run/$__runId-pr-check.json";
77
+ if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi;
100
78
  exit 0
101
79
 
102
80
  - id: precheck
@@ -111,141 +89,78 @@ states:
111
89
  command: >-
112
90
  mkdir -p .spur/run; if [ -z "$preReviewCmd" ]; then R=SKIP; elif sh -c "$preReviewCmd"; then R=PASS; else R=FAIL; fi; printf '%s\n' "$R" > ".spur/run/$__runId-pr-precheck.status"
113
91
 
114
- - id: push
92
+ - id: open
115
93
  description: >
116
- Publish the branch (normal push only, never force; sets upstream when missing).
117
- A PR reviews pushed commits only.
94
+ Publish the branch (normal push only, never force; sets upstream when missing) and reuse or
95
+ create its PR — a rejected push stops before any PR work. Writes FOUND|CREATED|FAIL to
96
+ .spur/run/${vars.__runId}-pr-open.status and the composed record (push + pr) to
97
+ .spur/run/${vars.__runId}-pr-open.json.
118
98
  onEnter:
119
99
  - kind: shell
120
100
  options:
121
- # (e) 0825: 9 lines — push result and status fallback are atomic; a split could
122
- # publish commits without recording the outcome.
101
+ # (e) 0825: 7 lines — publish + PR dedupe are one verb now; the status fallback keeps a
102
+ # silent driver from passing the gate.
123
103
  command: >-
124
- mkdir -p .spur/run &&
125
- STATUS_FILE=".spur/run/$__runId-pr-push.status" &&
126
- set +e &&
127
- $spurBin builder pr push --json --status-file "$STATUS_FILE"
128
- > ".spur/run/$__runId-pr-push.json";
129
- rc=$?; set -e &&
130
- if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
104
+ STATUS_FILE=".spur/run/$__runId-pr-open.status";
105
+ mkdir -p .spur/run;
106
+ $spurBin builder pr-open --base "$baseBranch" --json --status-file "$STATUS_FILE"
107
+ > ".spur/run/$__runId-pr-open.json";
108
+ if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi;
131
109
  exit 0
132
110
 
133
- - id: ensure-pr
134
- description: Find or create the GitHub PR for the current branch — never a duplicate.
135
- onEnter:
136
- - kind: shell
137
- options:
138
- # (e) 0825: 9 lines — PR dedupe lookup captures its record for the downstream
139
- # review link; ensure + capture must not race.
140
- command: >-
141
- mkdir -p .spur/run &&
142
- STATUS_FILE=".spur/run/$__runId-pr-ensure.status" &&
143
- set +e &&
144
- $spurBin builder pr ensure-pr --base "$baseBranch" --json
145
- --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr.json";
146
- rc=$?; set -e &&
147
- if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
148
- exit 0
149
-
150
- - id: request
151
- description: >
152
- Post the `@codex review` request (per-HEAD dedupe unless mode=rerun forces).
153
- Records the request under .spur/run/${vars.__runId}-pr-request.json.
154
- onEnter:
155
- - kind: shell
156
- options:
157
- # (e) 0825: 10 lines — the write-then-read split is the file-var transport feeding
158
- # wait/collect and must stay one action (same record, same run scope).
159
- command: >-
160
- F="$([ "$mode" = rerun ] && echo --force)"; $spurBin builder pr request --focus "$focus" $F --json --status-file ".spur/run/$__runId-pr-request.status" > ".spur/run/$__runId-pr-request.json"; [ -f ".spur/run/$__runId-pr-request.status" ] || echo FAIL > ".spur/run/$__runId-pr-request.status"; jq -e . ".spur/run/$__runId-pr-request.json" >/dev/null 2>&1 && jq -r '.requestedAt // ""' ".spur/run/$__runId-pr-request.json" > ".spur/run/$__runId-pr-since.txt" && jq -r '.head // ""' ".spur/run/$__runId-pr-request.json" > ".spur/run/$__runId-pr-head.txt"; exit 0
161
-
162
- - id: wait
111
+ - id: review
163
112
  description: >
164
- Bounded poll for Codex output on the current pushed HEAD. Timeout records TIMEOUT
165
- and routes to `pending` — never a failure (collect later with /sp:dev-pr-review collect).
166
- Reads the request record once at `request` (0771): requestedAt/head land in
167
- run-scoped .txt files, projected into prSince/prHead vars via file.read.into-var.
113
+ Request, wait for and collect the `@codex review` result on the current pushed HEAD
114
+ (per-HEAD dedupe unless mode=rerun; mode=submit or noWait=true stop at PENDING). Writes
115
+ CLEAN|FINDINGS|PENDING|FAIL to .spur/run/${vars.__runId}-pr-review.status — a wait timeout
116
+ is PENDING, never a failure — plus the composed findings record and the composite status.
168
117
  onEnter:
169
- - kind: file.read.into-var
170
- options:
171
- path: .spur/run/${vars.__runId}-pr-since.txt
172
- var: prSince
173
- - kind: file.read.into-var
174
- options:
175
- path: .spur/run/${vars.__runId}-pr-head.txt
176
- var: prHead
177
118
  - kind: shell
178
119
  options:
179
- # (e) 0825: 9 lines — bounded wait loop keeps rc bookkeeping so a crashed driver
180
- # fails closed instead of polling forever.
120
+ # (e) 0825: 10 lines — request/wait/collect are one verb now, so their result lands
121
+ # atomically; the status fallback covers a crashed driver. `mode=submit` or `noWait=true`
122
+ # pass --no-wait and `mode=rerun` passes --force, each as its own space-separated word
123
+ # (two substitutions, never concatenated). `/bin/sh -c` carries no `-e`, so a failed
124
+ # substitution is ignored.
181
125
  command: >-
182
- mkdir -p .spur/run &&
183
- STATUS_FILE=".spur/run/$__runId-pr-wait.status" &&
184
- set +e &&
185
- $spurBin builder pr wait --since "$prSince" --head "$prHead" --timeout "$waitTimeoutSec" --interval "$waitIntervalSec"
186
- --json --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-wait.json";
187
- rc=$?; set -e &&
188
- if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi &&
126
+ O="$([ "$mode" = submit ] || [ "$noWait" = "true" ] && echo --no-wait) $([ "$mode" = rerun ] && echo --force)";
127
+ STATUS_FILE=".spur/run/$__runId-pr-review.status";
128
+ mkdir -p .spur/run;
129
+ $spurBin builder pr-review --focus "$focus" $O --timeout "$waitTimeoutSec" --interval "$waitIntervalSec"
130
+ --json --status-file "$STATUS_FILE" > ".spur/run/$__runId-pr-findings.json";
131
+ if [ ! -f "$STATUS_FILE" ]; then printf 'FAIL\n' > "$STATUS_FILE"; fi;
189
132
  exit 0
190
-
191
- - id: collect
192
- description: >-
193
- Normalize the latest current-HEAD Codex result and record composite PR/CI status.
194
- Request record read via file.read.into-var vars (0771, see wait).
195
- onEnter:
196
- - kind: file.read.into-var
197
- options:
198
- path: .spur/run/${vars.__runId}-pr-since.txt
199
- var: prSince
200
- - kind: file.read.into-var
201
- options:
202
- path: .spur/run/${vars.__runId}-pr-head.txt
203
- var: prHead
204
133
  - kind: shell
205
134
  options:
206
- # (e) 0825: 9 lines — collect + status must run against the same since/head pair
207
- # before the composite status is decided (same 0771 record contract).
135
+ # The report reads the same composite PR/CI/Codex artifact as before (read-only, so a
136
+ # failed probe records its own error object instead of failing the collected run).
208
137
  command: >-
209
- mkdir -p .spur/run; $spurBin builder pr collect --since "$prSince" --head "$prHead" --json --status-file ".spur/run/$__runId-pr-collect.status" > ".spur/run/$__runId-pr-findings.json"; c=$?; $spurBin builder pr status --since "$prSince" --head "$prHead" --json > ".spur/run/$__runId-pr-status.json" && [ "$c" -eq 0 ] && [ -f ".spur/run/$__runId-pr-collect.status" ] || printf 'FAIL\n' > ".spur/run/$__runId-pr-collect.status"; cat ".spur/run/$__runId-pr-findings.json" 2>/dev/null; exit 0
138
+ $spurBin builder pr-status --json > ".spur/run/$__runId-pr-status.json" || true
210
139
 
211
140
  - id: done
212
141
  description: Terminal — review collected; findings artifact at .spur/run/${vars.__runId}-pr-findings.json
213
142
  - id: pending
214
- description: Terminal — review requested (or timed out) but not yet collected; run /sp:dev-pr-review collect later
143
+ description: Terminal — review requested (or timed out) but not yet collected; run /sp:dev-pr-review status later
215
144
  - id: failed
216
145
  description: Terminal — a gate went red; the stage .status/.json artifacts under .spur/run name the blocker
217
146
 
218
147
  transitions:
219
- - from: preflight
220
- to: hygiene
221
- description: Repo, gh auth, and clean tree verified
222
- guard:
223
- kind: shell
224
- options:
225
- command: 'test "$(cat .spur/run/$__runId-pr-preflight.status 2>/dev/null)" = PASS'
226
- - from: preflight
227
- to: failed
228
- terminalReason: failed-check
229
- description: Preflight red (detached HEAD, dirty tree, gh auth, no GitHub remote, or the current branch being the base branch) — stop before any publishing
230
- guard:
231
- kind: always
232
-
233
- - from: hygiene
148
+ - from: check
234
149
  to: precheck
235
150
  description: Clean or warn-only — warnings ride along to the report
236
151
  guard:
237
152
  kind: shell
238
153
  options:
239
- command: 'grep -qE "^(PASS|WARN)$" .spur/run/$__runId-pr-hygiene.status 2>/dev/null'
240
- - from: hygiene
154
+ command: 'grep -qE "^(PASS|WARN)$" .spur/run/$__runId-pr-check.status 2>/dev/null'
155
+ - from: check
241
156
  to: failed
242
157
  terminalReason: failed-check
243
- description: BLOCK (secrets/.env/conflict markers) or probe failure — never submit a tainted diff
158
+ description: BLOCK (secrets/.env/conflict markers), a preflight failure (detached HEAD, dirty tree, gh auth, no GitHub remote, or the current branch being the base branch), or a probe failure — stop before any publishing
244
159
  guard:
245
160
  kind: always
246
161
 
247
162
  - from: precheck
248
- to: push
163
+ to: open
249
164
  description: Project fast check passed or was skipped
250
165
  guard:
251
166
  kind: shell
@@ -258,104 +173,37 @@ transitions:
258
173
  guard:
259
174
  kind: always
260
175
 
261
- - from: push
262
- to: ensure-pr
263
- description: Branch published (or already up to date)
264
- guard:
265
- kind: shell
266
- options:
267
- command: 'grep -qE "^(PUSHED|UP_TO_DATE)$" .spur/run/$__runId-pr-push.status 2>/dev/null'
268
- - from: push
269
- to: failed
270
- terminalReason: failed-check
271
- description: Push rejected (e.g. non-fast-forward) — report; never force-push as a workaround
272
- guard:
273
- kind: always
274
-
275
- - from: ensure-pr
276
- to: request
277
- description: PR found or created
278
- guard:
279
- kind: shell
280
- options:
281
- command: 'grep -qE "^(FOUND|CREATED)$" .spur/run/$__runId-pr-ensure.status 2>/dev/null'
282
- - from: ensure-pr
283
- to: failed
284
- terminalReason: failed-check
285
- description: No reviewable delta or PR creation failed — report the actual git/GitHub state
286
- guard:
287
- kind: always
288
-
289
- # Declaration order matters: ALREADY_REVIEWED routes straight to collect (full mode dedupe);
290
- # submit/noWait stop at pending; everything else waits.
291
- - from: request
292
- to: collect
293
- description: Current HEAD already carries a Codex review — collect instead of duplicating
294
- guard:
295
- kind: shell
296
- options:
297
- command: 'test "$(cat .spur/run/$__runId-pr-request.status 2>/dev/null)" = ALREADY_REVIEWED'
298
- - from: request
299
- to: pending
300
- description: Request already in flight, or mode=submit/--no-wait after posting — return pending
301
- guard:
302
- kind: shell
303
- options:
304
- # (e) 0825: 4-predicate guard — ALREADY_REQUESTED/REQUESTED pair state is the
305
- # fallback route when the review driver stalls (mode/noWait honored).
306
- command: 'test "$(cat .spur/run/$__runId-pr-request.status 2>/dev/null)" = ALREADY_REQUESTED || { test "$(cat .spur/run/$__runId-pr-request.status 2>/dev/null)" = REQUESTED && { test "$mode" = "submit" || test "$noWait" = "true"; }; }'
307
- - from: request
308
- to: wait
309
- description: Request posted — poll for the result
310
- guard:
311
- kind: shell
312
- options:
313
- command: 'test "$(cat .spur/run/$__runId-pr-request.status 2>/dev/null)" = REQUESTED'
314
- - from: request
315
- to: failed
316
- terminalReason: failed-check
317
- description: Request failed (no PR, gh error) — report the preserved error
318
- guard:
319
- kind: always
320
-
321
- - from: wait
322
- to: collect
323
- description: Codex findings or an explicit clean result on the current HEAD found
324
- guard:
325
- kind: shell
326
- options:
327
- command: 'grep -qE "^(FOUND|CLEAN)$" .spur/run/$__runId-pr-wait.status 2>/dev/null'
328
- - from: wait
329
- to: pending
330
- description: Poll budget exhausted — pending, not failed
176
+ - from: open
177
+ to: review
178
+ description: Branch published and PR found or created
331
179
  guard:
332
180
  kind: shell
333
181
  options:
334
- command: 'test "$(cat .spur/run/$__runId-pr-wait.status 2>/dev/null)" = TIMEOUT'
335
- - from: wait
182
+ command: 'grep -qE "^(FOUND|CREATED)$" .spur/run/$__runId-pr-open.status 2>/dev/null'
183
+ - from: open
336
184
  to: failed
337
185
  terminalReason: failed-check
338
- description: Poll probe itself failed
186
+ description: Push rejected (e.g. non-fast-forward — report; never force-push as a workaround) or no reviewable delta or PR creation failed
339
187
  guard:
340
188
  kind: always
341
189
 
342
- - from: collect
190
+ - from: review
343
191
  to: done
344
192
  description: Current-HEAD review collected (CLEAN/FINDINGS)
345
193
  guard:
346
194
  kind: shell
347
195
  options:
348
- command: 'grep -qE "^(FINDINGS|CLEAN)$" .spur/run/$__runId-pr-collect.status 2>/dev/null'
349
- - from: collect
196
+ command: 'grep -qE "^(FINDINGS|CLEAN)$" .spur/run/$__runId-pr-review.status 2>/dev/null'
197
+ - from: review
350
198
  to: pending
351
- description: No current-HEAD result yet — pending, never clean
199
+ description: Request in flight, mode=submit/noWait, or a wait timeout — pending, not failed (collect later)
352
200
  guard:
353
201
  kind: shell
354
202
  options:
355
- command: 'test "$(cat .spur/run/$__runId-pr-collect.status 2>/dev/null)" = PENDING'
356
- - from: collect
203
+ command: 'test "$(cat .spur/run/$__runId-pr-review.status 2>/dev/null)" = PENDING'
204
+ - from: review
357
205
  to: failed
358
206
  terminalReason: failed-check
359
- description: Collect probe failed
207
+ description: Review probe itself failed (no PR, gh error, moved HEAD)
360
208
  guard:
361
209
  kind: always