@gobing-ai/spur 0.3.93 → 0.3.94

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 (81) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
  3. package/config/rules/strict/runtime-boundaries.yaml +2 -1
  4. package/config/rules/structure/protected-files.yaml +4 -0
  5. package/config/workflows/task-pipeline.yaml +17 -26
  6. package/package.json +9 -9
  7. package/plugins/sp/README.md +1 -1
  8. package/plugins/sp/commands/dev-review-session.md +4 -4
  9. package/plugins/sp/commands/dev-review.md +16 -5
  10. package/plugins/sp/hooks/pi/guard-extension.ts +17 -36
  11. package/plugins/sp/lib/idea-handoff.generated.mjs +1 -1
  12. package/plugins/sp/lib/inline-run.generated.d.mts +2 -0
  13. package/plugins/sp/lib/inline-run.generated.mjs +29 -8
  14. package/plugins/sp/plugin.json +1 -1
  15. package/plugins/sp/scripts/inline-run-setup.mjs +42 -4
  16. package/plugins/sp/scripts/inline-run-setup.ts +78 -4
  17. package/plugins/sp/scripts/quality-gate.mjs +11 -4
  18. package/plugins/sp/scripts/quality-gate.ts +22 -7
  19. package/plugins/sp/scripts/residual-scan.mjs +1 -1
  20. package/plugins/sp/scripts/residual-scan.ts +2 -1
  21. package/plugins/sp/skills/session-review/SKILL.md +11 -9
  22. package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
  23. package/plugins/sp/skills/spur-dev/references/execution-batch.md +45 -17
  24. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +16 -3
  25. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +16 -2
  26. package/spur.js +19288 -20178
  27. package/web/_astro/BoardApp.C02hAHPO.js +1 -0
  28. package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.FTEs3-N8.js} +107 -105
  29. package/web/_astro/{TaskDetail.DCqiC-OZ.js → TaskDetail.C-GdsS-t.js} +1 -1
  30. package/web/_astro/arc.uuAf51IT.js +1 -0
  31. package/web/_astro/{architectureDiagram-3BPJPVTR.DM_vp_hO.js → architectureDiagram-3BPJPVTR.CGe629A1.js} +1 -1
  32. package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.D1mGCq3p.js} +1 -1
  33. package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.CMsolcde.js} +1 -1
  34. package/web/_astro/channel.fsgl7o5j.js +1 -0
  35. package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.CrGA3fik.js} +1 -1
  36. package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DsLVVla0.js} +1 -1
  37. package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.Dxc59Tfi.js} +1 -1
  38. package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.CaEVE2Wy.js} +4 -4
  39. package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.BiJ4HXeI.js} +1 -1
  40. package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.Mxf1fru5.js} +1 -1
  41. package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM.pyOWQixH.js} +1 -1
  42. package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.BmEsg4vr.js} +1 -1
  43. package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.BHhhMFTO.js} +1 -1
  44. package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.BHhhMFTO.js} +1 -1
  45. package/web/_astro/{cose-bilkent-S5V4N54A.B1wSL-Xb.js → cose-bilkent-S5V4N54A.2fH4YOlp.js} +1 -1
  46. package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.C2j1_lKL.js} +1 -1
  47. package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.hZ2NCTdT.js} +2 -2
  48. package/web/_astro/diagram-2AECGRRQ.Bfw5_EzK.js +43 -0
  49. package/web/_astro/diagram-5GNKFQAL.Bt1V_Tmk.js +10 -0
  50. package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.DEk-YFwp.js} +3 -3
  51. package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.CDjndtQm.js} +1 -1
  52. package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.cFnUHScG.js} +1 -1
  53. package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.DlhYp7NV.js} +5 -5
  54. package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DNTpsxfx.js} +4 -4
  55. package/web/_astro/{ganttDiagram-6RSMTGT7.BO81S85v.js → ganttDiagram-6RSMTGT7.DC_p36PI.js} +1 -1
  56. package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.DkEqNI0P.js} +1 -1
  57. package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.CrjioCTG.js} +1 -1
  58. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.BcK0CN8n.js} +5 -5
  59. package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.p0CbBDX1.js} +1 -1
  60. package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.puHIFt6J.js} +7 -7
  61. package/web/_astro/{linear.C2_IkbZT.js → linear.Bb-3a1d7.js} +1 -1
  62. package/web/_astro/mermaid.core.CJDgXOJs.js +301 -0
  63. package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BoAZEI9t.js} +2 -2
  64. package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CJNSdqY7.js} +3 -3
  65. package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.DtTy7_0Z.js} +1 -1
  66. package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.BNYV98Xg.js} +3 -3
  67. package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DRq9DGHp.js} +4 -4
  68. package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B7KdBFCm.js} +1 -1
  69. package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CD62ZJ2H.js} +1 -1
  70. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.CeVk1TAj.js} +1 -1
  71. package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.C_ZPA5tT.js} +3 -3
  72. package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.CrThO_FQ.js} +5 -5
  73. package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.DSZCA5nl.js} +3 -3
  74. package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.KI7baTuk.js} +1 -1
  75. package/web/index.html +1 -1
  76. package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
  77. package/web/_astro/arc.CPwg6Rw0.js +0 -1
  78. package/web/_astro/channel.MYZLKNwy.js +0 -1
  79. package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
  80. package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
  81. 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.94",
11
11
  "source": "./plugins/sp"
12
12
  }
13
13
  ]
@@ -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)."
@@ -286,6 +286,13 @@ states:
286
286
  # a silent no-op routes the run to `failed` here instead of drifting
287
287
  # into test/review and being caught a full pass later.
288
288
  requireDiff: true
289
+ # Verification-only re-run admission (task 0976): when requireDiff
290
+ # sees zero non-corpus changes because the implementation already
291
+ # landed through another path, `spur task check <wbs>` PASS admits
292
+ # the empty implement as a certification pass; a check failure
293
+ # keeps the 0424 R3 empty-implement violation. Static option —
294
+ # deliberately no run-var knob (ADR-115).
295
+ requireDiffAllowCleanCheck: true
289
296
  # 0933 R26: escalation contract — the agent.run option names the QUESTION
290
297
  # file (the pause signal, deleted pre-dispatch for freshness); a paused
291
298
  # attempt (agent wrote its question there and exited 0) succeeds with
@@ -577,31 +584,24 @@ states:
577
584
  Deterministic failure-class router on a red gate (0943 R3). The engine has no onEnter
578
585
  conditionals, so the FAIL branch from `test`/`test-recheck` lands here as its own
579
586
  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.
587
+ `triage`. `decide failure-class` reads the bounded gate findings and routes: fix →
588
+ test-fix (the bounded repair hop; its default, recorded as `source: default` when no
589
+ backend decides — 0976 R2), stop → failed with terminalReason failed-check. A
590
+ missing/corrupt decision fails closed to failed-check. (0976 R2 collapsed the former
591
+ `retryable` lane: the declared default made it unreachable on the inline path.)
584
592
  onEnter:
585
593
  - kind: decide
586
594
  options:
587
595
  id: failure-class
588
596
  method: choice
589
597
  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]
598
+ The quality gate failed. Is the failure fixable by the repair agent, or should
599
+ the run stop here?
600
+ choices: [fix, stop]
594
601
  default: fix
595
602
  evidence:
596
603
  - .spur/run/${vars.wbs}-test-gate.findings
597
604
  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
605
 
606
606
  - id: review
607
607
  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`.
@@ -1007,8 +1007,7 @@ transitions:
1007
1007
  options:
1008
1008
  command: 'test "$mode" != fast'
1009
1009
  # ── 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.
1010
+ # attempt cap, then the fix lane, then the fail-closed defense. Declaration order matters.
1012
1011
  - from: test-fail-triage
1013
1012
  to: failed
1014
1013
  terminalReason: failed-check
@@ -1021,7 +1020,7 @@ transitions:
1021
1020
  - from: test-fail-triage
1022
1021
  to: failed
1023
1022
  terminalReason: retry-exhausted
1024
- description: Attempt cap reached — qualityGateMaxFixAttempts bounds the retryable recheck and fix lanes alike.
1023
+ description: Attempt cap reached — qualityGateMaxFixAttempts bounds the fix lane.
1025
1024
  # (warn) 5 commands: named fix attempts (legibility, 0874).
1026
1025
  guard:
1027
1026
  kind: shell
@@ -1029,14 +1028,6 @@ transitions:
1029
1028
  command: >-
1030
1029
  fix_attempts="$(cat .spur/run/$wbs-test-fix-attempt 2>/dev/null || echo 0)";
1031
1030
  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
1031
  - from: test-fail-triage
1041
1032
  to: test-fix
1042
1033
  description: Failure class fix and under the cap — the bounded repair hop, as before.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.3.93",
3
+ "version": "0.3.94",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -53,14 +53,14 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@commander-js/extra-typings": "^14.0.0",
56
- "@gobing-ai/ts-db": "^0.5.7",
57
- "@gobing-ai/ts-ai-runner": "^0.5.7",
58
- "@gobing-ai/ts-dual-workflow-engine": "^0.5.7",
59
- "@gobing-ai/ts-infra": "^0.5.7",
60
- "@gobing-ai/ts-llm-jsonl-importer": "^0.5.7",
61
- "@gobing-ai/ts-rule-engine": "^0.5.7",
62
- "@gobing-ai/ts-runtime": "^0.5.7",
63
- "@gobing-ai/ts-utils": "^0.5.7",
56
+ "@gobing-ai/ts-db": "^0.5.9",
57
+ "@gobing-ai/ts-ai-runner": "^0.5.9",
58
+ "@gobing-ai/ts-dual-workflow-engine": "^0.5.9",
59
+ "@gobing-ai/ts-infra": "^0.5.9",
60
+ "@gobing-ai/ts-llm-jsonl-importer": "^0.5.9",
61
+ "@gobing-ai/ts-rule-engine": "^0.5.9",
62
+ "@gobing-ai/ts-runtime": "^0.5.9",
63
+ "@gobing-ai/ts-utils": "^0.5.9",
64
64
  "@types/bun": "1.3.14",
65
65
  "@types/figlet": "^1.7.0",
66
66
  "@types/node-notifier": "8.0.5",
@@ -102,7 +102,7 @@ list this README is checked against.
102
102
  | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
103
103
  | `dev-next` | Status-aware router — inspect a task (or next frontier under a feature), dispatch the best existing `/sp:dev-*` command, chain on clean success |
104
104
  | `dev-run` | Run a task — full pipeline (precheck→implement→test→review→approve→verify→record→done) or single-step (implement) |
105
- | `dev-review` | Multi-dimensional review for a task or path — functional requirements traceability, SECUA framework, and architectural depth |
105
+ | `dev-review` | Multi-dimensional review for a task or path (traceability, SECUA, architecture); `--triage` fixes small findings, files the rest as tasks |
106
106
  | `dev-pr-review` | GitHub Codex PR review — prepare/reuse a PR, request `@codex review`, collect findings, validate/fix/re-review (fix mode); workflow-backed spine |
107
107
  | `dev-verify` | Verify a task against its requirements and Acceptance Criteria — traceability check producing a PASS/PARTIAL/FAIL verdict with evidence |
108
108
  | `dev-unit` | Generate or extend tests until the unit target is met |
@@ -1,5 +1,5 @@
1
1
  ---
2
- description: "Review the active coding-agent session immediately: summarize outcomes, distinguish resolved and open issues with evidence, and propose bounded improvements. With --triage: apply pure-doc / one-to-two-line fixes inline, then file remaining findings as one new task. Triggers: review this session, session wrap-up, immediate retrospective, what happened, what was resolved, triage findings"
2
+ description: "Review the active coding-agent session immediately: summarize outcomes, distinguish resolved and open issues with evidence, and propose bounded improvements. With --triage: apply pure-doc / one-to-two-line fixes inline, then file remaining findings as one or more implement-ready tasks. Triggers: review this session, session wrap-up, immediate retrospective, what happened, what was resolved, triage findings"
3
3
  role: reviewer
4
4
  argument-hint: "[<focus>] [--triage]"
5
5
  allowed-tools: ["Bash", "Read", "Grep", "Glob", "Skill"]
@@ -11,8 +11,8 @@ Wraps the **sp:session-review** skill for a lightweight review of the active hos
11
11
  default it is report-only: current conversation plus read-only repository evidence, run inline so
12
12
  the session context is preserved — no workflow launch, history import, task creation, or
13
13
  remediation. With `--triage`, it first triages the findings, then applies direct fixes (pure
14
- documentation work and one-to-two-line fixes) inline and files everything remaining as exactly one
15
- new task for further fixing. The result includes a non-overlapping time breakdown with durations in
14
+ documentation work and one-to-two-line fixes) inline and files everything remaining as one or more
15
+ implement-ready tasks for further fixing. The result includes a non-overlapping time breakdown with durations in
16
16
  `M:SS` or `H:MM:SS` form and `n/a` for unavailable measurements.
17
17
 
18
18
  ## Argument Flags
@@ -20,7 +20,7 @@ new task for further fixing. The result includes a non-overlapping time breakdow
20
20
  | Flag | Description | Default |
21
21
  | --- | --- | --- |
22
22
  | `[<focus>]` | Optional question or operation to emphasize without excluding material session outcomes. | full active session |
23
- | `--triage` | After the report: bucket findings → apply pure-doc / 1–2-line fixes inline → create one task for the remainder. | off (report-only) |
23
+ | `--triage` | After the report: bucket findings → apply pure-doc / 1–2-line fixes inline → file the remainder as one or more implement-ready tasks. | off (report-only) |
24
24
 
25
25
  For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
26
26
 
@@ -1,7 +1,7 @@
1
1
  ---
2
- description: "Review code for a task or path — multi-dimensional review across functional traceability, SECUA quality, and architectural depth. Triggers: \"review this\", \"check the code\", \"SECUA review\", \"dev review\", \"audit this\"."
2
+ description: "Review code for a task or path — multi-dimensional review across functional traceability, SECUA quality, and architectural depth. With --triage: fix small findings directly, then file the rest as implement-ready tasks. Triggers: \"review this\", \"check the code\", \"SECUA review\", \"dev review\", \"audit this\", \"triage review findings\"."
3
3
  role: reviewer
4
- argument-hint: "[<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--fix (deprecated)]"
4
+ argument-hint: "[<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--triage] [--worktree [<name>]]"
5
5
  allowed-tools: ["Bash", "Read", "Skill"]
6
6
  ---
7
7
 
@@ -16,17 +16,28 @@ Wraps the **sp:functional-review**, **sp:code-verification**, and **sp:code-impr
16
16
  | `[<wbs\|path>]` | Task WBS or source path to review. | cwd |
17
17
  | `--agent` `<inline\|auto\|name>` | Who runs the model-bearing review. | omit |
18
18
  | `--focus` `<dims>` | Review dimensions (functional / SECUA / architecture). | all |
19
- | `--fix` | Deprecated no-op + warning; route remediation to /sp:dev-verify --fix. | off |
19
+ | `--triage` | After the review: fix small findings directly, file the rest as one or more implement-ready tasks. | off (report-only) |
20
+ | `--worktree` `[<name>]` | Run the triage in an isolated git worktree; FF-merge on success, retain on failure. Bare `--worktree` creates a fresh tree; `--worktree <name>` adopts an existing worktree by name/path/branch. Requires `--triage`. | off |
20
21
 
21
22
  For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
22
23
 
23
24
  ## Usage
24
25
 
25
- /sp:dev-review [<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--fix (deprecated)]
26
+ ```
27
+ /sp:dev-review [<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--triage] [--worktree [<name>]]
28
+ /sp:dev-review scripts --triage
29
+ /sp:dev-review 0962 --triage --worktree
30
+ ```
31
+
32
+ Put the target first: a bare token after `--worktree` is read as the worktree name.
26
33
 
27
34
  ## Implementation
28
35
 
29
36
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
30
37
  - WBS mode (`<wbs>`): `Skill(skill="sp:functional-review", args="<wbs> $ARGUMENTS")` + `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<wbs> $ARGUMENTS")` (functional traceability + SECUA framework + architectural depth; the three skills return review fragments and the coordinator (`sp:super-reviewer`) writes the combined `## Review` — F92 0593 R1)
31
38
  - Path mode (`<path>`): `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<path> $ARGUMENTS")` (advisory SECUA quality + architectural depth; performs no task mutation)
32
- - `--fix`: Deprecated (no-op + warning message; route remediation to `/sp:dev-verify --fix`). **`--next` removed** (feature H8, 2026-07-31) — it was a deprecated no-op; route progression to `/sp:dev-next`. **was: `--next` deprecated no-op.**
39
+ - `--triage`: after the review returns, run the triage protocol in [dev-operations.md § 2. review](../skills/spur-dev/references/dev-operations.md#2-review) **inline in this session** — the review skills and `sp:super-reviewer` stay report-only.
40
+ - Without `--worktree`, every write lands in the current working tree on the current branch: no branch creation, no checkout, no commit.
41
+ - `--worktree [<name>]`: the [execution-batch.md § Worktree isolation](../skills/spur-dev/references/execution-batch.md#worktree-isolation---worktree-name) lifecycle as a run of one (marker `command` = `dev-review`, `selector` = the target). Rejected without `--triage`.
42
+ While it runs, fixes and filed tasks land in the worktree copy; the main tree shows them only after the FF-merge.
43
+ - `--fix`: Deprecated (still accepted: no-op + warning; route remediation to `--triage` or `/sp:dev-verify --fix`). **`--next` removed** (feature H8, 2026-07-31) — it was a deprecated no-op; route progression to `/sp:dev-next`. **was: `--next` deprecated no-op.**
@@ -25,6 +25,7 @@ import { isAbsolute, join, resolve } from 'node:path';
25
25
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
26
26
  import { getEnvVar, getEnvVars } from '../../lib/env';
27
27
  import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
28
+ import { cappedByteLength, truncateSummary } from '../context-post-tool';
28
29
  import { classifyCommand } from '../destructive-policy';
29
30
  import { couldBeTaskFile } from '../task-file-policy';
30
31
 
@@ -36,8 +37,6 @@ import { couldBeTaskFile } from '../task-file-policy';
36
37
  const spurContextDir = (): string => join(process.cwd(), '.spur', 'context');
37
38
  const sessionFilePath = (): string => join(spurContextDir(), '.session.json');
38
39
  const ledgerFilePath = (): string => join(spurContextDir(), 'token-ledger.jsonl');
39
- const REDACTION_CAP = 4096;
40
- const SUMMARY_MAX_CHARS = 200;
41
40
 
42
41
  // ─── Helpers ─────────────────────────────────────────────────────────────
43
42
 
@@ -122,39 +121,17 @@ interface ToolEvent {
122
121
  tool_input?: Record<string, unknown>;
123
122
  }
124
123
 
125
- function estimateTokenCount(text: string): number {
126
- if (!text) return 0;
127
- // Rough estimate: ~4 chars per token for English text
128
- return Math.ceil(text.length / 4);
129
- }
130
-
124
+ // Summaries persist to disk, so they go through the same scrubber as the Claude hook —
125
+ // the local copy this replaced stored the raw command (secrets included) as `summary`.
131
126
  function summarizeToolEvent(event: ToolEvent): string {
132
127
  const input = event.tool_input ?? {};
133
128
  const candidates = [input.file_path, input.command, input.pattern, input.glob_pattern, input.glob, input.path];
134
129
  for (const c of candidates) {
135
- if (typeof c === 'string' && c.trim()) {
136
- const s = c.trim();
137
- return s.length > SUMMARY_MAX_CHARS ? `${s.slice(0, SUMMARY_MAX_CHARS - 3)}...` : s;
138
- }
130
+ if (typeof c === 'string' && c.trim()) return truncateSummary(c);
139
131
  }
140
132
  return `(${event.tool_name ?? 'unknown'})`;
141
133
  }
142
134
 
143
- function redactText(text: string): string {
144
- // Strip obvious secret patterns
145
- let result = text.slice(0, REDACTION_CAP);
146
- result = result.replace(
147
- /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----/g,
148
- '[REDACTED: PRIVATE KEY]',
149
- );
150
- result = result.replace(/ghp_[A-Za-z0-9]{36}/g, '[REDACTED: GITHUB_TOKEN]');
151
- result = result.replace(/gho_[A-Za-z0-9]{36}/g, '[REDACTED: GITHUB_TOKEN]');
152
- result = result.replace(/sk-[A-Za-z0-9]{20,}/g, '[REDACTED: API_KEY]');
153
- result = result.replace(/api[_-]key['"]?\s*[:=]\s*['"][A-Za-z0-9_-]{16,}['"]/gi, '[REDACTED: API_KEY]');
154
- result = result.replace(/AKIA[0-9A-Z]{16}/g, '[REDACTED: AWS_KEY]');
155
- return result;
156
- }
157
-
158
135
  function appendToLedger(event: ToolEvent, command: string | undefined): void {
159
136
  try {
160
137
  if (!existsSync(spurContextDir())) return;
@@ -162,16 +139,19 @@ function appendToLedger(event: ToolEvent, command: string | undefined): void {
162
139
  if (!sessionId) return;
163
140
 
164
141
  const summary = summarizeToolEvent(event);
165
- const tokens = command ? estimateTokenCount(redactText(command)) : 0;
142
+ const tokens = command ? Math.ceil(cappedByteLength(command) / 4) : 0;
143
+ const input = event.tool_input ?? {};
144
+ // Pi tool names are lowercase (`read`) and its write/edit tools take `path`, not `file_path`.
145
+ const path = typeof input.file_path === 'string' ? input.file_path : input.path;
166
146
 
167
147
  const ledgerEntry = JSON.stringify({
168
148
  session: sessionId,
169
- type: event.tool_name === 'Read' ? 'read' : 'write',
149
+ type: event.tool_name?.toLowerCase() === 'read' ? 'read' : 'write',
170
150
  tool: event.tool_name,
171
- path: event.tool_input?.file_path ?? null,
151
+ file: typeof path === 'string' ? path : undefined,
172
152
  summary,
173
153
  tokens,
174
- timestamp: new Date().toISOString(),
154
+ ts: new Date().toISOString(),
175
155
  });
176
156
 
177
157
  appendFileSync(ledgerFilePath(), `${ledgerEntry}\n`);
@@ -216,7 +196,7 @@ function initSession(): void {
216
196
  type: 'session_start',
217
197
  agent: session.agent,
218
198
  model: session.model,
219
- timestamp: session.started_at,
199
+ ts: session.started_at,
220
200
  });
221
201
  appendFileSync(ledgerFilePath(), `${startEntry}\n`);
222
202
  } catch {
@@ -256,7 +236,7 @@ function cleanupSession(): void {
256
236
  reads,
257
237
  writes,
258
238
  tokens,
259
- timestamp: new Date().toISOString(),
239
+ ts: new Date().toISOString(),
260
240
  });
261
241
  appendFileSync(ledgerFilePath(), `${endEntry}\n`);
262
242
 
@@ -272,8 +252,9 @@ function cleanupSession(): void {
272
252
  export default function (pi: ExtensionAPI): void {
273
253
  // ── task-write-guard + careful-guard ──────────────────────────────
274
254
  pi.on('tool_call', async (event, ctx) => {
275
- // task-write-guard: block Write/Edit to Spur task files
276
- if (event.toolName === 'write' || event.toolName === 'edit') {
255
+ // task-write-guard: block Write/Edit to Spur task files.
256
+ // Both guards honor the same `off` escape hatches as the Claude hooks.
257
+ if ((event.toolName === 'write' || event.toolName === 'edit') && getEnvVar('SPUR_WRITE_GUARD') !== 'off') {
277
258
  const input = event.input as Record<string, unknown> | undefined;
278
259
  // Pi's write/edit tools use `path` (not Claude Code's `file_path`)
279
260
  const filePath = resolveInputPath(input);
@@ -287,7 +268,7 @@ export default function (pi: ExtensionAPI): void {
287
268
  }
288
269
 
289
270
  // careful-guard: warn on destructive Bash commands
290
- if (event.toolName === 'bash') {
271
+ if (event.toolName === 'bash' && getEnvVar('SPUR_CAREFUL') !== 'off') {
291
272
  const input = event.input as Record<string, unknown> | undefined;
292
273
  const command = typeof input?.command === 'string' ? input.command : '';
293
274
  const hit = command ? classifyCommand(command) : null;
@@ -322,7 +322,7 @@ END`;var MEASURE_COLS="messages, tool_calls, skill_calls, fresh_input_tokens, ca
322
322
  ''
323
323
  )
324
324
  ELSE ''
325
- END`;var POST_WATERMARK_TABLES=[...KEYED_ROLLUP_TABLES,...GLOBAL_RANKED_ROLLUP_TABLES,"history_daily_stats","history_board_source_daily"];var HISTORY_IMPORT_SCHEMA_VERSION="0.5.7",HISTORY_IMPORT_SCHEMA_SQL=`
325
+ END`;var POST_WATERMARK_TABLES=[...KEYED_ROLLUP_TABLES,...GLOBAL_RANKED_ROLLUP_TABLES,"history_daily_stats","history_board_source_daily"];var HISTORY_IMPORT_SCHEMA_VERSION="0.5.9",HISTORY_IMPORT_SCHEMA_SQL=`
326
326
  CREATE TABLE IF NOT EXISTS history_import_checkpoint (
327
327
  source TEXT NOT NULL,
328
328
  source_file TEXT NOT NULL,
@@ -1,6 +1,8 @@
1
1
  /** GENERATED by scripts/commands/bundle-plugin-lib.ts — do not edit. Requires Bun for SQLite. */
2
2
  export declare const createOrAttachInlineRun: typeof import('@gobing-ai/spur-app').createOrAttachInlineRun;
3
3
  export declare const openInlineRunProjectDb: typeof import('@gobing-ai/spur-app').openInlineRunProjectDb;
4
+ export declare const persistWorktreeRuns: typeof import('@gobing-ai/spur-app').persistWorktreeRuns;
5
+ export declare const runDecideForInlineRun: typeof import('@gobing-ai/spur-app').runDecideForInlineRun;
4
6
  export declare const computeProofInputFingerprint: typeof import('@gobing-ai/spur-app').computeProofInputFingerprint;
5
7
  export declare const readProofInputContents: typeof import('@gobing-ai/spur-app').readProofInputContents;
6
8
  export declare const createWorkflowActionTraceWriter: typeof import('@gobing-ai/spur-app').createWorkflowActionTraceWriter;