@gobing-ai/spur 0.4.8 → 0.4.9

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 (145) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/pipeline-budgets.json +2 -2
  3. package/config/rules/boundary/config-loading-ownership.yaml +1 -0
  4. package/config/rules/boundary/sp-plugin-standalone.yaml +1 -1
  5. package/config/rules/boundary/sp-script-placement.yaml +1 -1
  6. package/config/rules/docs/sourced-api-claims.yaml +45 -7
  7. package/config/rules/strict/runtime-boundaries.yaml +40 -1
  8. package/config/rules/structure/protected-files.yaml +1 -1
  9. package/config/rules/surface/check-cli-surface.yaml +33 -0
  10. package/config/rules/typescript/no-debugger.yaml +3 -0
  11. package/config/workflow-candidates.json +2 -2
  12. package/config/workflows/feature-verification.yaml +6 -11
  13. package/config/workflows/history-anatomy.yaml +14 -28
  14. package/config/workflows/idea-pipeline.yaml +6 -19
  15. package/config/workflows/pr-review.yaml +9 -16
  16. package/config/workflows/task-pipeline.yaml +16 -23
  17. package/config/workflows/wrapup-pipeline.yaml +21 -63
  18. package/package.json +3 -3
  19. package/plugins/sp/README.md +70 -37
  20. package/plugins/sp/agents/super-planner.md +5 -5
  21. package/plugins/sp/agents/super-reviewer.md +2 -2
  22. package/plugins/sp/commands/dev-changelog.md +1 -1
  23. package/plugins/sp/commands/dev-daily.md +1 -1
  24. package/plugins/sp/commands/dev-pr-review.md +1 -1
  25. package/plugins/sp/commands/dev-review-session.md +1 -1
  26. package/plugins/sp/commands/dev-runall.md +4 -4
  27. package/plugins/sp/commands/dev-verify.md +4 -6
  28. package/plugins/sp/commands/dev-verifyall.md +4 -5
  29. package/plugins/sp/hooks/hooks.json +11 -1
  30. package/plugins/sp/lib/context-post-tool.ts +1 -1
  31. package/plugins/sp/lib/context-session-start.ts +1 -1
  32. package/plugins/sp/lib/context-session-stop.ts +1 -1
  33. package/plugins/sp/lib/destructive-policy.ts +65 -1
  34. package/plugins/sp/plugin.json +2 -5
  35. package/plugins/sp/{hooks → scripts/hooks}/agent-lifecycle.ts +1 -1
  36. package/plugins/sp/scripts/hooks/careful-guard.ts +94 -0
  37. package/plugins/sp/{hooks → scripts/hooks}/context-post-tool.ts +2 -2
  38. package/plugins/sp/{hooks → scripts/hooks}/context-session-start.ts +1 -1
  39. package/plugins/sp/{hooks → scripts/hooks}/context-session-stop.ts +1 -1
  40. package/plugins/sp/{hooks → scripts/hooks}/fleet-guest-stop.ts +6 -4
  41. package/plugins/sp/{hooks → scripts/hooks}/task-write-guard.ts +38 -21
  42. package/plugins/sp/skills/code-implementation/SKILL.md +1 -1
  43. package/plugins/sp/skills/code-verification/SKILL.md +8 -8
  44. package/plugins/sp/skills/code-verification/references/secu-review.md +4 -3
  45. package/plugins/sp/skills/code-verification/references/verdict-schema.md +9 -1
  46. package/plugins/sp/skills/daily-summary/SKILL.md +4 -5
  47. package/plugins/sp/skills/dogfood-testing/SKILL.md +9 -9
  48. package/plugins/sp/skills/dogfood-testing/references/report-template.md +1 -1
  49. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -2
  50. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  51. package/plugins/sp/skills/parallel-execution/SKILL.md +1 -1
  52. package/plugins/sp/skills/pr-reviewing/SKILL.md +14 -15
  53. package/plugins/sp/skills/session-review/SKILL.md +3 -3
  54. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  55. package/plugins/sp/skills/spur-check/SKILL.md +14 -13
  56. package/plugins/sp/skills/spur-cli/SKILL.md +1 -1
  57. package/plugins/sp/skills/spur-cli/references/agent.md +12 -0
  58. package/plugins/sp/skills/spur-cli/references/builder.md +27 -6
  59. package/plugins/sp/skills/spur-cli/references/features.md +6 -1
  60. package/plugins/sp/skills/spur-cli/references/history.md +3 -0
  61. package/plugins/sp/skills/spur-cli/references/self.md +5 -3
  62. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +3 -0
  63. package/plugins/sp/skills/spur-cli/references/tasks.md +13 -3
  64. package/plugins/sp/skills/spur-cli/references/workflows.md +36 -1
  65. package/plugins/sp/skills/spur-dev/SKILL.md +1 -1
  66. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +1 -1
  67. package/plugins/sp/skills/spur-dev/references/dev-operations.md +12 -12
  68. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +10 -11
  69. package/plugins/sp/skills/spur-dev/references/execution-batch.md +16 -17
  70. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +1 -1
  71. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +8 -11
  72. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +6 -8
  73. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  74. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +33 -40
  75. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +24 -24
  76. package/plugins/sp/skills/spur-doctor/SKILL.md +5 -4
  77. package/spur.js +41901 -30598
  78. package/config/plugin-scripts.json +0 -116
  79. package/config/script-placement-baseline.json +0 -33
  80. package/plugins/sp/hooks/careful-guard.ts +0 -74
  81. package/plugins/sp/hooks/pi/guard-extension.ts +0 -304
  82. package/plugins/sp/lib/dogfood-pipeline.ts +0 -265
  83. package/plugins/sp/lib/dogfood-report.ts +0 -278
  84. package/plugins/sp/lib/history-anatomy.generated.d.mts +0 -112
  85. package/plugins/sp/lib/history-anatomy.generated.mjs +0 -691
  86. package/plugins/sp/lib/idea-handoff.generated.d.mts +0 -17
  87. package/plugins/sp/lib/idea-handoff.generated.mjs +0 -1446
  88. package/plugins/sp/lib/inline-run-app.ts +0 -68
  89. package/plugins/sp/lib/inline-run.generated.d.mts +0 -37
  90. package/plugins/sp/lib/inline-run.generated.mjs +0 -3103
  91. package/plugins/sp/lib/plan-projection.generated.d.mts +0 -197
  92. package/plugins/sp/lib/plan-projection.generated.mjs +0 -982
  93. package/plugins/sp/lib/quality-gate.generated.d.mts +0 -139
  94. package/plugins/sp/lib/quality-gate.generated.mjs +0 -30510
  95. package/plugins/sp/lib/residual-scan.generated.d.mts +0 -93
  96. package/plugins/sp/lib/residual-scan.generated.mjs +0 -383
  97. package/plugins/sp/lib/run-summary-core.ts +0 -225
  98. package/plugins/sp/lib/spur-bin.ts +0 -36
  99. package/plugins/sp/lib/step-profile.generated.d.mts +0 -71
  100. package/plugins/sp/lib/step-profile.generated.mjs +0 -174
  101. package/plugins/sp/lib/transcript.ts +0 -112
  102. package/plugins/sp/scripts/batch-plan.mjs +0 -1096
  103. package/plugins/sp/scripts/batch-plan.ts +0 -145
  104. package/plugins/sp/scripts/batch-preflight.mjs +0 -423
  105. package/plugins/sp/scripts/batch-preflight.ts +0 -270
  106. package/plugins/sp/scripts/commit-guard.mjs +0 -226
  107. package/plugins/sp/scripts/commit-guard.ts +0 -248
  108. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +0 -626
  109. package/plugins/sp/scripts/daily-summary/daily-summary.ts +0 -855
  110. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +0 -211
  111. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +0 -110
  112. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +0 -282
  113. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +0 -98
  114. package/plugins/sp/scripts/feature-verification-steps.mjs +0 -82
  115. package/plugins/sp/scripts/feature-verification-steps.ts +0 -164
  116. package/plugins/sp/scripts/history-anatomy-cache.mjs +0 -931
  117. package/plugins/sp/scripts/history-anatomy-cache.ts +0 -247
  118. package/plugins/sp/scripts/idea-handoff.mjs +0 -32
  119. package/plugins/sp/scripts/idea-handoff.ts +0 -47
  120. package/plugins/sp/scripts/inline-run-dispatch.mjs +0 -131
  121. package/plugins/sp/scripts/inline-run-dispatch.ts +0 -115
  122. package/plugins/sp/scripts/inline-run-setup.mjs +0 -746
  123. package/plugins/sp/scripts/inline-run-setup.ts +0 -198
  124. package/plugins/sp/scripts/persist-out-check.mjs +0 -217
  125. package/plugins/sp/scripts/persist-out-check.ts +0 -249
  126. package/plugins/sp/scripts/pr-reviewing.mjs +0 -712
  127. package/plugins/sp/scripts/pr-reviewing.ts +0 -952
  128. package/plugins/sp/scripts/quality-gate.mjs +0 -30491
  129. package/plugins/sp/scripts/quality-gate.ts +0 -80
  130. package/plugins/sp/scripts/residual-scan.mjs +0 -634
  131. package/plugins/sp/scripts/residual-scan.ts +0 -248
  132. package/plugins/sp/scripts/run-summary.mjs +0 -758
  133. package/plugins/sp/scripts/run-summary.ts +0 -107
  134. package/plugins/sp/scripts/script-root.mjs +0 -129
  135. package/plugins/sp/scripts/script-root.ts +0 -199
  136. package/plugins/sp/scripts/session-timeline.mjs +0 -359
  137. package/plugins/sp/scripts/session-timeline.ts +0 -190
  138. package/plugins/sp/scripts/task-diffstat.mjs +0 -181
  139. package/plugins/sp/scripts/task-diffstat.ts +0 -219
  140. package/plugins/sp/scripts/workflow-step-profile.mjs +0 -338
  141. package/plugins/sp/scripts/workflow-step-profile.ts +0 -165
  142. package/plugins/sp/scripts/wrapup-drift-probe.mjs +0 -190
  143. package/plugins/sp/scripts/wrapup-drift-probe.ts +0 -261
  144. package/plugins/sp/scripts/wrapup-steps.mjs +0 -627
  145. package/plugins/sp/scripts/wrapup-steps.ts +0 -891
@@ -272,16 +272,16 @@ plugins/sp/
272
272
  │ └── wayfinder/ # Multi-session investigation maps (SKILL.md only)
273
273
  ├── commands/ # Slash-command wrappers — the SSOT (hand-editable thin wrappers; see Commands below)
274
274
  ├── agents/ # 4 specialist subagents (expert-spur, super-coder, super-planner, super-reviewer)
275
- ├── hooks/ # hooks.json + one thin entry per registered identity
276
- │ # + task-write-guard.ts (compat shim) + careful-guard.ts (opt-in, unwired)
277
- │ # + pi/guard-extension.ts (plugin.json `extensions.pi` entry point)
278
- │ # entries delegate to lib cores (task 1174); hook suites live in tests/hooks/
275
+ ├── hooks/ # hooks.json (the registry) only — Pi runs it through superskill's native Pi adapter
276
+ │ # — no hook entries here; see scripts/hooks/ (superskill ADR-039)
279
277
  ├── lib/ # Handwritten plugin cores — context-{post-tool,session-start,session-stop}.ts,
280
278
  │ # agent-hint.ts, context-owner.ts, destructive-policy.ts, task-file-policy.ts,
281
- │ # env.ts, plus generated *.generated.mjs|.d.mts bundles over packages/app
282
- ├── scripts/ # Executable helpers, split from prompts (ADR-031) — batch-preflight.ts + scripts/<skill>/
283
- │ # (daily-summary: daily-summary.ts; dogfood-testing: {detect-pipeline-driving, validate-report}.ts)
284
- ├── tests/ # Plugin tests — skill-structure.test.ts + hooks/ (guard, context, Pi suites)
279
+ │ # env.ts (no generated bundles: script absorption retired them)
280
+ ├── scripts/ # Hook entries only (script absorption complete, task 1213 —
281
+ │ # no script-root, no .mjs twins, no manifest)
282
+ │ └── hooks/ # One entry per hook identity, run by `superskill hook run sp <id>`;
283
+ │ # entries delegate to lib cores; suites live in tests/hooks/
284
+ ├── tests/ # Plugin tests — skill-structure.test.ts + hooks/ (guard, context, lifecycle suites)
285
285
  │ # + evals/ (behavioral eval corpus + judge; see tests/evals/README.md,
286
286
  │ # runner at scripts/commands/skill-eval.ts) + per-skill suites
287
287
  ├── plugin.json # Marketplace entry
@@ -348,7 +348,7 @@ surface or run one workflow. All skills target the same five core platforms: `cl
348
348
  | `conflict-finding` | 1.0 | Authority-aware semantic audit — four-pillar (source/task/feature/authority) conflict discovery, claim-specific authority resolution, reproducible evidence, confirmed owner-routed remediation; backs `/sp:dev-find-conflict` |
349
349
  | `fleet-join` | 1.0 | Guest fleet occupancy — join as a live guest occupant, loop `spur agent wait --inbox` to pull work, reply through `spur message reply`, then leave; G73 (task 1081) |
350
350
  | `next-feature` | 1.0 | Prompt-first feature frontier prioritizer — sync-first precondition, B3 actionability gate (cited, never restated), tiered rubric over measured signals, D1–D4 defect proposals conforming to the restructure map schema; backs `/sp:dev-find-next` |
351
- | `pr-reviewing` | 1.0 | GitHub Codex PR review — PR prepare/reuse, `@codex review` request with per-HEAD dedupe, bounded polling, findings normalization, validated fix + re-review; spine SSOT `pr-review.yaml` + `scripts/pr-reviewing.ts`; backs `/sp:dev-pr-review` |
351
+ | `pr-reviewing` | 1.0 | GitHub Codex PR review — PR prepare/reuse, `@codex review` request with per-HEAD dedupe, bounded polling, findings normalization, validated fix + re-review; spine SSOT `pr-review.yaml` + `spur builder pr`; backs `/sp:dev-pr-review` |
352
352
  | `indexed-context` | 1.0 | Cross-agent project context — anatomy/learnings/pitfalls/buglog/memory in `.spur/context/`; hook-tracked token-ledger; graceful degradation on agents without hooks |
353
353
  | `history-anatomy` | 1.0 | Diagnostic interpretation owner over already-imported history — daily/ad-hoc mode contract, closed finding taxonomy, twelve-section report contract, `enrich`/`validate` rubrics; no workflow launch, no JSONL fallback, no corpus mutation |
354
354
  | `session-review` | 1.1 | Inline review of the active coding-agent session — compact outcomes, evidence-backed resolved/open issue classification, proposal-only improvements, and next actions; no workflow, import, delegation, or mutation |
@@ -411,14 +411,10 @@ hand-editable source for the operator command surface. Per-platform adapters are
411
411
  output** owned by `superskill` (`superskill install sp`) and never committed here. Plugin `sp` ships
412
412
  no per-platform artifacts — only the platform-independent thin wrappers.
413
413
 
414
- **Install-time `lib/` ownership (1120).** Staging and refreshing the plugin's sibling
415
- `lib/inline-run.generated.mjs` app bundle into the shared scripts root is `superskill`'s install
416
- contract (superskill commit `b42961b`, first release after 0.3.35): spur ships the bundle, superskill
417
- places it. The inline scripts carry a skew guard for the window where the installed bundle is older
418
- than its scripts (e.g. superskill ≤ 0.3.35): they fail with a named version-skew error — bundle
419
- path, missing exports, and both remedies — instead of an `is not a function` TypeError. Until a
420
- superskill release containing `b42961b` is installed and `sp` reinstalled, pass
421
- `--spur-bin <spur checkout>/apps/cli/src/index.ts`.
414
+ **No install-time app bundle.** The plugin used to ship a `lib/inline-run.generated.mjs` app bundle
415
+ that `superskill` staged beside the scripts (1120). Script absorption (PR-4..PR-6) moved every
416
+ consumer onto `spur <noun> <verb>`, so the bundle and its skew guard are gone: the skills call the
417
+ installed `spur` CLI directly.
422
418
 
423
419
  **Thin-wrapper contract** is enforced by `scripts/commands/validate-commands.ts`:
424
420
 
@@ -487,11 +483,12 @@ the agent provides an isolated context window.
487
483
  #### 4. Hooks (`hooks/`)
488
484
 
489
485
  Event-driven enforcement that runs automatically without user invocation. `hooks.json` registers
490
- six handlers across seven events:
486
+ seven handlers across seven events:
491
487
 
492
488
  | Event | Matcher | Handler | Timeout |
493
489
  | -------------- | ------------------------------------- | ---------------------------------------------- | ------- |
494
490
  | `PreToolUse` | `Write\|Edit` | `superskill hook run sp task-write-guard` | 10s |
491
+ | `PreToolUse` | `Bash` | `superskill hook run sp careful-guard` | 10s |
495
492
  | `PostToolUse` | `Bash\|Grep\|Glob\|Read\|Write\|Edit` | `superskill hook run sp context-post-tool` | 10s |
496
493
  | `SessionStart` | — | `superskill hook run sp context-session-start` | 15s |
497
494
  | `SessionStart`, `UserPromptSubmit`, `Stop` | — | `superskill hook run sp agent-lifecycle` | 5s |
@@ -515,14 +512,58 @@ hint on first launch, and `context-session-stop` closes out the session record.
515
512
  `SPUR_SPEC_ID` is set, so outside a fleet it makes no call. `fleet-guest-stop` delivers pending
516
513
  inbox work to a session joined as a fleet guest at turn end; any other session passes through.
517
514
 
518
- **Available but unwired:** `careful-guard.ts` ships in `hooks/` (its suite is
519
- `tests/hooks/careful-guard.test.ts`) as an opt-in `PreToolUse` guard that asks before destructive
520
- shell commands (`rm -rf`, `DROP TABLE`,
521
- `git push --force`, …) — it is **not** registered in `hooks.json`. Fail-open by contract; escape
522
- hatch `SPUR_CAREFUL=off`.
515
+ **Destructive-command guard.** `careful-guard` (task 0215 R3) fires on every `Bash` tool call and
516
+ asks the operator to confirm destructive shell commands (`rm -rf`, `DROP TABLE`, `git push --force`,
517
+ …); rebuildable caches (`rm -rf node_modules`, `dist`, …) pass. The policy is
518
+ `lib/destructive-policy.ts`. On Pi, superskill's native adapter routes the `ask` to `ctx.ui.confirm`
519
+ (and blocks when Pi has no UI). Hosts that cannot confirm (Codex, omp, generic) deny an
520
+ unconfirmable `ask` with the reason. Malformed input and runner errors still fail open; escape hatch
521
+ `SPUR_CAREFUL=off`.
522
+
523
+ **Runtime contract (superskill ADR-039).** `superskill hook run sp <id>` runs the plugin's own
524
+ `scripts/hooks/<id>.ts` under Bun with the canonical payload on stdin. An allow prints **nothing**
525
+ (a printed Claude `permissionDecision:"allow"` would bypass the user's permission rules). A
526
+ block/ask prints one JSON object that superskill renders per host:
527
+
528
+ - `fleet-guest-stop` prints the host-neutral `{"decision":"block","reason"}`.
529
+ - The two `PreToolUse` guards print Claude-native JSON with the reason in both
530
+ `permissionDecisionReason` and `systemMessage`. On Codex/omp, `hook run` turns a deny into exit 2
531
+ with that reason on stderr, and an unconfirmable ask becomes the same fail-closed denial. They stay Claude-native because the neutral form cannot carry
532
+ `systemMessage`, Claude Code's user-facing warning.
533
+
534
+ **Why `scripts/hooks/` (one canonical location).** superskill resolves `hooks/<id>` first, then
535
+ `scripts/hooks/<id>`, under every plugin root. But install delivers different trees to different
536
+ targets:
537
+
538
+ - Codex, OpenCode, Antigravity and Hermes get only `scripts/`, staged as
539
+ `.agents/scripts/sp/`, with `lib/` beside it as `.agents/scripts/lib/`.
540
+ - Native targets (Claude Code, omp, Grok) get the whole plugin.
541
+
542
+ So `scripts/hooks/<id>.ts`, importing `../../lib/…`, is the only path that resolves the same way on
543
+ every target. `hooks/` keeps the registry only. `script-contract-check`
544
+ rejects a hook entry there: on native installs it would shadow the staged one, and staged targets
545
+ would never receive it. Hook logic stays in the plugin (Revision 2026-10-15); the files are entries
546
+ and lib cores, not `spur` commands.
523
547
 
524
548
  **Escape hatch:** `SPUR_WRITE_GUARD=off` short-circuits the write guard before any subprocess.
525
549
 
550
+ **Pi.** The plugin ships no Pi-specific extension any more: the former
551
+ `hooks/pi/guard-extension.ts` (declared through `plugin.json` `extensions.pi`) is retired. Pi gets
552
+ the same seven `hooks.json` hooks as every other host, through superskill's **native Pi hook
553
+ adapter** (superskill PR #4, ADR-039): `superskill install` generates one Pi extension module per
554
+ declared hook, each running `superskill hook run sp <id>` with `SUPERSKILL_HOOK_HOST=pi`, and
555
+ applies the decision with Pi's primitives — `{block}` for deny, `ctx.ui.confirm` for `ask`, a
556
+ follow-up message for a Stop `block`. Requirements:
557
+
558
+ - **Superskill 0.3.39 or newer** for the native Pi adapter and staged-hook timeout metadata.
559
+ Older installs may retain the `@vahor/pi-hooks` shim or omit hook-specific timeout budgets.
560
+ - **A reinstall to Pi and omp** after upgrading: `superskill install sp --targets pi,omp`. Reinstall
561
+ removes the old `sp` extension package and any shim entries, so a hook never runs twice. An
562
+ existing Pi install keeps the retired extension until it is reinstalled.
563
+
564
+ **Spur CLI requirement:** sp requires Spur 0.5.0 or newer on PATH. The script absorption is a clean break;
565
+ older CLI versions do not provide the absorbed commands.
566
+
526
567
  #### 5. Scripts (`scripts/` + `hooks/`)
527
568
 
528
569
  Executable TypeScript that implements hook enforcement logic and deterministic helpers. Scripts are
@@ -532,20 +573,12 @@ hold `SKILL.md` and prompt-side companions only.
532
573
 
533
574
  | Script | Role |
534
575
  | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
535
- | `hooks/task-write-guard.ts` | Compatibility shim for older installs that still execute the script path directly. Forwards stdin to the stable PATH command `superskill hook run sp task-write-guard`, mirrors parseable PreToolUse decisions, and fails open if the runtime is unavailable. Performs no source-tree CLI lookup. |
536
- | `hooks/context-*.ts` | argv/stdin entries for the three registered context hooks; the capture, ledger and session logic is the `lib/context-*.ts` core (task 1174) |
537
- | `hooks/{agent-lifecycle,fleet-guest-stop}.ts` | Fleet hooks (1080/1081): member lifecycle reports via `spur agent report`; turn-end inbox delivery to joined guests |
538
- | `hooks/careful-guard.ts` | Opt-in destructive-command guard (unwired — see Hooks above); policy is `lib/destructive-policy.ts` |
576
+ | `scripts/hooks/task-write-guard.ts` | PreToolUse `Write\|Edit` guard: denies raw edits of task-corpus files (ownership from `spur task resolve --strict`), silent on allow, fail-open on every error path |
577
+ | `scripts/hooks/context-*.ts` | argv/stdin entries for the three registered context hooks; the capture, ledger and session logic is the `lib/context-*.ts` core (task 1174) |
578
+ | `scripts/hooks/{agent-lifecycle,fleet-guest-stop}.ts` | Fleet hooks (1080/1081): member lifecycle reports via `spur agent report`; turn-end inbox delivery to joined guests |
579
+ | `scripts/hooks/careful-guard.ts` | PreToolUse `Bash` destructive-command confirm (see Hooks above); policy is `lib/destructive-policy.ts` |
539
580
  | `lib/context-{post-tool,session-start,session-stop}.ts` | Redaction-capped ledger append, session open/ownership and owner-gated teardown for `sp:indexed-context` |
540
581
  | `lib/{agent-hint,context-owner,destructive-policy,task-file-policy}.ts` | Shared hook cores — agent/model hint resolution, host-native session owner derivation, destructive-command classification, task-file matching |
541
- | `scripts/batch-preflight.ts` | Pure TABLE A STOP evaluation for `super-planner` — skip doomed pipeline launches without spawning a Skill subprocess; recovery hints map stuck statuses to a single `/sp:dev-*` hop |
542
- | `scripts/workflow-step-profile.ts` | Thin glue over the generated lib bundle (`lib/step-profile.generated.mjs` from `packages/app/src/workflow/step-profile.ts`) — read-only step profile from `spur workflow trace`: per node and action-kind durations, idle gaps, session mode and `cacheHit` coverage, plus the ADR-115 cache-window flags `sp:spur-doctor` turns into workflow-optimization proposals |
543
- | `scripts/dogfood-testing/detect-pipeline-driving.ts` | Live Phase 1.0 CLI gate over `lib/dogfood-pipeline.ts` — word-boundary detector for pipeline-driving testees (leading-space invariant) |
544
- | `scripts/dogfood-testing/validate-report.ts` | CLI entry over `lib/dogfood-report.ts` — pure `validateReport(md)`, footer-mandatory + 7-check finalize-or-abort contract with stable error codes |
545
- | `scripts/daily-summary/daily-summary.ts` | ccusage + git-history orchestration helpers for `sp:daily-summary`, with its own console toggle |
546
- | `scripts/task-diffstat.ts` | 0943 triage-lane diffstat producer — numstat + untracked scan vs the run base; writes `<wbs>-diffstat.json` with the sensitive-path flag; fails safe (sensitive) |
547
- | `scripts/run-summary.ts` | Measured execution summary `/sp:dev-run` / `/sp:dev-runall` print by default (`--no-summary` skips) — windows the host transcript by `spur workflow progress` attempt timestamps; per stage/run time, wait, tool calls, total / non-cached tokens; `n/a` for subprocess windows |
548
- | `scripts/session-timeline.ts` | Measured Time/Wait/Token evidence for `sp:session-review` — reads the active Claude Code transcript read-only, one segment per operator prompt, usage deduplicated by message id, tokens split total / non-cached; `{available:false}` exit 0 when no transcript |
549
582
  | `scripts/commands/validate-flag-contracts.ts` | Mechanical consistency gate — compares flag claims across command files, flag-glossary, cross-cutting, dev-operations, and ADR; reports disagreements |
550
583
  | `*.test.ts` | Unit suites — `tests/hooks/` for the hook entries and their lib cores, `tests/<skill>/` per ADR-031 pairing |
551
584
 
@@ -580,7 +613,7 @@ graph TB
580
613
 
581
614
  subgraph "Execution layer"
582
615
  CLI["spur CLI<br/>task · feature · rule · workflow<br/>· agent · history · message"]
583
- SCRIPT["hooks/task-write-guard.ts"]
616
+ SCRIPT["scripts/hooks/task-write-guard.ts"]
584
617
  end
585
618
 
586
619
  subgraph "Corpus layer"
@@ -695,7 +728,7 @@ root codex/opencode use in global mode.
695
728
  | `skills/*.md` | `~/.claude/skills/` | Adapted as Skills 2.0 skill directories — all platforms receive skills uniformly |
696
729
  | `commands/*.md` | `~/.claude/commands/` | Adapted as Skills 2.0 skill entries (`disable-model-invocation: true`) |
697
730
  | `agents/*.md` | `~/.claude/agents/` | Adapted as Skills 2.0 skill entries (model-invocable); Pi additionally gets native agent format |
698
- | `hooks/hooks.json` | `~/.claude/hooks/` | Converted to target-native format (pi-hooks shim for pi/omp, HOOK.yaml for hermes) |
731
+ | `hooks/hooks.json` | `~/.claude/hooks/` | Converted to target-native format (generated native extensions for pi, pi-hooks shim for omp, HOOK.yaml for hermes) |
699
732
  | `hooks/*.ts` | plugin hook runtime / compatibility copy | Copied alongside platform output only for environments that still invoke script paths directly |
700
733
 
701
734
  Each skill declares its own platform support in `metadata.platforms` frontmatter.
@@ -35,7 +35,7 @@ task runs and frames the work above them.
35
35
  You are the **batch driver**. You run the loop documented in
36
36
  **[references/execution-batch.md](../skills/spur-dev/references/execution-batch.md)** - resolve the
37
37
  task set from the selector, freeze it, topologically order by dependencies, **preflight** each WBS
38
- against TABLE A STOP rows (via `sp:next-router` / `plugins/sp/scripts/batch-preflight.ts`), run each
38
+ against TABLE A STOP rows (via `sp:next-router` / `spur task preflight`), run each
39
39
  ready task through the standard single-task pipeline, inspect each terminal verdict, optional
40
40
  **one-shot recovery** hop, decide continue/halt, and emit a structured batch report.
41
41
 
@@ -75,13 +75,13 @@ You own the spaces **between** task runs:
75
75
  - **Topologically order** the frozen set by `dependencies[]` (Step 2). Abort on cycle; pre-block
76
76
  unmet out-of-set deps.
77
77
  - **Preflight (Step 2.5 / 3.0)** - before each pipeline launch, evaluate TABLE A STOP rows
78
- (`batch-preflight.ts` or equivalent). Skip A2/A7/A8/A9; still **launch `task-pipeline.yaml`** for
78
+ (`spur task preflight` or equivalent). Skip A2/A7/A8/A9; still **launch `task-pipeline.yaml`** for
79
79
  ready WBS (never substitute a `dev-next` loop for the happy path).
80
80
  - **Run each ready task** through `task-pipeline.yaml` via `spur workflow run --async`
81
81
  (Step 3). The command/script layer polls `spur workflow trace` to terminal state - polling is
82
82
  transport, not planner reasoning (R3: the poll loop must not live in this agent's body; the planner
83
83
  inspects the terminal verdict, not the poll iterations). Follow the
84
- `plugins/sp/scripts/batch-preflight.ts` precedent for scripted transport.
84
+ `spur task preflight` precedent for scripted transport.
85
85
  - **Parallelize only when requested** by applying `sp:parallel-execution` to a proven-independent
86
86
  subset (Step 3 optional path). Serialize when dependency, file-overlap, or budget checks fail.
87
87
  Preflight still runs per WBS before fan-out; recovery stays sequential.
@@ -156,14 +156,14 @@ When you fan out or dispatch a subagent, apply the four disciplines the SSOT
156
156
  - [ ] Drive the loop in execution-batch.md - resolve -> freeze -> order -> **preflight** -> run -> inspect
157
157
  -> optional recovery -> decide -> report.
158
158
  - [ ] **Preflight** each WBS before `workflow run` using
159
- `node "$(superskill script path sp batch-preflight.mjs)"` (or the pure `preflightTask` helper) with
159
+ `spur task preflight <wbs>` (or the pure `preflightTask` helper) with
160
160
  `spur task show --json` deps. On `action: skip` (A2/A7/A8/A9), do **not** start the pipeline;
161
161
  record skip in the batch report with the reason string.
162
162
  - [ ] Launch each **ready** per-task pipeline with `--async`. The command/script layer polls
163
163
  `spur workflow trace` to terminal state - this is transport, not planner reasoning (R3). A
164
164
  pipeline with `agent.run` stages runs for many minutes; the planner inspects the terminal
165
165
  verdict + `.spur/run/<wbs>-verdict.json`, never the poll iterations. Synchronous invocation
166
- risks orphaned runs; follow the `batch-preflight.ts` precedent for scripted transport.
166
+ risks orphaned runs; follow the `spur task preflight` precedent for scripted transport.
167
167
  - [ ] Forward `--auto` and `--agent` into each per-task `--vars` and nothing else.
168
168
  - [ ] Freeze the set at kickoff; never re-query `spur task list` to recompute membership mid-batch.
169
169
  - [ ] Abort the whole batch on a dependency cycle before running any task.
@@ -182,7 +182,7 @@ the cell, not the sentence. `OPEN` states "still live", and `ACCEPTED` marks a n
182
182
  observation you are deliberately not acting on; both behave as an ordinary open row.
183
183
 
184
184
  **Pipeline cost of an open finding (1122).** The review PASS edges run this same classification
185
- before verify (`residual-scan review-gate`): a PASS whose table still carries an open P1-P3 row
185
+ before verify (`spur task residual <wbs> review-gate`): a PASS whose table still carries an open P1-P3 row
186
186
  routes into `review-fail-triage` → the bounded repair hop instead of verify. Open P1-P3 findings
187
187
  block `done`; fixing one invalidates the certified digest and re-runs quality → review → verify on
188
188
  a fresh digest. Only P4 rows and `DEFER`-ed P3 rows are wrap residuals — disposition the finding in
@@ -247,7 +247,7 @@ assert no external claim and need no citation. An uncited external claim is an a
247
247
  as an explicit gap in the report body (above), so a reader can re-check it.
248
248
 
249
249
 
250
- **Machine-checked at the gate (findings table).** `residual-scan review-gate` applies the same two
250
+ **Machine-checked at the gate (findings table).** `spur task residual <wbs> review-gate` applies the same two
251
251
  predicates to every finding row: a Finding cell carrying one of the verify lint's claim markers with
252
252
  no citation form becomes a **blocking** `review-contract` item, so `review → verify` stops and the
253
253
  run enters `review-fail-triage`. The `Location` column is deliberately not consulted as a citation —
@@ -26,5 +26,5 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
26
26
 
27
27
  ## Implementation
28
28
 
29
- Follow the inline procedure in [dev-operations.md](../skills/spur-dev/references/dev-operations.md#8-changelog) (changelog). This is a docs-only generator: run no quality gates (`bun run gate`, lint, typecheck, tests, `spur-check`, rules) — a generic "verify before finishing" rule does not apply here. The generator's own claim-verification pass (dev-operations §8, step 7: API/library claims get `<!-- src: … · HIGH|MEDIUM|LOW -->` citations) is part of the procedure, not a gate.
29
+ Follow the inline procedure in [dev-operations.md](../skills/spur-dev/references/dev-operations.md#8-changelog) (changelog). This is a docs-only generator: run no quality gates (`bun run gate`, lint, typecheck, tests, `spur-check`, rules) — a generic "verify before finishing" rule does not apply here. The generator's own claim-verification pass is part of the procedure, not a gate, and carries two required checks per claim bullet (dev-operations §8, step 7): a **source citation** — an anchor naming a URL or a repo `path:line` — and a **confidence level**, exactly one of `HIGH`, `MEDIUM`, `LOW`, both held in the bullet's `<!-- src: … · <level> -->` comment and re-verified by step 8's self-check.
30
30
 
@@ -27,4 +27,4 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
27
27
 
28
28
  ## Implementation
29
29
 
30
- Run the generator per [daily-summary/SKILL.md](../skills/daily-summary/SKILL.md): `node "$(superskill script path sp daily-summary/daily-summary.mjs)" $ARGUMENTS`.
30
+ Run the generator per [daily-summary/SKILL.md](../skills/daily-summary/SKILL.md): `spur history summary $ARGUMENTS`.
@@ -32,7 +32,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
32
32
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
33
33
  - Delegate everything: `Skill(skill="sp:pr-reviewing", args="$ARGUMENTS")`. The skill owns mode
34
34
  routing, finding triage, fix, and rules authoring; `pr-review.yaml` is the SSOT
35
- for the review spine's state order and guards; the staged `pr-reviewing.ts` is the
35
+ for the review spine's state order and guards; `spur builder pr` is the
36
36
  deterministic git/gh core every spine step shells out to.
37
37
  - The external review goes through the GitHub PR and an `@codex review` comment only — never local
38
38
  Codex review commands. Never force-push, rewrite history, merge the PR, or discard unrelated
@@ -15,7 +15,7 @@ documentation work and one-to-two-line fixes) inline and files everything remain
15
15
  implement-ready tasks for further fixing; `--triage` is an alias of `--fix auto`. With `--fix all`,
16
16
  it defers nothing: every actionable finding is fixed inline and no task is filed. The result includes a non-overlapping time breakdown —
17
17
  work time, operator wait, tool calls and tokens (total / non-cached) per stage, measured from the host transcript by
18
- `session-timeline.mjs` — with durations in `M:SS` or `H:MM:SS` form and `n/a` only when that
18
+ `spur history timeline` — with durations in `M:SS` or `H:MM:SS` form and `n/a` only when that
19
19
  measurement is unavailable. The transcript resolves from `--transcript`, else
20
20
  `CLAUDE_CODE_SESSION_ID`, else `PI_SESSION_FILE` (the pi host sets it and no Claude session id); a
21
21
  pi `<skill name="…">` wrapper row is the operator's `/skill` invocation and opens a segment
@@ -42,7 +42,7 @@ feature-derived, `dev-runall` runs `spur feature check <id> --fix --json` then `
42
42
  resolving tasks — a non-zero strict check aborts the batch with verdict `aborted` and the
43
43
  structured findings, before any task pipeline action, task 0510 R2; scoped: `L4.scenario-unverified` and `L4.verifying-incomplete-tasks`
44
44
  (expected pre-run states of a not-yet-run or reopened feature) are reported verbatim but do not abort (the non-aborting set is
45
- `NON_ABORTING_PREFLIGHT_CODES` in `plugins/sp/scripts/batch-preflight.ts`; `--fix` repairs land in the report's `autoRepairs`) —
45
+ `NON_ABORTING_PREFLIGHT_CODES` behind `spur task preflight --classify-feature`; `--fix` repairs land in the report's `autoRepairs`) —
46
46
  any other strict error aborts), `--mode`
47
47
  `<sequential|parallel|auto>` (default `sequential`; `parallel` fans out a proven-independent subset —
48
48
  see `execution-parallel-isolation.md`; `auto` picks one per batch — see below), `--concurrency <n>`
@@ -63,7 +63,7 @@ creates a fresh tree, `<name>` form adopts an existing worktree by name/path/bra
63
63
  `execution-worktree-setup.md` + `execution-worktree-landing.md`).
64
64
 
65
65
  **`--mode auto` (smart scheduling).** Ordering is always dependency-correct — every mode
66
- topo-sorts the frozen set with `batch-plan.mjs order` (deterministic, never hand-sorted). `auto`
66
+ topo-sorts the frozen set with `spur task batch-plan order` (deterministic, never hand-sorted). `auto`
67
67
  additionally reads that schedule's dependency waves (tasks declaring the same file are serialized)
68
68
  and decides once, before the first task: `parallel` when some wave holds ≥2 independent tasks and
69
69
  `--concurrency` ≥2; otherwise `sequential` in the host session. `--worktree` or explicit
@@ -101,8 +101,8 @@ full distinction.
101
101
 
102
102
  - **Bootstrap reads (1128):** before the first dispatch, read [SKILL.md § Bootstrap reads](../skills/spur-dev/SKILL.md#bootstrap-reads) and load only that mode's files — the section whose trigger has not fired is on demand.
103
103
  - **Publish the visible batch plan first (1105):** publish `A Prepare batch` (A1–A4) and `Z Batch
104
- report` before the first task, then one letter per task from `batch-plan.mjs waves` after
105
- freeze/order and digit children from `batch-plan.mjs task-children` at task start —
104
+ report` before the first task, then one letter per task from `spur task batch-plan waves` after
105
+ freeze/order and digit children from `spur task batch-plan task-children` at task start —
106
106
  [execution-batch.md](../skills/spur-dev/references/execution-batch.md#27-visible-batch-plan-1105-r3)
107
107
  § 2.7 Visible batch plan.
108
108
  - Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface). Interactive sequential omit/`inline` keeps the orchestrator in the host session through the [inline pipeline driver](../skills/spur-dev/references/inline-pipeline-driver.md); the resolved selector applies uniformly — eligible `agent.run` stages dispatch once to a native subagent with host fallback (0508). `--agent auto`, a name, or parallel mode retains the isolated per-task workflow boundary.
@@ -32,7 +32,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
32
32
  ```
33
33
 
34
34
  **Shippable readiness** (feature-level): when `--fix all` and the task has a `feature_id`, the
35
- skill runs `spur feature check <id>` after the per-task verdict and emits `Shippable: PASS|FAIL|N/A`.
35
+ skill runs `spur feature check <id> --as done --strict` after the per-task verdict and emits `Shippable: PASS|FAIL|N/A`.
36
36
  Default **on** with `--fix all`. Opt out only with **`--skip-shippable`** (alias `--skip-shipable`).
37
37
  See `sp:code-verification` § Shippable readiness gate.
38
38
 
@@ -42,14 +42,12 @@ See `sp:code-verification` § Shippable readiness gate.
42
42
  - `Skill(skill="sp:code-verification", args="verify $ARGUMENTS")`
43
43
  - **Residual sweep (F96 R1, post-record since 0987):** after `task verdict` derives
44
44
  `.spur/run/<wbs>-verdict.json` and `spur task record` has flipped the verdict-proven R/AC boxes,
45
- run `residual-scan scan` + `fold` (observe-only; folds the verdict's `residual-sweep` check,
45
+ run `spur task residual <wbs> scan` + `fold` (observe-only; folds the verdict's `residual-sweep` check,
46
46
  PASS → PARTIAL when a blocking residual exists) — under every `--fix` mode — then, only on a
47
47
  downgrade, re-run `spur task record <wbs> --verdict-file .spur/run/<wbs>-verdict.json` so Testing
48
48
  carries the final verdict. Same order as the pipeline `record` state (0983): scanning before
49
- record reads unticked-but-proven boxes and folds a clean PASS to PARTIAL. Resolve the script via `superskill script path sp
50
- residual-scan.mjs`; never reference `plugins/sp/scripts/` in shipped surfaces
51
- (script-contract-check rule 4). When the task reaches `done` through `--next`, run
52
- `residual-scan settle` (best-effort). On a folded PARTIAL: the bounded remediation loop has
49
+ record reads unticked-but-proven boxes and folds a clean PASS to PARTIAL. When the task reaches
50
+ `done` through `--next`, run `spur task residual <wbs> settle` (best-effort). On a folded PARTIAL: the bounded remediation loop has
53
51
  already run — a residual-failed task is routed to router C6 (HITL STOP), never another
54
52
  automatic fix loop.
55
53
 
@@ -60,7 +60,7 @@ statuses. Do not confuse with `--keep-going`
60
60
 
61
61
  **Shippable readiness** (feature-level): when `--fix all` and a feature context exists
62
62
  (`--feature` or a unique shared `feature_id` across the set), after all per-task verifies the
63
- orchestrator runs **one** `spur feature check <id>` and emits `Shippable: PASS|FAIL|N/A`.
63
+ orchestrator runs **one** `spur feature check <id> --as done --strict` and emits `Shippable: PASS|FAIL|N/A`.
64
64
  A FAIL means the batch is **not clean** (rollup at least PARTIAL) even if every task AC PASSed.
65
65
  Default **on** with `--fix all`. Opt out only with **`--skip-shippable`** (alias `--skip-shipable`).
66
66
  Without `--fix all`, shippable is not evaluated (optional note: use `--fix all` for ship gate).
@@ -74,7 +74,6 @@ Without `--fix all`, shippable is not evaluated (optional note: use `--fix all`
74
74
  - Shippable gate: once after the batch (same skill § Shippable readiness gate)
75
75
  - **Residual sweep (F96 R1, per task, post-record since 0987):** same contract as
76
76
  `/sp:dev-verify` — after each task's verdict artifact is derived and `spur task record` has
77
- flipped its verdict-proven boxes, run `residual-scan scan` + `fold` (under every `--fix` mode),
78
- and re-record only on a downgrade; on reaching `done` via `--next`, run `settle`. Script resolution:
79
- `superskill script path sp residual-scan.mjs` (never `plugins/sp/scripts/` —
80
- script-contract-check rule 4).
77
+ flipped its verdict-proven boxes, run `spur task residual <wbs> scan` + `fold` (under every
78
+ `--fix` mode), and re-record only on a downgrade; on reaching `done` via `--next`, run
79
+ `spur task residual <wbs> settle`.
@@ -1,5 +1,5 @@
1
1
  {
2
- "description": "Spur (sp) plugin hooks — task corpus write protection + indexed-context token-ledger tracking + fleet lifecycle reporting.",
2
+ "description": "Spur (sp) plugin hooks — task corpus write protection + destructive-command confirm + indexed-context token-ledger tracking + fleet lifecycle reporting. Each `superskill hook run sp <id>` resolves to scripts/hooks/<id>.ts (superskill ADR-039).",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -11,6 +11,16 @@
11
11
  "timeout": 10
12
12
  }
13
13
  ]
14
+ },
15
+ {
16
+ "matcher": "Bash",
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "superskill hook run sp careful-guard",
21
+ "timeout": 10
22
+ }
23
+ ]
14
24
  }
15
25
  ],
16
26
  "PostToolUse": [
@@ -17,7 +17,7 @@
17
17
  * Self-contained by design (task 0232/0246/0248). Installed hook configs use
18
18
  * `superskill hook run sp context-post-tool`.
19
19
  *
20
- * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/hooks/context-post-tool.ts` keeps the argv/stdin adapter the installed entry resolves to.
20
+ * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/scripts/hooks/context-post-tool.ts` keeps the argv/stdin adapter the installed entry resolves to.
21
21
  */
22
22
 
23
23
  import { execSync } from 'node:child_process';
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Self-contained by design (task 0232/0246).
13
13
  *
14
- * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/hooks/context-session-start.ts` keeps the argv/stdin adapter.
14
+ * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/scripts/hooks/context-session-start.ts` keeps the argv/stdin adapter.
15
15
  */
16
16
 
17
17
  import { execSync } from 'node:child_process';
@@ -18,7 +18,7 @@
18
18
  * Self-contained by design (task 0232). Installed hook configs use the portable
19
19
  * `superskill hook run sp context-session-stop` entrypoint.
20
20
  *
21
- * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/hooks/context-session-stop.ts` keeps the argv/stdin adapter.
21
+ * Task 1174: this core lives in `plugins/sp/lib/`; `plugins/sp/scripts/hooks/context-session-stop.ts` keeps the argv/stdin adapter.
22
22
  */
23
23
 
24
24
  import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
@@ -123,6 +123,69 @@ const DESTRUCTIVE: Array<{ label: string; re: RegExp }> = [
123
123
  { label: 'a docker prune (docker system prune)', re: /\bdocker\s+system\s+prune\b/i },
124
124
  ];
125
125
 
126
+ /**
127
+ * A single shell word usable as a git global-option value. Quoted forms are kept
128
+ * whole — `-C "my repo"` must read as one argument — while an unquoted word stops at
129
+ * whitespace and the shell separators `; & |`, so the pass can never swallow a
130
+ * following command.
131
+ */
132
+ const GIT_OPTION_VALUE = String.raw`(?:"[^"]*"|'[^']*'|[^\s;&|]+)`;
133
+
134
+ /**
135
+ * Git global options that consume a following word in their separated form — the word
136
+ * after them is a value, never the subcommand. Verified against git 2.55.0: each of
137
+ * these takes the next word (`git --git-dir rev-parse …` → `unknown option:
138
+ * --is-inside-work-tree`, i.e. git had already consumed `rev-parse` as the value).
139
+ * The value-less long options (`--no-pager`, `--bare`, `--no-replace-objects`, …) need
140
+ * no list: missing one only makes the pass stop earlier, it can never misread a value
141
+ * as a subcommand. Do NOT add an option here unless it really consumes the next word —
142
+ * `--exec-path` is optional-value, so listing it would swallow the subcommand and hide
143
+ * a destructive verb (git 2.55.0: `git --exec-path /tmp/p reset --hard` prints the exec
144
+ * path and exits; the `=`-glued form does consume the value).
145
+ */
146
+ const GIT_VALUE_TAKING_OPTION = '--(?:git-dir|work-tree|namespace|config-env|attr-source)';
147
+
148
+ /**
149
+ * One `git` global option unit, including the whitespace before its value.
150
+ *
151
+ * - `--name=value` — any long option in glued form.
152
+ * - `KNOWN --name value` — the value-taking long options above, separated form.
153
+ * - `-C value` / `-c value` — the value-taking short options; git rejects glued short
154
+ * forms (`git -C. status` → `unknown option: -C.`, verified on 2.55.0), so their
155
+ * value is always the next word.
156
+ * - `--name` / `-xy` — any value-less flag (`--no-pager`, `--bare`, `-p`, …).
157
+ *
158
+ * An option run is a sequence of these units; it ends at the first token that is not an
159
+ * option — the subcommand. Matching the whole grammar rather than a four-form whitelist
160
+ * keeps sibling options of the same defect class from hiding a destructive verb without
161
+ * teaching every destructive pattern git's option table.
162
+ */
163
+ const GIT_GLOBAL_OPTION = String.raw`(?:--[A-Za-z][A-Za-z0-9-]*=${GIT_OPTION_VALUE}|${GIT_VALUE_TAKING_OPTION}[ \t]+${GIT_OPTION_VALUE}|-(?:[A-Za-z]*[Cc])[ \t]+${GIT_OPTION_VALUE}|--[A-Za-z][A-Za-z0-9-]*|-[A-Za-z]+)`;
164
+
165
+ /**
166
+ * `git` followed by any run of global options directly before the subcommand. Replacing
167
+ * the whole span with `git` lets the destructive subcommand patterns fire on
168
+ * option-prefixed invocations without teaching every pattern git's option grammar.
169
+ *
170
+ * The run cannot cross a command boundary: units match `[ \t]` only (never a newline)
171
+ * and an unquoted value stops at `; & |`.
172
+ */
173
+ const GIT_WITH_GLOBAL_OPTIONS = new RegExp(String.raw`\bgit\b(?:[ \t]+${GIT_GLOBAL_OPTION})*`, 'g');
174
+
175
+ /**
176
+ * Remove supported git global options between the `git` executable and its subcommand.
177
+ *
178
+ * `git -C . push --force`, `git --no-pager push --force` and
179
+ * `git -c core.hooksPath=/tmp/hooks push -f` used to classify as safe because the
180
+ * subcommand patterns require `git` immediately before the verb. The
181
+ * replacement edits only the matched option span: every other byte — newlines, command
182
+ * separators, quotes — is preserved, so the patterns cannot begin matching across a
183
+ * command boundary, and no shell expansion or `git` process is involved.
184
+ */
185
+ export function stripGitGlobalOptions(command: string): string {
186
+ return command.replace(GIT_WITH_GLOBAL_OPTIONS, 'git');
187
+ }
188
+
126
189
  /** Return a human label for the destructive command, or null when the command is safe. */
127
190
  export function classifyCommand(command: string): string | null {
128
191
  for (const rmMatch of command.matchAll(/\brm\b([^\n&|;]*)/g)) {
@@ -139,8 +202,9 @@ export function classifyCommand(command: string): string | null {
139
202
  return 'a recursive force remove (rm -rf)';
140
203
  }
141
204
  }
205
+ const normalized = stripGitGlobalOptions(command);
142
206
  for (const { label, re } of DESTRUCTIVE) {
143
- if (re.test(command)) return label;
207
+ if (re.test(normalized)) return label;
144
208
  }
145
209
  return null;
146
210
  }
@@ -1,8 +1,5 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.8",
4
- "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
- "extensions": {
6
- "pi": ["./hooks/pi/guard-extension.ts"]
7
- }
3
+ "version": "0.4.9",
4
+ "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics."
8
5
  }
@@ -26,7 +26,7 @@
26
26
  */
27
27
 
28
28
  import { spawn } from 'node:child_process';
29
- import { getEnvVars } from '../lib/env';
29
+ import { getEnvVars } from '../../lib/env';
30
30
 
31
31
  /** Lifecycle states `spur agent report` accepts — mirrors MEMBER_LIFECYCLE_STATES in the domain. */
32
32
  export type MemberLifecycleState = 'working' | 'idle' | 'blocked';