@gobing-ai/spur 0.3.93 → 0.3.95

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 (153) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +42 -0
  3. package/board/index.d.ts +38 -0
  4. package/config/plugin-scripts.json +5 -0
  5. package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
  6. package/config/rules/strict/runtime-boundaries.yaml +2 -1
  7. package/config/rules/structure/protected-files.yaml +4 -0
  8. package/config/workflows/feature-verification.yaml +7 -4
  9. package/config/workflows/idea-pipeline.yaml +10 -4
  10. package/config/workflows/task-pipeline.yaml +71 -52
  11. package/config/workflows/wrapup-pipeline.yaml +92 -17
  12. package/package.json +15 -11
  13. package/plugins/sp/README.md +1 -1
  14. package/plugins/sp/commands/dev-review-session.md +4 -4
  15. package/plugins/sp/commands/dev-review.md +16 -5
  16. package/plugins/sp/commands/dev-verify.md +7 -4
  17. package/plugins/sp/commands/dev-verifyall.md +4 -3
  18. package/plugins/sp/commands/dev-wrap.md +1 -1
  19. package/plugins/sp/hooks/context-post-tool.ts +2 -2
  20. package/plugins/sp/hooks/context-session-start.ts +2 -1
  21. package/plugins/sp/hooks/context-session-stop.ts +22 -14
  22. package/plugins/sp/hooks/pi/guard-extension.ts +112 -172
  23. package/plugins/sp/lib/idea-handoff.generated.mjs +2 -2
  24. package/plugins/sp/lib/inline-run.generated.d.mts +4 -2
  25. package/plugins/sp/lib/inline-run.generated.mjs +35 -14
  26. package/plugins/sp/plugin.json +1 -1
  27. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
  28. package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
  29. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
  30. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
  31. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
  32. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
  33. package/plugins/sp/scripts/feature-sync-bounded.mjs +5 -2
  34. package/plugins/sp/scripts/feature-sync-bounded.ts +3 -1
  35. package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
  36. package/plugins/sp/scripts/history-anatomy-cache.mjs +3 -3
  37. package/plugins/sp/scripts/history-anatomy-cache.ts +4 -4
  38. package/plugins/sp/scripts/inline-run-setup.mjs +60 -12
  39. package/plugins/sp/scripts/inline-run-setup.ts +122 -15
  40. package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
  41. package/plugins/sp/scripts/pr-reviewing.ts +1 -0
  42. package/plugins/sp/scripts/quality-gate.mjs +15 -6
  43. package/plugins/sp/scripts/quality-gate.ts +30 -9
  44. package/plugins/sp/scripts/residual-scan.mjs +4 -4
  45. package/plugins/sp/scripts/residual-scan.ts +5 -4
  46. package/plugins/sp/scripts/script-contract-check.ts +175 -46
  47. package/plugins/sp/scripts/script-root.mjs +125 -0
  48. package/plugins/sp/scripts/script-root.ts +195 -0
  49. package/plugins/sp/scripts/stage-registry-adapter.ts +1 -1
  50. package/plugins/sp/scripts/task-evidence-precheck.ts +4 -2
  51. package/plugins/sp/scripts/task-size-precheck.ts +3 -1
  52. package/plugins/sp/scripts/validate-commands.ts +12 -12
  53. package/plugins/sp/scripts/validate-flag-contracts.ts +42 -30
  54. package/plugins/sp/scripts/verify-answer-lint.ts +3 -1
  55. package/plugins/sp/scripts/workflow-step-profile.mjs +5 -2
  56. package/plugins/sp/scripts/workflow-step-profile.ts +3 -1
  57. package/plugins/sp/scripts/wrapup-drift-probe.mjs +5 -2
  58. package/plugins/sp/scripts/wrapup-drift-probe.ts +4 -2
  59. package/plugins/sp/scripts/wrapup-steps.mjs +32 -8
  60. package/plugins/sp/scripts/wrapup-steps.ts +56 -10
  61. package/plugins/sp/skills/code-verification/SKILL.md +12 -7
  62. package/plugins/sp/skills/code-verification/references/verdict-schema.md +11 -0
  63. package/plugins/sp/skills/functional-review/SKILL.md +1 -1
  64. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  65. package/plugins/sp/skills/session-review/SKILL.md +11 -9
  66. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +9 -1
  67. package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
  68. package/plugins/sp/skills/spur-dev/references/execution-batch.md +65 -17
  69. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -3
  70. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +22 -5
  71. package/plugins/sp/tsconfig.json +8 -0
  72. package/spur.js +20934 -21087
  73. package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.BxJuwD7I.js} +151 -152
  74. package/web/_astro/BoardApp.Cpxntzad.js +1 -0
  75. package/web/_astro/TaskDetail.C5bW4WGV.js +1 -0
  76. package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
  77. package/web/_astro/arc.CyjRvNMY.js +1 -0
  78. package/web/_astro/architectureDiagram-3BPJPVTR.B4lWRzJA.js +36 -0
  79. package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.Be38USQ2.js} +1 -1
  80. package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
  81. package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
  82. package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
  83. package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
  84. package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
  85. package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
  86. package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
  87. package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.DT9Fj5Qx.js} +1 -1
  88. package/web/_astro/channel.CI6N_tCg.js +1 -0
  89. package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.SeSBWLg5.js} +1 -1
  90. package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DoO4VE14.js} +1 -1
  91. package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.DrmTFA53.js} +1 -1
  92. package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.Bi-TUb_V.js} +4 -4
  93. package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.B-xp8Uhi.js} +1 -1
  94. package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.DGWrDnUU.js} +1 -1
  95. package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
  96. package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM._BagvPDy.js} +1 -1
  97. package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.VOboYWQ0.js} +1 -1
  98. package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
  99. package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.D_apfHS7.js} +1 -1
  100. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.D_apfHS7.js} +1 -1
  101. package/web/_astro/client.BhluLQqe.js +1 -0
  102. package/web/_astro/client.DBpSRLnx.js +9 -0
  103. package/web/_astro/cose-bilkent-S5V4N54A.DNLU_L-x.js +1 -0
  104. package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.D8borhV-.js} +1 -1
  105. package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.Uc0lvjcV.js} +2 -2
  106. package/web/_astro/diagram-2AECGRRQ.DDIxtDBo.js +43 -0
  107. package/web/_astro/diagram-5GNKFQAL.DVoDnD3H.js +10 -0
  108. package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.CRubTJ-0.js} +3 -3
  109. package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.m9SeYcXQ.js} +1 -1
  110. package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.C6s2ZjV5.js} +1 -1
  111. package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.nSzr5KTj.js} +5 -5
  112. package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DrZcys16.js} +4 -4
  113. package/web/_astro/ganttDiagram-6RSMTGT7.Do4M1b-K.js +292 -0
  114. package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.BWc9oJ4l.js} +1 -1
  115. package/web/_astro/index.5dn-Zm-2.js +1 -0
  116. package/web/_astro/index.C-IqPpW2.js +1 -0
  117. package/web/_astro/index.CENnIEqT.css +1 -0
  118. package/web/_astro/index.Cyc_GA4f.js +1 -0
  119. package/web/_astro/index.tZfqwSut.js +1 -0
  120. package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.BIdOctFk.js} +1 -1
  121. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.CV5SuG2t.js} +5 -5
  122. package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.CxE-rEJ-.js} +1 -1
  123. package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
  124. package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.BPRdKWs_.js} +7 -7
  125. package/web/_astro/{linear.C2_IkbZT.js → linear.zRsuuDTE.js} +1 -1
  126. package/web/_astro/mermaid.core.jAJTcMKc.js +301 -0
  127. package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BffJxfCr.js} +2 -2
  128. package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CacwWaE3.js} +3 -3
  129. package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.hXELD06-.js} +1 -1
  130. package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.DdEbPcqT.js} +3 -3
  131. package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DgaDocKv.js} +4 -4
  132. package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B0KScnAu.js} +1 -1
  133. package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CV9M_WNc.js} +1 -1
  134. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.BOAo74Et.js} +1 -1
  135. package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.CqqepUMe.js} +3 -3
  136. package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.DDqe4dOG.js} +5 -5
  137. package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.B3R9OPdO.js} +3 -3
  138. package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.B3SvgXpQ.js} +1 -1
  139. package/web/board-runtime.json +69 -0
  140. package/web/index.html +2 -2
  141. package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
  142. package/web/_astro/TaskDetail.DCqiC-OZ.js +0 -1
  143. package/web/_astro/arc.CPwg6Rw0.js +0 -1
  144. package/web/_astro/architectureDiagram-3BPJPVTR.DM_vp_hO.js +0 -36
  145. package/web/_astro/channel.MYZLKNwy.js +0 -1
  146. package/web/_astro/client.yhYJvxCU.js +0 -9
  147. package/web/_astro/cose-bilkent-S5V4N54A.B1wSL-Xb.js +0 -1
  148. package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
  149. package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
  150. package/web/_astro/ganttDiagram-6RSMTGT7.BO81S85v.js +0 -292
  151. package/web/_astro/index.De90oHcH.js +0 -1
  152. package/web/_astro/index.Hjbr15fG.css +0 -1
  153. package/web/_astro/mermaid.core.GAOYeSR0.js +0 -303
@@ -7,7 +7,7 @@
7
7
  "plugins": [
8
8
  {
9
9
  "name": "sp",
10
- "version": "0.3.93",
10
+ "version": "0.3.95",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
package/README.md CHANGED
@@ -59,6 +59,21 @@ If `~/.local/bin` isn't already on your `PATH`, add it (e.g. `export PATH="$HOME
59
59
  in your shell profile). Supported targets: `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`.
60
60
  Windows: use the Bun path under WSL.
61
61
 
62
+ **Verification.** The installer downloads to a temp file, checks it against the release's
63
+ `SHA256SUMS`, and only then atomically replaces `spur`. A failed download, a checksum mismatch, or a
64
+ missing `SHA256SUMS` leaves an existing install untouched and exits non-zero. `SPUR_RELEASE_URL`
65
+ overrides the download base URL (a mirror, or a `file://` directory in tests).
66
+
67
+ The checksum is a **code-execution** boundary, not just an integrity check: having verified the
68
+ download, the installer *runs* it (`spur init`), so a release whose `SHA256SUMS` cannot be verified
69
+ is a release you cannot safely install. `SPUR_SKIP_VERIFY=1` is the single explicit bypass and
70
+ prints a warning — keep it for local or air-gapped installs, and pipe the script into `sh` only from
71
+ a source you trust.
72
+
73
+ **Releasing standalone binaries.** Run `bun run build:binaries` in `apps/cli`, then upload **all**
74
+ of `dist/cli/spur-*` **and** `dist/cli/SHA256SUMS` to the GitHub Release for the tag. A release
75
+ without `SHA256SUMS` makes the installer fail by default.
76
+
62
77
  ### Package layout (what ships)
63
78
 
64
79
  The published tarball is a self-contained Bun bundle plus static assets:
@@ -98,6 +113,33 @@ spur status
98
113
 
99
114
  Every command supports `--json` for machine-readable output.
100
115
 
116
+ ### Extending the Spur Board
117
+
118
+ A project can add its own Board modules — a compiled React tool or an embedded URL app — without
119
+ editing this package. An installed Board reads them from a fresh project's `.spur/config.yaml`:
120
+
121
+ ```yaml
122
+ bootstrap:
123
+ modules:
124
+ - id: team-board
125
+ name: Team board
126
+ icon: "🧭"
127
+ type: react
128
+ directory: board/team-board/dist
129
+ entry: index.js
130
+ styles: [index.css]
131
+ - id: docs
132
+ name: Documentation
133
+ icon: "📚"
134
+ type: iframe
135
+ url: https://docs.example.test/
136
+ ```
137
+
138
+ `spur init` seeds `modules: []` (built-ins only); change selection with a config edit, then restart
139
+ `spur serve` and reload. The full authoring contract — the declaration union, the
140
+ `@gobing-ai/spur/board` type export, the Vite library setup and the frame/trust limits — is in the
141
+ [downstream Board modules design](https://github.com/gobing-ai/spur/blob/main/docs/design/downstream-board-modules.md).
142
+
101
143
  ## Documentation
102
144
 
103
145
  Full docs, architecture, and the complete command surface live in the
@@ -0,0 +1,38 @@
1
+ // Generated by scripts/commands/emit-board-types.ts from
2
+ // apps/web/src/modules/contribution.ts — edit the authoring source, then regenerate.
3
+ import type { ComponentType } from 'react';
4
+ /**
5
+ * Public authoring contract for a native (compiled React) Board module contribution
6
+ * (A8 / task 0988 R1).
7
+ *
8
+ * This file is the ONE authoring source for the declaration-only `@gobing-ai/spur/board`
9
+ * package export: `scripts/commands/emit-board-types.ts` emits `apps/cli/board/index.d.ts`
10
+ * from it, so the published ABI cannot drift from the Board's own types. Keep it
11
+ * type-only — no React *value* import may reach the CLI or plugin artifacts (the CLI
12
+ * ships no React; the contributor supplies compatible React types through its own
13
+ * devDependencies).
14
+ *
15
+ * Module metadata (id/name/icon/route) is deliberately absent: it is project
16
+ * configuration (one selection authority), while this shape carries only what the
17
+ * renderer needs to mount a contribution.
18
+ */
19
+ export interface BoardModuleContribution {
20
+ /** Contribution protocol version this Board accepts. */
21
+ readonly apiVersion: 1;
22
+ /**
23
+ * Workspace component. Rendered inside the Board's React tree with the Board's own
24
+ * React instance — it must not call `createRoot`, and no private Board context is
25
+ * exported to it.
26
+ */
27
+ readonly component: ComponentType;
28
+ /** Optional companion the Board's right panel renders while this module is active. */
29
+ readonly rightPanelComponent?: ComponentType;
30
+ }
31
+ /**
32
+ * The named export a native Board module entry must publish.
33
+ *
34
+ * Declared rather than implemented: `@gobing-ai/spur/board` is a declaration-only export,
35
+ * not a runtime SDK (consumers import its types; their compiled ESM entry exports this
36
+ * name for the renderer).
37
+ */
38
+ export declare const webModule: BoardModuleContribution;
@@ -93,6 +93,11 @@
93
93
  "rel": "script-contract-check.ts",
94
94
  "contract": "repo-only"
95
95
  },
96
+ {
97
+ "rel": "script-root.ts",
98
+ "contract": "standard",
99
+ "twin": "script-root.mjs"
100
+ },
96
101
  {
97
102
  "rel": "stage-registry-adapter.ts",
98
103
  "contract": "repo-only"
@@ -0,0 +1,48 @@
1
+ $schema: "@gobing-ai/spur/schemas/rule-file.schema.json"
2
+ # Test-only subpath boundary — @gobing-ai/spur-app/testing and the app's testing/
3
+ # module are for tests and the mock itself, never production sources (task 0975 R3).
4
+ #
5
+ # WHY: `@gobing-ai/spur-app/testing` (packages/app/src/testing/history-board-mock.ts)
6
+ # is a test-only mock subpath (E82). A production import couples shipped code to a
7
+ # fake, and it crept toward the server transport before 0961 severed the seam. The
8
+ # evaluator's glob matcher is segment-aware with `*`/`**` only (no brace groups), so
9
+ # the ts/tsx globs would need separate entries — the repo carries .ts sources only,
10
+ # matching the dao-boundary precedent.
11
+ #
12
+ # Scope covers src trees only: `<workspace>/tests/**` directories stay outside, and
13
+ # `packages/app/src/testing/**` (the mock's own home) is exempted explicitly.
14
+ rules:
15
+ - id: no-testing-subpath-import
16
+ description: >
17
+ Production sources must not import the @gobing-ai/spur-app/testing subpath
18
+ (history-board mock, E82/0961). Tests under <workspace>/tests/** and the
19
+ packages/app/src/testing/** module itself are exempt.
20
+ severity: error
21
+ evaluator:
22
+ type: forbidden-import
23
+ config:
24
+ forbidden:
25
+ - specifier: "@gobing-ai/spur-app/testing"
26
+ scope:
27
+ include:
28
+ - "apps/**/src/**/*.ts"
29
+ - "packages/**/src/**/*.ts"
30
+ exclude:
31
+ - "packages/app/src/testing/**"
32
+
33
+ - id: no-relative-testing-module-import
34
+ description: >
35
+ packages/app production sources must not reach the testing/ module through
36
+ relative imports (from './testing/…' or '../…/testing…'). The testing/**
37
+ module itself and workspace tests/ directories are exempt.
38
+ severity: error
39
+ evaluator:
40
+ type: forbidden-import
41
+ config:
42
+ forbidden:
43
+ - pattern: "(?:from\\s+|import\\(\\s*)['\"](\\.\\.?/)+(?:[^'\"]*/)?testing(?:/|['\"])"
44
+ scope:
45
+ include:
46
+ - "packages/app/src/**/*.ts"
47
+ exclude:
48
+ - "packages/app/src/testing/**"
@@ -73,6 +73,7 @@ rules:
73
73
 
74
74
  # Synchronous template & workflow resolvers:
75
75
  - "apps/cli/src/commands/task.ts" # sync loadTemplateContent/Bodies callbacks
76
- - "packages/app/src/services/workflow-service.ts" # composition advisory source (0775: facts extracted from the live definition inside the synchronous validate path; the baseline snapshot sync read was retired)
76
+ - "packages/app/src/services/workflow-service.ts" # run-log/checkpoint reclamation realpath confinement (:988, :1014)
77
+ - "packages/app/src/workflow/run-record.ts" # confined sync run-record reads: realpath + fstat byte-window (moved from workflow-service.ts, 0962)
77
78
  - "apps/cli/src/release-ops.ts" # sync manifest/workspace reads + Bun.write rewrites inside the git release flow (task 0617); mirrors task.ts sync-template exemption
78
79
  - "apps/cli/src/version-carriers.ts" # sync manifest reads + Bun.write rewrites for registered release carriers (task 0854); mirrors release-ops.ts sync exemption
@@ -37,6 +37,10 @@ rules:
37
37
  # this rule's include scope — scanning generated copies only adds noise
38
38
  # (e.g. `id: task-implement-transition` trips the sk- token shape).
39
39
  - "apps/cli/config/"
40
+ # `apps/cli/plugins/` is the same kind of gitignored build:bundle output — a staged
41
+ # npm copy of `plugins/`, itself outside the include scope. Minified generated libs
42
+ # there trip the api-key shape (0976: HEAD's inline-run.generated.mjs fires too).
43
+ - "apps/cli/plugins/"
40
44
 
41
45
  - id: no-unsafe-git-commands
42
46
  description: "Unsafe git commands (force-push, hard reset) can overwrite history or cause data loss (AGENTS.md CRITICAL safety)."
@@ -50,13 +50,16 @@ states:
50
50
  digest, run identity) and the coarse status file, always exit 0 so the
51
51
  transition guard routes missing/corrupt/FAIL to `failed`.
52
52
  onEnter:
53
+ # 0960 R2: run-start script-resolution identity — record which tree this run
54
+ # resolved (source-repo vs installed) before the verification step runs. Never aborts.
53
55
  - kind: shell
54
56
  options:
55
57
  command: >-
56
- bun plugins/sp/scripts/feature-verification-steps.ts verify
57
- --feature-id "$featureId"
58
- --run-id "$__runId"
59
- --spur-bin "$spurBin"
58
+ S=plugins/sp/scripts/script-root.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp script-root.mjs 2>/dev/null)"; RUNNER=bun; case "$S" in *.mjs) RUNNER=node ;; esac; [ -f "$S" ] && "$RUNNER" "$S" --run-id "$__runId" --spur-bin "$spurBin" || echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 0
59
+ - kind: shell
60
+ options:
61
+ command: >-
62
+ mkdir -p .spur/run; S=plugins/sp/scripts/feature-verification-steps.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp feature-verification-steps.mjs 2>/dev/null)"; RUNNER=bun; case "$S" in *.mjs) RUNNER=node ;; esac; [ -f "$S" ] && "$RUNNER" "$S" verify --feature-id "$featureId" --run-id "$__runId" --spur-bin "$spurBin" || printf 'FAIL feature-verification failed closed — script not found — run superskill install sp --marketplace gobing-ai/spur\n' > ".spur/run/$__runId-feature-verification.status"; exit 0
60
63
 
61
64
  - id: done
62
65
  description: Terminal — repo-wide checks passed and the receipt is bound.
@@ -84,6 +84,12 @@ states:
84
84
  handoff — no task execution. Soft agent doctor (status file) so doctor red
85
85
  routes to `failed` via transitions instead of a raw lifecycle abort.
86
86
  onEnter:
87
+ # 0960 R2: run-start script-resolution identity — record which tree this run
88
+ # resolved (source-repo vs installed) before any step executes. Never aborts.
89
+ - kind: shell
90
+ options:
91
+ command: >-
92
+ S=plugins/sp/scripts/script-root.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp script-root.mjs 2>/dev/null)"; RUNNER=bun; case "$S" in *.mjs) RUNNER=node ;; esac; [ -f "$S" ] && "$RUNNER" "$S" --run-id "$__runId" --spur-bin "$spurBin" || echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 0
87
93
  - kind: note
88
94
  options:
89
95
  message: "Idea pipeline start for idea: ${vars.idea}"
@@ -254,12 +260,12 @@ states:
254
260
  options:
255
261
  command: >-
256
262
  mkdir -p .spur/run &&
257
- S=plugins/sp/scripts/idea-coverage-check.ts &&
258
- if [ ! -f "$S" ]; then S="$(superskill script path sp idea-coverage-check.ts 2>/dev/null)"; fi &&
263
+ S=plugins/sp/scripts/idea-coverage-check.ts;
264
+ [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp idea-coverage-check.ts 2>/dev/null)";
259
265
  if [ -n "$S" ] && [ -f "$S" ]; then
260
266
  bun "$S" --run-id "$__runId" --report ".spur/run/$__runId-idea-eval-report.md" --ac ".spur/run/$__runId-idea-ac-content.md" || printf 'FAIL run=%s checker exited nonzero (bun missing or checker crash)\n' "$__runId" > ".spur/run/$__runId-idea-coverage.reason";
261
267
  else
262
- printf 'FAIL run=%s checker not found — run superskill install sp\n' "$__runId" | tee ".spur/run/$__runId-idea-coverage.reason" >&2;
268
+ printf 'FAIL run=%s checker not found — run superskill install sp --marketplace gobing-ai/spur\n' "$__runId" | tee ".spur/run/$__runId-idea-coverage.reason" >&2;
263
269
  printf 'FAIL\n' > ".spur/run/$__runId-idea-coverage.status";
264
270
  fi
265
271
  # 0945 R2: derive the route facts ONCE at this boundary (0769 pattern) — guards read these
@@ -558,7 +564,7 @@ states:
558
564
  elif H="$(superskill script path sp idea-handoff.mjs 2>/dev/null)" && [ -f "$H" ]; then
559
565
  node "$H";
560
566
  else
561
- echo "idea handoff failed closed — idea-handoff script not found — run 'superskill install sp'" >&2;
567
+ echo "idea handoff failed closed — idea-handoff script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2;
562
568
  exit 1;
563
569
  fi
564
570
  - id: handoff
@@ -13,9 +13,10 @@
13
13
  # `test` is the project quality gate (shell + bounded /sp:dev-fixall), not
14
14
  # /sp:dev-unit (coverage gap-fill; router C3/C5).
15
15
  #
16
- # F96 residual sweep (0950): precheck captures the resume-safe base commit; verify
17
- # scans and folds residuals between the verdict and the proof bind (blocking leftovers
18
- # downgrade PASS → PARTIAL and take the existing remediation edge); done settles
16
+ # F96 residual sweep (0950, reordered 0983): precheck captures the resume-safe base
17
+ # commit; record scans and folds residuals AFTER `task record` flips the verdict-proven
18
+ # boxes (a post-record blocking leftover downgrades PASS → PARTIAL, fails the record →
19
+ # done guard closed, and re-records Testing before the failed edge); done settles
19
20
  # deferrals; failed renders the recovery report. No new state, edge, or model query.
20
21
  #
21
22
  # Vars (passed as a JSON object via `--vars`):
@@ -187,6 +188,12 @@ states:
187
188
  Fast deterministic task readiness and size checks. Failures route to the
188
189
  `failed` terminal state (not a raw lifecycle abort mid-enter).
189
190
  onEnter:
191
+ # 0960 R2: run-start script-resolution identity — record which tree this run
192
+ # resolved (source-repo vs installed) before any step executes. Never aborts.
193
+ - kind: shell
194
+ options:
195
+ command: >-
196
+ S=plugins/sp/scripts/script-root.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp script-root.mjs 2>/dev/null)"; RUNNER=bun; case "$S" in *.mjs) RUNNER=node ;; esac; [ -f "$S" ] && "$RUNNER" "$S" --run-id "$__runId" --spur-bin "$spurBin" || echo "script-root failed closed — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 0
190
197
  # (e) git-status hygiene advisory; never fails the run (0487 R6 / 0511 R1).
191
198
  - kind: shell
192
199
  options:
@@ -207,12 +214,12 @@ states:
207
214
  - kind: shell
208
215
  options:
209
216
  command: >-
210
- mkdir -p .spur/run; S=plugins/sp/scripts/task-size-precheck.ts; [ -f "$S" ] || S="$(superskill script path sp task-size-precheck.ts 2>/dev/null)"; if [ -f "$S" ]; then bun "$S" "$wbs" --spur-bin "$spurBin" --max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems"; else echo "task-size-precheck failed closed — checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'." >&2; echo "FAIL" > ".spur/run/$wbs-precheck-size.status"; fi; exit 0
217
+ mkdir -p .spur/run; S=plugins/sp/scripts/task-size-precheck.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp task-size-precheck.ts 2>/dev/null)"; if [ -f "$S" ]; then bun "$S" "$wbs" --spur-bin "$spurBin" --max-reqs "$maxImplementReqs" --max-plan-items "$maxImplementPlanItems"; else echo "task-size-precheck failed closed — checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp --marketplace gobing-ai/spur'." >&2; echo "FAIL" > ".spur/run/$wbs-precheck-size.status"; fi; exit 0
211
218
  # (d) task-evidence-precheck.ts owns the check; shell only resolves it and fails closed (0726 R2).
212
219
  - kind: shell
213
220
  options:
214
221
  command: >-
215
- mkdir -p .spur/run; S=plugins/sp/scripts/task-evidence-precheck.ts; [ -f "$S" ] || S="$(superskill script path sp task-evidence-precheck.ts 2>/dev/null)"; if [ -f "$S" ]; then bun "$S" "$wbs" --spur-bin "$spurBin"; else echo "task-evidence-precheck failed closed — checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'." >&2; echo "FAIL" > ".spur/run/$wbs-precheck-evidence.status"; fi; exit 0
222
+ mkdir -p .spur/run; S=plugins/sp/scripts/task-evidence-precheck.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp task-evidence-precheck.ts 2>/dev/null)"; if [ -f "$S" ]; then bun "$S" "$wbs" --spur-bin "$spurBin"; else echo "task-evidence-precheck failed closed — checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp --marketplace gobing-ai/spur'." >&2; echo "FAIL" > ".spur/run/$wbs-precheck-evidence.status"; fi; exit 0
216
223
  # (e) F96 R1 (0950): capture the run's base commit once — a resumed or re-entered run
217
224
  # keeps its original base so residual scans stay anchored to the same diff.
218
225
  - kind: shell
@@ -286,6 +293,13 @@ states:
286
293
  # a silent no-op routes the run to `failed` here instead of drifting
287
294
  # into test/review and being caught a full pass later.
288
295
  requireDiff: true
296
+ # Verification-only re-run admission (task 0976): when requireDiff
297
+ # sees zero non-corpus changes because the implementation already
298
+ # landed through another path, `spur task check <wbs>` PASS admits
299
+ # the empty implement as a certification pass; a check failure
300
+ # keeps the 0424 R3 empty-implement violation. Static option —
301
+ # deliberately no run-var knob (ADR-115).
302
+ requireDiffAllowCleanCheck: true
289
303
  # 0933 R26: escalation contract — the agent.run option names the QUESTION
290
304
  # file (the pause signal, deleted pre-dispatch for freshness); a paused
291
305
  # attempt (agent wrote its question there and exited 0) succeeds with
@@ -425,7 +439,7 @@ states:
425
439
  - kind: shell
426
440
  options:
427
441
  command: >-
428
- mkdir -p .spur/run; if [ -f plugins/sp/scripts/quality-gate.ts ]; then bun plugins/sp/scripts/quality-gate.ts run; elif Q="$(superskill script path sp quality-gate.mjs 2>/dev/null)" && [ -f "$Q" ]; then node "$Q" run; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
442
+ mkdir -p .spur/run; if [ -f config/plugin-scripts.json -a -f plugins/sp/scripts/quality-gate.ts ]; then bun plugins/sp/scripts/quality-gate.ts run; elif Q="$(superskill script path sp quality-gate.mjs 2>/dev/null)" && [ -f "$Q" ]; then node "$Q" run; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
429
443
 
430
444
  - id: test-fix
431
445
  description: >
@@ -508,7 +522,7 @@ states:
508
522
  - kind: shell
509
523
  options:
510
524
  command: >-
511
- mkdir -p .spur/run; if [ -f plugins/sp/scripts/quality-gate.ts ]; then bun plugins/sp/scripts/quality-gate.ts recheck; elif Q="$(superskill script path sp quality-gate.mjs 2>/dev/null)" && [ -f "$Q" ]; then node "$Q" recheck; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
525
+ mkdir -p .spur/run; if [ -f config/plugin-scripts.json -a -f plugins/sp/scripts/quality-gate.ts ]; then bun plugins/sp/scripts/quality-gate.ts recheck; elif Q="$(superskill script path sp quality-gate.mjs 2>/dev/null)" && [ -f "$Q" ]; then node "$Q" recheck; else echo "quality gate failed closed — quality-gate script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf 'FAIL\n' > ".spur/run/$wbs-test-gate.status"; fi; exit 0
512
526
 
513
527
  - id: triage
514
528
  description: >-
@@ -528,7 +542,7 @@ states:
528
542
  - kind: shell
529
543
  options:
530
544
  command: >-
531
- mkdir -p .spur/run; if [ -f plugins/sp/scripts/task-diffstat.ts ]; then bun plugins/sp/scripts/task-diffstat.ts --spur-bin "$spurBin"; elif D="$(superskill script path sp task-diffstat.mjs 2>/dev/null)" && [ -f "$D" ]; then node "$D"; else echo "task-diffstat failed closed — task-diffstat script not found — run 'superskill install sp'" >&2; printf '{"files":0,"insertions":0,"deletions":0,"paths":[],"sensitive":true}\n' > ".spur/run/$wbs-diffstat.json"; fi; exit 0
545
+ mkdir -p .spur/run; if [ -f config/plugin-scripts.json -a -f plugins/sp/scripts/task-diffstat.ts ]; then bun plugins/sp/scripts/task-diffstat.ts --spur-bin "$spurBin"; elif D="$(superskill script path sp task-diffstat.mjs 2>/dev/null)" && [ -f "$D" ]; then node "$D"; else echo "task-diffstat failed closed — task-diffstat script not found — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; printf '{"files":0,"insertions":0,"deletions":0,"paths":[],"sensitive":true}\n' > ".spur/run/$wbs-diffstat.json"; fi; exit 0
532
546
  # (b)+(c) lane pre-guard BEFORE decide: a caller-set mode is projected verbatim (R2b);
533
547
  # a sensitive path or >400 changed lines pins safety (R2c) and the decide result below
534
548
  # is never consulted. A missing/malformed diffstat row (producer crashed or never ran,
@@ -577,31 +591,24 @@ states:
577
591
  Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
578
592
  conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
579
593
  deterministic state (the sanctioned alternative), while the green branch goes to
580
- `triage`. `decide failure-class` reads the bounded gate findings and routes: retryable
581
- → test-recheck (the attempt is counted on entry, so qualityGateMaxFixAttempts still
582
- bounds the loop), fix → test-fix (the repair hop, as before), stop → failed with
583
- terminalReason failed-check. A missing/corrupt decision fails closed to failed-check.
594
+ `triage`. `decide failure-class` reads the bounded gate findings and routes: fix →
595
+ test-fix (the bounded repair hop; its default, recorded as `source: default` when no
596
+ backend decides — 0976 R2), stop → failed with terminalReason failed-check. A
597
+ missing/corrupt decision fails closed to failed-check. (0976 R2 collapsed the former
598
+ `retryable` lane: the declared default made it unreachable on the inline path.)
584
599
  onEnter:
585
600
  - kind: decide
586
601
  options:
587
602
  id: failure-class
588
603
  method: choice
589
604
  question: >-
590
- The quality gate failed. Is the failure retryable (transient or environmental
591
- — just re-run the gate), fixable by the repair agent, or should the run stop
592
- here?
593
- choices: [retryable, fix, stop]
605
+ The quality gate failed. Is the failure fixable by the repair agent, or should
606
+ the run stop here?
607
+ choices: [fix, stop]
594
608
  default: fix
595
609
  evidence:
596
610
  - .spur/run/${vars.wbs}-test-gate.findings
597
611
  resultFile: .spur/run/${vars.wbs}-failure-class.decision
598
- # A retryable classification still counts an attempt so the existing
599
- # qualityGateMaxFixAttempts cap bounds the recheck loop (0943 R3; the cap edge reads
600
- # the same counter the fixall path uses).
601
- - kind: shell
602
- options:
603
- command: >-
604
- A=".spur/run/$wbs-test-fix-attempt"; [ "$(jq -r '.value // ""' ".spur/run/$wbs-failure-class.decision" 2>/dev/null)" = retryable ] && { n="$(cat "$A" 2>/dev/null || echo 0)"; printf '%s\n' "$((n + 1))" > "$A"; }; exit 0
605
612
 
606
613
  - id: review
607
614
  description: Three-dimensional code review via /sp:dev-review (functional requirements traceability + SECUA framework (Security, Efficiency, Correctness, Usability, Architecture) + architecture depth), findings written to `## Review`.
@@ -706,10 +713,10 @@ states:
706
713
  options:
707
714
  command: >-
708
715
  LINT_SCRIPT="plugins/sp/scripts/verify-answer-lint.ts";
709
- [ -f "$LINT_SCRIPT" ] ||
716
+ [ -f config/plugin-scripts.json -a -f "$LINT_SCRIPT" ] ||
710
717
  LINT_SCRIPT="$(superskill script path sp verify-answer-lint.ts 2>/dev/null)";
711
718
  if [ -z "$LINT_SCRIPT" ] || [ ! -f "$LINT_SCRIPT" ]; then
712
- echo "verify-answer-lint: checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'" >&2;
719
+ echo "verify-answer-lint: checker not found in plugins/sp/scripts/ nor staged — run 'superskill install sp --marketplace gobing-ai/spur'" >&2;
713
720
  exit 1;
714
721
  fi;
715
722
  bun "$LINT_SCRIPT" "$wbs"
@@ -718,16 +725,6 @@ states:
718
725
  - kind: shell
719
726
  options:
720
727
  command: "$spurBin task verdict $wbs --from-answer .spur/run/$wbs-verify-answer.txt"
721
- # (d) F96 R2 (0950): residual-scan.ts owns the sweep; shell only resolves it.
722
- # ONE hard action (scan+fold): a scanner crash fails verify closed — the
723
- # verify→failed catch-all routes a missing/malformed verdict. Runs AFTER
724
- # `task verdict` and BEFORE the jq bind so a blocking residual turns PASS
725
- # into PARTIAL (fold rewrites the verdict artifact) and takes the existing
726
- # verify→test-fix edge while attempts remain.
727
- - kind: shell
728
- options:
729
- command: >-
730
- S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ ! -f "$S" ]; then echo "residual-scan failed closed — scanner not found in plugins/sp/scripts/ nor staged — run 'superskill install sp'" >&2; exit 1; fi; case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" scan "$wbs" --spur-bin "$spurBin" && "$RUNNER" "$S" fold "$wbs" --spur-bin "$spurBin"
731
728
  # (e) one jq mutation binds the verdict to the proof digest (0703 R3; runId 0730 §B.2 /
732
729
  # 0757 R4; definitionDigest 0759 R5; honest review stamp 0785 R4; soft action + hard guard).
733
730
  - kind: shell
@@ -745,7 +742,11 @@ states:
745
742
  evidence (0785 R4) — before any task record or status mutation. Post-record step
746
743
  conditionally syncs feature status via the bounded `feature-sync-bounded` wrapper
747
744
  (task 0411 retry-suppression) if `feature_id` is present, or appends an orphan
748
- link proposal to the run report if absent (task 0328 / ADR-0322).
745
+ link proposal to the run report if absent (task 0328 / ADR-0322). The post-record
746
+ residual sweep (0983) re-scans the task file after the verdict-proven box flips and
747
+ folds blocking leftovers into the verdict before `record → done`; a downgrade
748
+ re-records Testing from the final artifact so a failed task never keeps a stale
749
+ PASS narrative.
749
750
  onEnter:
750
751
  # 0785 R3: BOUND verify-verdict registration — FIRST action, before any task record or
751
752
  # status mutation, replacing the redundant proof.fingerprint compare (the action itself
@@ -771,7 +772,11 @@ states:
771
772
  options:
772
773
  id: task-record-transition
773
774
  executable: "${vars.spurBin}"
774
- args: ["task", "record", "${vars.wbs}", "--solution-from-diff", "--transition", "testing"]
775
+ # 0980: --no-lifecycle mirrors the wip/done hops — the pipeline run is
776
+ # already the lifecycle record, so a record-stage transition must not
777
+ # spawn a nested task-lifecycle run that outlives the pipeline as a
778
+ # `running` orphan. The target-aware check gate still runs (inline).
779
+ args: ["task", "record", "${vars.wbs}", "--solution-from-diff", "--transition", "testing", "--no-lifecycle"]
775
780
  retry:
776
781
  maxAttempts: 2
777
782
  delayMs: 2000
@@ -791,7 +796,30 @@ states:
791
796
  - kind: shell
792
797
  options:
793
798
  command: >-
794
- S=plugins/sp/scripts/record-feature-sync.ts; [ -f "$S" ] || S="$(superskill script path sp record-feature-sync.mjs 2>/dev/null)"; [ "$deferFeatureSync" = "true" ] && echo "feature sync deferred to batch integration" >> ".spur/run/$wbs-report.txt" || if [ -f "$S" ]; then bun "$S" --spur-bin "$spurBin"; else echo "feature sync skipped — record-feature-sync not found in plugins/sp/scripts/ nor staged" >> ".spur/run/$wbs-report.txt"; fi; exit 0
799
+ S=plugins/sp/scripts/record-feature-sync.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp record-feature-sync.mjs 2>/dev/null)"; [ "$deferFeatureSync" = "true" ] && echo "feature sync deferred to batch integration" >> ".spur/run/$wbs-report.txt" || if [ -f "$S" ]; then bun "$S" --spur-bin "$spurBin"; else echo "feature sync skipped — record-feature-sync not found in plugins/sp/scripts/ nor staged" >> ".spur/run/$wbs-report.txt"; fi; exit 0
800
+ # (d) F96 (0983): residual-scan.ts owns the sweep; shell only resolves it. The
801
+ # scan moved here from verify: it must read the task file AFTER `task record`
802
+ # flipped the verdict-proven Requirement/AC boxes (0692), or a proven-but-unticked
803
+ # box folds every good run to PARTIAL and the done guard is unreachable (0967).
804
+ # Classification unchanged: any box still unchecked post-record (unproven R/AC,
805
+ # unfinished Plan) stays blocking and fails the record → done guard closed.
806
+ # ONE hard action (scan+fold): a scanner crash fails record closed.
807
+ - kind: shell
808
+ options:
809
+ command: >-
810
+ S=plugins/sp/scripts/residual-scan.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ ! -f "$S" ]; then echo "residual-scan failed closed — scanner not found in plugins/sp/scripts/ nor staged — run 'superskill install sp --marketplace gobing-ai/spur'" >&2; exit 1; fi; case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" scan "$wbs" --spur-bin "$spurBin" && "$RUNNER" "$S" fold "$wbs" --spur-bin "$spurBin"
811
+ # (e) F96 R5 (0983): a sweep downgrade must reach the task record — re-record from
812
+ # the FINAL (post-fold) artifact so Testing carries the downgraded verdict; a failed
813
+ # task must not retain a stale PASS Testing narrative. Soft (exit 0): the downgrade
814
+ # itself already fails the record → done guard; a re-record failure prints its
815
+ # re-run command and the failed state renders the recovery report.
816
+ # 0980: --no-lifecycle for the same orphan reason as the main record command —
817
+ # inert today (the task is already at testing, so this is a same-status no-op),
818
+ # but it keeps the no-nested-lifecycle invariant if the flow shifts.
819
+ - kind: shell
820
+ options:
821
+ command: >-
822
+ verdict="$(jq -r .verdict .spur/run/$wbs-verdict.json 2>/dev/null)"; if [ -n "$verdict" ] && [ "$verdict" != PASS ]; then $spurBin task record $wbs --solution-from-diff --transition testing --no-lifecycle || echo "post-downgrade re-record failed — re-run: $spurBin task record $wbs" >&2; fi; exit 0
795
823
 
796
824
  - id: done
797
825
  description: >
@@ -805,7 +833,7 @@ states:
805
833
  - kind: shell
806
834
  options:
807
835
  command: >-
808
- S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" settle "$wbs" --spur-bin "$spurBin" || echo "residual-settle failed — re-run: residual-scan settle $wbs" >&2; else echo "residual-scan not staged — settle skipped — re-run: residual-scan settle $wbs" >&2; fi; exit 0
836
+ S=plugins/sp/scripts/residual-scan.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" settle "$wbs" --spur-bin "$spurBin" || echo "residual-settle failed — re-run: residual-scan settle $wbs" >&2; else echo "residual-scan not staged — settle skipped — re-run: residual-scan settle $wbs" >&2; fi; exit 0
809
837
  # (c) command.gate owns the done transition with classified transient retry.
810
838
  - kind: command.gate
811
839
  options:
@@ -852,7 +880,7 @@ states:
852
880
  - kind: shell
853
881
  options:
854
882
  command: >-
855
- S=plugins/sp/scripts/residual-scan.ts; [ -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" report "$wbs" --spur-bin "$spurBin" || echo "residual-report failed — re-run: residual-scan report $wbs" >&2; else echo "residual-scan not staged — report skipped — re-run: residual-scan report $wbs" >&2; fi; exit 0
883
+ S=plugins/sp/scripts/residual-scan.ts; [ -f config/plugin-scripts.json -a -f "$S" ] || S="$(superskill script path sp residual-scan.mjs 2>/dev/null)"; if [ -f "$S" ]; then case "$S" in *.mjs) RUNNER=node ;; *) RUNNER=bun ;; esac; "$RUNNER" "$S" report "$wbs" --spur-bin "$spurBin" || echo "residual-report failed — re-run: residual-scan report $wbs" >&2; else echo "residual-scan not staged — report skipped — re-run: residual-scan report $wbs" >&2; fi; exit 0
856
884
 
857
885
  - id: cancelled
858
886
  description: Terminal — pipeline cancelled by operator at the approval gate (R1).
@@ -1007,8 +1035,7 @@ transitions:
1007
1035
  options:
1008
1036
  command: 'test "$mode" != fast'
1009
1037
  # ── failure-class routing (0943 R3): stop first (never mislabeled by the cap), then the
1010
- # attempt cap (bounds BOTH lanes — retryable counted on entry, fix as before), then the
1011
- # decision lanes, then the fail-closed defense. Declaration order matters.
1038
+ # attempt cap, then the fix lane, then the fail-closed defense. Declaration order matters.
1012
1039
  - from: test-fail-triage
1013
1040
  to: failed
1014
1041
  terminalReason: failed-check
@@ -1021,7 +1048,7 @@ transitions:
1021
1048
  - from: test-fail-triage
1022
1049
  to: failed
1023
1050
  terminalReason: retry-exhausted
1024
- description: Attempt cap reached — qualityGateMaxFixAttempts bounds the retryable recheck and fix lanes alike.
1051
+ description: Attempt cap reached — qualityGateMaxFixAttempts bounds the fix lane.
1025
1052
  # (warn) 5 commands: named fix attempts (legibility, 0874).
1026
1053
  guard:
1027
1054
  kind: shell
@@ -1029,14 +1056,6 @@ transitions:
1029
1056
  command: >-
1030
1057
  fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
1031
1058
  test "$fix_attempts" -ge "$qualityGateMaxFixAttempts"
1032
- - from: test-fail-triage
1033
- to: test-recheck
1034
- description: Failure class retryable and under the cap — re-run the gate without fixall; the attempt was counted on entry.
1035
- guard:
1036
- kind: shell
1037
- options:
1038
- command: >-
1039
- jq -e '.value == "retryable"' ".spur/run/$wbs-failure-class.decision" >/dev/null 2>&1
1040
1059
  - from: test-fail-triage
1041
1060
  to: test-fix
1042
1061
  description: Failure class fix and under the cap — the bounded repair hop, as before.
@@ -1172,6 +1191,6 @@ transitions:
1172
1191
  - from: record
1173
1192
  to: failed
1174
1193
  terminalReason: failed-check
1175
- description: Task check failed or proof evidence missing/malformed/mismatched — block before done.
1194
+ description: Task check failed, a post-record residual downgrade, or proof evidence missing/malformed/mismatched — block before done.
1176
1195
  guard:
1177
1196
  kind: always