@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
@@ -134,6 +134,7 @@ list this README is checked against.
134
134
  | `dev-find-next` | Prompt-first feature frontier prioritizer — ranks the open feature frontier by derived importance/urgency with per-candidate evidence; gates unactionable features; emits tree defects as proposals only |
135
135
  | `dev-fixall` | Fix all lint, type, and test errors systematically across the working tree |
136
136
  | `dev-simplify` | Simplify recently-changed code for clarity without changing behavior — incremental, test-after-each, revert on regression |
137
+ | `dev-refactor` | Lens-routed refactoring with a preservation contract — taste lenses (api/architect/tests/ui) classify findings on the shared schema; cutting/breaking changes pause for operator approval; forwards to sp:code-refactoring |
137
138
  | `dev-arch` | Survey a codebase (or module tree) for shallow modules and deepening opportunities — emit a ranked MARKDOWN candidate report that feeds the planning half; never auto-refactors |
138
139
  | `dev-reverse` | Reverse-engineer a codebase — analyze unfamiliar repos, generate HLD/architecture docs, audit quality/security, and produce onboarding documentation |
139
140
  | `dev-gitmsg` | Generate conventional commit message(s) from the current change set via one bounded diff capture, optionally commit |
@@ -199,6 +200,7 @@ plugins/sp/
199
200
  │ ├── code-review/ # Pre-commit self-review + SECUA review lenses
200
201
  │ │ └── references/{review-lenses, self-review-checklist}.md
201
202
  │ ├── code-simplification/ # Behavior-preserving simplification
203
+ │ ├── code-refactoring/ # Lens-routed refactoring coordinator (shared finding schema, P1–P4 severity map, fix ladder, focus detection)
202
204
  │ ├── code-testing/ # Testing / coverage competency
203
205
  │ │ └── references/{unit-testing.md, stacks/{bun-ts, go, python}.md}
204
206
  │ ├── code-verification/ # Verify + SECUA review
@@ -316,6 +318,7 @@ surface or run one workflow. All skills target the same five core platforms: `cl
316
318
  | `code-review` | 1.0 | Pre-commit self-review checklist (6 categories, catches 60-80% of issues) + SECUA review lenses + findings processing |
317
319
  | `code-simplification` | 1.0 | Behavior-preserving simplification — Chesterton's Fence, signal tables, incremental change + test-after-each, scope-to-changed |
318
320
  | `code-improvement` | 1.0 | Architectural deepening — surface shallow/tightly-coupled modules and propose refactors that make them deep, testable, AI-navigable; backs `/sp:dev-arch` |
321
+ | `code-refactoring` | 1.0 | Lens-routed refactoring coordinator with a preservation contract — focus auto-detection, taste-lens dispatch, shared finding schema, P1–P4 severity map, objective/taste gates, fix ladder with revert-on-regression; backs `/sp:dev-refactor` |
319
322
  | `functional-review` | 1.0 | Requirements-traceability assessment — per-requirement verdicts with file:line evidence that the implementation satisfies ALL task requirements; pipeline Phase 8b gate |
320
323
  | `doubt-driven-development` | 1.0 | In-flight adversarial review of a non-trivial decision before committing it — hand artifact + contract to a fresh-context skeptic, reconcile, stop at 3 cycles |
321
324
  | `source-driven-development` | 1.0 | Source-first verification — verify framework/API/library facts against primary sources before generating code; separates "the API exists" from "used correctly under its contract" |
@@ -340,10 +343,10 @@ surface or run one workflow. All skills target the same five core platforms: `cl
340
343
  | `spur-composer` | 1.0 | Cross-noun composition — workflow catalog selection, the ephemeral→project→shared ladder, ADR-115 budgets, trace-driven rule tuning; applies accepted `spur-doctor` proposals through `spur` verbs; never judges its own output and never runs a recurring loop |
341
344
  | `spur-doctor` | 1.0 | Cross-noun evaluation — read-only CLI evidence per noun (task/feature/rule/workflow/agent spec), reflection over `history-anatomy` findings through a closed action-class map, and a proposal table; writes nothing; diagnoses artifacts, not runtime environments (`spur agent doctor`'s job) |
342
345
  | `redesign-web-ui` | 1.0 | Existing-UI visual upgrade — audit generic AI fingerprints, apply in-stack polish against `DESIGN.md` / live tokens, verify behavior and viewports; does not migrate frameworks |
343
- | `taste-refactoring-api` | — | API design and refactoring for REST/HTTP, RPC/gRPC, GraphQL, and event contracts; compatibility, security, and migration review |
344
- | `taste-refactoring-ui` | — | UI design and refactoring with visual hierarchy, typography, spacing, color, and interaction review |
345
- | `taste-refactoring-architect` | — | Architecture review, simplification, and refactoring; boundary modeling, dependency reduction, and migration planning |
346
- | `taste-refactoring-tests` | — | Test suite refactoring for failure sensitivity, regression detection, and assertion strength; eliminates brittle mocks |
346
+ | `taste-refactoring-api` | 1.0 | API design and refactoring for REST/HTTP, RPC/gRPC, GraphQL, event contracts, and CLI surfaces; compatibility, security, and migration review |
347
+ | `taste-refactoring-ui` | 1.0 | UI design and refactoring with visual hierarchy, typography, spacing, color, and interaction review |
348
+ | `taste-refactoring-architect` | 1.0 | Architecture review, simplification, and refactoring; boundary modeling, dependency reduction, and migration planning |
349
+ | `taste-refactoring-tests` | 1.0 | Test suite refactoring for failure sensitivity, regression detection, and assertion strength; eliminates brittle mocks |
347
350
 
348
351
  #### Bounded coexistence and retirement gate — `sp:issue-finding` (HA-S1 0661)
349
352
 
@@ -620,14 +623,11 @@ pipeline owns one lifecycle phase:
620
623
 
621
624
  | Workflow | Phase | Entry command |
622
625
  | --------------------------- | --------------------------------- | --------------------------------- |
623
- | `basic.yaml` | Generic implement/check/fix | direct `spur workflow run` |
624
626
  | `feature-lifecycle.yaml` | Feature status FSM | `spur feature update` |
625
627
  | `task-lifecycle.yaml` | Task status FSM | `spur task update` |
626
628
  | `task-pipeline.yaml` | Single-task execution | `/sp:dev-run` |
627
629
  | `idea-pipeline.yaml` | Idea/planning → feature + tasks | `/sp:dev-idea`, `/sp:dev-plan` |
628
- | `feature-dev.yaml` | Feature umbrella execution | `/sp:dev-runall --feature` |
629
630
  | `wrapup-pipeline.yaml` | Post-execution wrap-up | `/sp:dev-wrap`, `/sp:dev-wrapall` |
630
- | `docs-pipeline.yaml` | Docs-only task execution | `/sp:dev-run --mode implement` |
631
631
  | `wayfinder-resolution.yaml` | Wayfinder ticket resolution loop | `spur workflow run` (free-form) |
632
632
 
633
633
  ### Lifecycle operations
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Turn a vague idea into a feature with AC and a decomposed task batch — discovery, idea-eval, feature-create, AC, feature-check, system-design, decompose, batch-create (Design by default), handoff
3
3
  role: planner
4
- argument-hint: "\"<idea>\" [--auto] [--skip-design] [--approve-taste] [--agent <inline|auto|name>]"
4
+ argument-hint: "\"<idea>\" [--from-file <path>] [--auto] [--skip-design] [--approve-taste] [--agent <inline|auto|name>]"
5
5
  allowed-tools: ["Bash", "Read", "Skill", "AskUserQuestion"]
6
6
  ---
7
7
 
@@ -14,7 +14,8 @@ contract below maps to that workflow's transitions.
14
14
 
15
15
  | Flag | Description | Default |
16
16
  | --- | --- | --- |
17
- | `"<idea>"` | Vague idea to turn into a feature with AC and tasks. | required |
17
+ | `"<idea>"` | Vague idea to turn into a feature with AC and tasks. | required (or `--from-file`) |
18
+ | `--from-file` `<path>` | Read the idea from a file instead of the positional argument. Mutually exclusive with `"<idea>"` — exactly one must be present. The file's contents become the verbatim idea text (trimmed of surrounding whitespace only). Useful for long or multiline asks that are awkward to quote. | off |
18
19
  | `--auto` | Skip objective HITL gates only (taste gates still pause). | off |
19
20
  | `--skip-design` | Omit system-design and per-task Design. | off |
20
21
  | `--approve-taste` | With `--auto`: set idea_approved + design_approved so idea-eval / design-approval do not pause. | off |
@@ -45,5 +46,11 @@ vars as subsets of `--approve-taste` (`idea_approved` / `design_approved`). Pref
45
46
  - Omitted/`inline`: drive `idea-pipeline.yaml` through the [inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md). Do not launch `spur workflow run`, `spur agent run`, or a native subagent unless the operator explicitly requests delegation.
46
47
  - `auto`/name: launch `spur workflow run idea-pipeline.yaml --async`, observe with one `workflow trace --follow`, and only report cancellation as stopped when `workflow cancel --json` returns `killed: true`.
47
48
  - `Skill(skill="sp:spur-dev", args="idea $ARGUMENTS")`
49
+ - Pass the idea text through every hop **verbatim** — never paraphrase or shorten it in the nested
50
+ `Skill` call or stage prompts; long ideas lose their trailing asks when summarized at the hop.
51
+ - Before executing the pipeline's `start` state, persist the operator's idea argument (or the
52
+ `--from-file` contents) **unmodified** to `.spur/run/<run-id>-idea-input.md` (0887 R1); every
53
+ model-bearing stage prompt treats that file as the authoritative ask, and the precheck fails
54
+ the run when it is empty or missing.
48
55
  - Stage contract (discovery → idea-eval → feature-create → AC → feature-check → system-design →
49
56
  decompose → batch-create → ready-prepare → handoff): `plugins/sp/skills/spur-dev/references/dev-operations.md` § idea.
@@ -0,0 +1,33 @@
1
+ ---
2
+ description: Lens-routed refactoring with a preservation contract — taste lenses classify findings against the shared schema; cutting/breaking changes pause for operator approval
3
+ role: reviewer
4
+ argument-hint: "[<description>] [--scope <path>] [--focus <api|architect|tests|ui|auto>] [--fix <none|blockers-first|all>] [--check <cmd>] [--agent <inline|auto|name>] [--auto]"
5
+ allowed-tools: ["Bash", "Read", "Edit", "Skill"]
6
+ ---
7
+
8
+ # Dev Refactor
9
+
10
+ Wraps the **sp:code-refactoring** skill.
11
+
12
+ ## Argument Flags
13
+
14
+ | Flag | Description | Default |
15
+ | --- | --- | --- |
16
+ | `[<description>]` | Free-text steering for the lenses (e.g. "pagination consistency"). | — |
17
+ | `--scope` `<path>` | Path bound; no edit may land outside it. | working tree |
18
+ | `--focus` `<api\|architect\|tests\|ui\|auto>` | Lens set; comma list allowed (`api,tests`). | auto |
19
+ | `--fix` `<none\|blockers-first\|all>` | Apply policy; `blockers-first` = P1/P2. | none |
20
+ | `--check` `<cmd>` | Verification command for baseline and per-fix checks. | project gate |
21
+ | `--agent` `<inline\|auto\|name>` | Who runs the model-bearing analysis. | inline |
22
+ | `--auto` | Skip objective gates only; cutting/breaking taste gates still pause. | off |
23
+
24
+ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
25
+
26
+ ## Usage
27
+
28
+ /sp:dev-refactor [<description>] [--scope <path>] [--focus <api|architect|tests|ui|auto>] [--fix <none|blockers-first|all>] [--check <cmd>] [--agent <inline|auto|name>] [--auto]
29
+
30
+ ## Implementation
31
+
32
+ - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
33
+ - `Skill(skill="sp:code-refactoring", args="$ARGUMENTS")`
@@ -1,3 +1,4 @@
1
+ import { getEnvVars } from '@gobing-ai/ts-utils';
1
2
  /**
2
3
  * Shared host-agent identification helpers — the **only** host-agent detection path
3
4
  * in the sp plugin hooks.
@@ -22,11 +23,11 @@
22
23
  * 3. `TERM_PROGRAM` — terminal identifier (e.g. `claude`, `ghostty`)
23
24
  * 4. `SPUR_DEFAULT_AGENT` — configured default agent
24
25
  *
25
- * @param env - environment record (defaults to `process.env`)
26
+ * @param env - environment record (defaults to `getEnvVars()`)
26
27
  * @param fallback - value returned when no candidate matches (defaults to `undefined`)
27
28
  */
28
29
  export function resolveAgentHint(
29
- env: NodeJS.ProcessEnv = process.env,
30
+ env: NodeJS.ProcessEnv = getEnvVars(),
30
31
  fallback: string | undefined = undefined,
31
32
  ): string | undefined {
32
33
  const candidates = [env.SPUR_AGENT, env.CLAUDE_CODE_ENTRYPOINT, env.TERM_PROGRAM, env.SPUR_DEFAULT_AGENT];
@@ -45,11 +46,11 @@ export function resolveAgentHint(
45
46
  * 3. `OPENAI_MODEL` — OpenAI API model
46
47
  * 4. `CLAUDE_MODEL` — Claude Code model setting
47
48
  *
48
- * @param env - environment record (defaults to `process.env`)
49
+ * @param env - environment record (defaults to `getEnvVars()`)
49
50
  * @param fallback - value returned when no candidate matches (defaults to `undefined`)
50
51
  */
51
52
  export function resolveModelHint(
52
- env: NodeJS.ProcessEnv = process.env,
53
+ env: NodeJS.ProcessEnv = getEnvVars(),
53
54
  fallback: string | undefined = undefined,
54
55
  ): string | undefined {
55
56
  const candidates = [env.SPUR_MODEL, env.ANTHROPIC_MODEL, env.OPENAI_MODEL, env.CLAUDE_MODEL];
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bun
2
+
2
3
  /**
3
4
  * careful-guard — PreToolUse guard for destructive shell commands (task 0215, R3).
4
5
  *
@@ -21,6 +22,7 @@
21
22
  * adapter. This file owns the Claude Code payload/decision shape only.
22
23
  */
23
24
 
25
+ import { getEnvVar } from '@gobing-ai/ts-utils';
24
26
  import { classifyCommand } from './destructive-policy';
25
27
 
26
28
  interface ToolPayload {
@@ -43,7 +45,7 @@ function preToolUseDecision(decision: Decision, reason?: string): never {
43
45
  }
44
46
 
45
47
  async function main(): Promise<void> {
46
- if (process.env.SPUR_CAREFUL === 'off') preToolUseDecision('allow');
48
+ if (getEnvVar('SPUR_CAREFUL') === 'off') preToolUseDecision('allow');
47
49
 
48
50
  const stdinText = await Bun.stdin.text();
49
51
  let payload: ToolPayload;
@@ -23,6 +23,7 @@
23
23
  import { execSync } from 'node:child_process';
24
24
  import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
25
25
  import { join, resolve, sep } from 'node:path';
26
+ import { getEnvVar } from '@gobing-ai/ts-utils';
26
27
 
27
28
  /** Tools recorded by this hook (must match hooks.json PostToolUse matcher). */
28
29
  export const ALLOWED_TOOLS = new Set(['Bash', 'Grep', 'Glob', 'Read', 'Write', 'Edit']);
@@ -411,7 +412,7 @@ export function readContextFreshness(contextDir: string): string | null {
411
412
  // Entrypoint — thin wrapper; logic lives in {@link recordToolUseEvent} for unit coverage.
412
413
  if (import.meta.main) {
413
414
  void (async () => {
414
- const dir = join(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), '.spur', 'context');
415
+ const dir = join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context');
415
416
  try {
416
417
  const stdinText = await Bun.stdin.text();
417
418
  const payload = JSON.parse(stdinText) as ToolPayload;
@@ -15,6 +15,7 @@
15
15
  import { execSync } from 'node:child_process';
16
16
  import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
+ import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
18
19
  import { resolveAgentHint, resolveModelHint } from './agent-hint';
19
20
  import { checkContextFreshness } from './context-post-tool';
20
21
 
@@ -82,7 +83,7 @@ interface SessionFileBody {
82
83
  * a stale file is only reachable from inside a live agent run, where reusing it is the correct
83
84
  * answer anyway.
84
85
  */
85
- export function resolveActiveSession(dir: string, now: Date, env: NodeJS.ProcessEnv = process.env): string | null {
86
+ export function resolveActiveSession(dir: string, now: Date, env: NodeJS.ProcessEnv = getEnvVars()): string | null {
86
87
  let raw: string;
87
88
  try {
88
89
  raw = readFileSync(join(dir, '.session.json'), 'utf-8');
@@ -123,7 +124,7 @@ export function resolveActiveSession(dir: string, now: Date, env: NodeJS.Process
123
124
  */
124
125
  export function recordSessionStart(
125
126
  dir: string,
126
- env: NodeJS.ProcessEnv = process.env,
127
+ env: NodeJS.ProcessEnv = getEnvVars(),
127
128
  now: () => Date = () => new Date(),
128
129
  ): string | null {
129
130
  try {
@@ -195,7 +196,7 @@ export function recordSessionStart(
195
196
  // Entrypoint — kept minimal so unit coverage focuses on pure helpers above.
196
197
  if (import.meta.main) {
197
198
  try {
198
- recordSessionStart(join(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), '.spur', 'context'));
199
+ recordSessionStart(join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context'));
199
200
  } catch {
200
201
  /* fail-open */
201
202
  }
@@ -17,6 +17,7 @@
17
17
 
18
18
  import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
19
19
  import { join } from 'node:path';
20
+ import { getEnvVar } from '@gobing-ai/ts-utils';
20
21
 
21
22
  interface LedgerEvent {
22
23
  session: string;
@@ -53,7 +54,7 @@ function exitOk(): never {
53
54
  }
54
55
 
55
56
  async function main(): Promise<void> {
56
- const dir = join(process.env.CLAUDE_PROJECT_DIR ?? process.cwd(), '.spur', 'context');
57
+ const dir = join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context');
57
58
 
58
59
  const sessionFile = join(dir, '.session.json');
59
60
  if (!existsSync(sessionFile)) exitOk();
@@ -23,6 +23,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSy
23
23
  import { homedir } from 'node:os';
24
24
  import { isAbsolute, join, resolve } from 'node:path';
25
25
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
26
+ import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
26
27
  import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
27
28
  import { classifyCommand } from '../destructive-policy';
28
29
  import { couldBeTaskFile } from '../task-file-policy';
@@ -81,7 +82,7 @@ function resolveSpurTaskOwnership(filePath: string): TaskOwnership {
81
82
  spawnSync(cmd, args, { cwd: process.cwd(), encoding: 'utf-8', timeout: 8000 });
82
83
 
83
84
  // 1. SPUR_BIN env override (may include args) or `spur` on PATH
84
- const envBin = process.env.SPUR_BIN || 'spur';
85
+ const envBin = getEnvVar('SPUR_BIN') || 'spur';
85
86
  const envParts = envBin.split(' ');
86
87
  let res = run(envParts[0] ?? 'spur', [...envParts.slice(1), 'task', 'resolve', filePath, '--strict', '--json']);
87
88
  // Only 0 (owned) / 1 (unowned) are valid spur exit codes; 127 (interpreter
@@ -203,8 +204,8 @@ function initSession(): void {
203
204
  const sessionId = generateSessionId();
204
205
  const session = {
205
206
  session_id: sessionId,
206
- agent: resolveAgentHintShared(process.env, 'pi'),
207
- model: resolveModelHintShared(process.env),
207
+ agent: resolveAgentHintShared(getEnvVars(), 'pi'),
208
+ model: resolveModelHintShared(getEnvVars()),
208
209
  started_at: new Date().toISOString(),
209
210
  };
210
211
  writeFileSync(sessionFilePath(), `${JSON.stringify(session, null, 2)}\n`);
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import { spawnSync } from 'node:child_process';
23
+ import { getEnvVar } from '@gobing-ai/ts-utils';
23
24
  import { couldBeTaskFile } from './task-file-policy';
24
25
 
25
26
  interface ToolPayload {
@@ -40,7 +41,7 @@ function preToolUseDecision(decision: 'allow' | 'deny', reason?: string): never
40
41
 
41
42
  /** Resolve whether a path is owned by a Spur task via `spur task resolve --strict --json`. */
42
43
  function resolveSpurTaskOwnership(filePath: string, cwd: string): TaskOwnership {
43
- const spurBin = process.env.SPUR_BIN || 'spur';
44
+ const spurBin = getEnvVar('SPUR_BIN') || 'spur';
44
45
  const parts = spurBin.split(' ');
45
46
  const cmd = parts[0] ?? 'spur';
46
47
  const args = [...parts.slice(1), 'task', 'resolve', filePath, '--strict', '--json'];
@@ -54,7 +55,7 @@ function resolveSpurTaskOwnership(filePath: string, cwd: string): TaskOwnership
54
55
  }
55
56
 
56
57
  async function main(): Promise<void> {
57
- if (process.env.SPUR_WRITE_GUARD === 'off') preToolUseDecision('allow');
58
+ if (getEnvVar('SPUR_WRITE_GUARD') === 'off') preToolUseDecision('allow');
58
59
 
59
60
  const stdinText = await Bun.stdin.text();
60
61
  let payload: ToolPayload;
@@ -73,7 +74,7 @@ async function main(): Promise<void> {
73
74
  // cannot name a task file — that is every ordinary source edit.
74
75
  if (!couldBeTaskFile(filePath)) preToolUseDecision('allow');
75
76
 
76
- const ownership = resolveSpurTaskOwnership(filePath, process.env.CLAUDE_PROJECT_DIR ?? process.cwd());
77
+ const ownership = resolveSpurTaskOwnership(filePath, getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd());
77
78
  if (ownership === 'owned') {
78
79
  preToolUseDecision(
79
80
  'deny',