@gobing-ai/spur 0.3.47 → 0.3.49
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.example.yaml +94 -15
- package/config/transition-shims.json +33 -0
- package/config/workflows/basic.yaml +2 -0
- package/config/workflows/docs-pipeline.yaml +2 -0
- package/config/workflows/feature-dev.yaml +8 -0
- package/config/workflows/idea-pipeline.yaml +10 -0
- package/config/workflows/planning-pipeline.yaml +4 -0
- package/config/workflows/pr-review.yaml +338 -0
- package/config/workflows/task-pipeline.yaml +8 -0
- package/config/workflows/wayfinder-resolution.yaml +4 -0
- package/config/workflows/wrapup-pipeline.yaml +18 -1
- package/package.json +8 -8
- package/plugins/sp/README.md +9 -6
- package/plugins/sp/agents/expert-spur.md +1 -0
- package/plugins/sp/commands/dev-arch.md +2 -1
- package/plugins/sp/commands/dev-brainstorm.md +2 -1
- package/plugins/sp/commands/dev-changelog.md +1 -0
- package/plugins/sp/commands/dev-daily.md +1 -0
- package/plugins/sp/commands/dev-debug.md +2 -1
- package/plugins/sp/commands/dev-dogfood.md +2 -1
- package/plugins/sp/commands/{dev-featurechange.md → dev-feature-change.md} +8 -10
- package/plugins/sp/commands/dev-find-conflict.md +2 -1
- package/plugins/sp/commands/dev-find-issue.md +36 -43
- package/plugins/sp/commands/dev-find-next.md +5 -4
- package/plugins/sp/commands/dev-fixall.md +1 -0
- package/plugins/sp/commands/dev-gitmsg.md +1 -0
- package/plugins/sp/commands/dev-gtd.md +12 -12
- package/plugins/sp/commands/dev-handover.md +1 -0
- package/plugins/sp/commands/dev-history-load.md +63 -0
- package/plugins/sp/commands/dev-idea.md +1 -0
- package/plugins/sp/commands/dev-next.md +2 -1
- package/plugins/sp/commands/dev-parallel.md +2 -1
- package/plugins/sp/commands/dev-plan.md +2 -1
- package/plugins/sp/commands/dev-pr-review.md +39 -0
- package/plugins/sp/commands/dev-refine.md +5 -3
- package/plugins/sp/commands/dev-refineall.md +2 -1
- package/plugins/sp/commands/dev-refresh.md +2 -1
- package/plugins/sp/commands/dev-reverse.md +2 -1
- package/plugins/sp/commands/dev-review.md +2 -1
- package/plugins/sp/commands/dev-run.md +3 -2
- package/plugins/sp/commands/dev-runall.md +3 -2
- package/plugins/sp/commands/dev-simplify.md +2 -1
- package/plugins/sp/commands/dev-unit.md +2 -1
- package/plugins/sp/commands/dev-verify.md +2 -1
- package/plugins/sp/commands/dev-verifyall.md +2 -1
- package/plugins/sp/commands/dev-wrap.md +7 -5
- package/plugins/sp/commands/dev-wrapall.md +7 -5
- package/plugins/sp/commands/rule-add.md +1 -0
- package/plugins/sp/commands/rule-refine.md +1 -0
- package/plugins/sp/commands/rule-scan.md +1 -0
- package/plugins/sp/commands/spur-init.md +1 -0
- package/plugins/sp/commands/workflow-add.md +1 -0
- package/plugins/sp/commands/workflow-refine.md +1 -0
- package/plugins/sp/hooks/careful-guard.ts +5 -80
- package/plugins/sp/hooks/destructive-policy.ts +146 -0
- package/plugins/sp/hooks/pi/guard-extension.ts +33 -46
- package/plugins/sp/hooks/task-file-policy.ts +31 -0
- package/plugins/sp/hooks/task-write-guard.ts +4 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +106 -0
- package/plugins/sp/scripts/feature-sync-bounded.ts +28 -2
- package/plugins/sp/scripts/history-load.ts +400 -0
- package/plugins/sp/scripts/pr-reviewing.ts +867 -0
- package/plugins/sp/scripts/stage-registry-adapter.ts +66 -31
- package/plugins/sp/scripts/surface-drift-inventory.ts +908 -0
- package/plugins/sp/scripts/task-size-precheck.ts +30 -4
- package/plugins/sp/scripts/transition-shim-check.ts +238 -0
- package/plugins/sp/scripts/validate-commands.ts +33 -2
- package/plugins/sp/scripts/validate-flag-contracts.ts +5 -2
- package/plugins/sp/skills/code-implementation/SKILL.md +9 -1
- package/plugins/sp/skills/code-verification/SKILL.md +29 -28
- package/plugins/sp/skills/issue-finding/SKILL.md +123 -141
- package/plugins/sp/skills/issue-finding/examples/expected-findings.json +1 -1
- package/plugins/sp/skills/issue-finding/references/session-formats.md +87 -90
- package/plugins/sp/skills/next-feature/SKILL.md +6 -6
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +5 -5
- package/plugins/sp/skills/next-feature/references/signal-derivation.md +7 -2
- package/plugins/sp/skills/next-router/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +40 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +285 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -0
- package/plugins/sp/skills/spur-cli/references/agent.md +12 -7
- package/plugins/sp/skills/spur-cli/references/features/hierarchy-mece.md +5 -5
- package/plugins/sp/skills/spur-cli/references/features.md +1 -1
- package/plugins/sp/skills/spur-cli/references/team.md +10 -3
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +17 -0
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +44 -23
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +14 -11
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +14 -12
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +28 -7
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -0
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +12 -2
- package/schemas/spur-config.schema.json +47 -3
- package/spur.js +12223 -7716
- package/web/_astro/BoardApp.8hiqShQn.js +1 -0
- package/web/_astro/{BoardApp.DKyrGxdo.js → BoardApp.BjQUNhuj.js} +74 -74
- package/web/_astro/{TaskDetail.6-27_LMa.js → TaskDetail.CVBuD6dF.js} +1 -1
- package/web/_astro/{arc.Df-9AQvS.js → arc.BMMjdODi.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.VAI_-paS.js → architectureDiagram-3BPJPVTR.BU5ShzXf.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFpUY1ue.js → blockDiagram-GPEHLZMM.Bj1iEqPD.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CF8doOpg.js → c4Diagram-AAUBKEIU.vX8wepCL.js} +1 -1
- package/web/_astro/channel.EwdSemIC.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.BnjK3fjt.js → chunk-2J33WTMH.BKAipTym.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.x6ZDnJKq.js → chunk-4BX2VUAB.B68XkPG7.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.zY-0uu7w.js → chunk-55IACEB6.BmeDLcrc.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BZxKg_Vi.js → chunk-727SXJPM.PDuBA3Kw.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.Cpi9G9Td.js → chunk-AQP2D5EJ.C7A044za.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DWTB-Pif.js → chunk-FMBD7UC4.BtzKKFqR.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BPDQbiOG.js → chunk-ND2GUHAM.BJuDeeOy.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.BRWIcuoM.js → chunk-QZHKN3VN.DSeMDgcQ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.mGTCZsDO.js → classDiagram-4FO5ZUOK.D53Q4tCw.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.mGTCZsDO.js → classDiagram-v2-Q7XG4LA2.D53Q4tCw.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.D1GEut-z.js → cose-bilkent-S5V4N54A.c712AFRH.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BV0XG9Do.js → dagre-BM42HDAG.D-idisph.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DzpYxsjo.js → diagram-2AECGRRQ.DLgnsJCU.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Cm9YzJh4.js → diagram-5GNKFQAL.BiaxBVqx.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.BjhottUj.js → diagram-KO2AKTUF.C8HX1vd8.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BFsQW5kb.js → diagram-LMA3HP47.CfqDLLes.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.8pdpzSWO.js → diagram-OG6HWLK6.15SDiEed.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Bd7KUJmJ.js → erDiagram-TEJ5UH35.DksYtOYM.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.7LWffkaE.js → flowDiagram-I6XJVG4X.DR_Au-HV.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.BeDcO5tI.js → ganttDiagram-6RSMTGT7.CHhHrffI.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.Ca4n730A.js → gitGraphDiagram-PVQCEYII.B2Xehvam.js} +1 -1
- package/web/_astro/{index.Dbvuw6d4.css → index.DAxu50UF.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.B0OakQYb.js → infoDiagram-5YYISTIA.C9c3CNNN.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.DSmNQe-1.js → ishikawaDiagram-YF4QCWOH.BibUHkh8.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.Cy5ruEUu.js → journeyDiagram-JHISSGLW.BYoVHiyO.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.CUJXub0p.js → kanban-definition-UN3LZRKU.CM1K5wHE.js} +1 -1
- package/web/_astro/{linear.DC1jCCXn.js → linear.SPpjJUb-.js} +1 -1
- package/web/_astro/{mermaid.core.DxVP99Ab.js → mermaid.core.BAgx3nnb.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D0MaV6sJ.js → mindmap-definition-RKZ34NQL.D35oPG1R.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.DCC6_q32.js → pieDiagram-4H26LBE5.DiWuRwk7.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.BeUOAM7C.js → quadrantDiagram-W4KKPZXB.B9PBzTWn.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.Dbl4MASO.js → requirementDiagram-4Y6WPE33.CYuuamFN.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.HsLg0VS4.js → sankeyDiagram-5OEKKPKP.W24UhhtD.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DT7DJTnZ.js → sequenceDiagram-3UESZ5HK.BpbNjA51.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.d_ju1Vr1.js → stateDiagram-AJRCARHV.DqVsHudf.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.DMCAjMJ4.js → stateDiagram-v2-BHNVJYJU.CzwHYX81.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DNOHr62_.js → timeline-definition-PNZ67QCA.Bc3B6djw.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.B7dUy-1W.js → vennDiagram-CIIHVFJN.C-D5rh8O.js} +1 -1
- package/web/_astro/{wardley-L42UT6IY.DEqOXvBh.js → wardley-L42UT6IY.D7PdYCqn.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BCRb2p6x.js → wardleyDiagram-YWT4CUSO.CwmJKXF3.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.NxVQLdBh.js → xychartDiagram-2RQKCTM6.avDYnLsb.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/BoardApp.Ce6zJYAH.js +0 -1
- package/web/_astro/channel.Uhm9O3UV.js +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Run a task — full pipeline (precheck→implement→test→review→approve→verify→record→done) or single-step (implement)
|
|
3
|
+
role: coder
|
|
3
4
|
argument-hint: "<wbs> [--mode <full|implement>] [--agent <inline|auto|name>] [--auto] [--next] [--wrap] [--continue]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Write", "Edit", "Skill"]
|
|
5
6
|
---
|
|
@@ -14,7 +15,7 @@ Wraps the **sp:spur-dev** and **sp:code-implementation** skills.
|
|
|
14
15
|
| --- | --- | --- |
|
|
15
16
|
| `<wbs>` | Task WBS to run. | required |
|
|
16
17
|
| `--mode` `<full\|implement>` | Full pipeline or single implement step. | full |
|
|
17
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. In an interactive session, omit/`inline` keeps the controller in the host session (non-subprocess)
|
|
18
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. In an interactive session, omit/`inline` keeps the controller in the host session (non-subprocess) — **omit**'s eligible full-mode `agent.run` stages may dispatch once to a native subagent, with host fallback (task 0508); explicit `--agent inline` is the zero-dispatch carve-out (every stage executes in the invoking session, never a native subagent). `auto` or a name keeps subprocess dispatch. | omit |
|
|
18
19
|
| `--auto` | Skip objective HITL confirmations. | off |
|
|
19
20
|
| `--next` | Chain-to-completion via the next-router. | off |
|
|
20
21
|
| `--wrap` | Run the wrap hop after the main step. The `--agent` selector is preserved into the `/sp:dev-wrap <wbs>` handoff when supplied; omission remains omission. The wrap hop is workflow-backed and reports its trigger-3 subprocess override. | off |
|
|
@@ -37,7 +38,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
37
38
|
|
|
38
39
|
**Flags:**
|
|
39
40
|
|
|
40
|
-
- `--auto` | `--agent <inline|auto|name>` — Skip objective HITL confirmations (taste/irreversible gates still pause). `--agent` names who does the model-bearing work. Interactive omit/`inline` keeps the controller and implement-only stages in this session; full mode reads `task-pipeline.yaml` as the SSOT and interprets its actions/guards through the inline driver, where eligible `agent.run` stages may dispatch once to a native subagent and otherwise run in the host (task 0508). It records `stage <id> executed inline in session <session-id>` or `stage <id> executed via subagent <agent-id> (host session <session-id>)` in the run log. `auto` or a name is merged into `vars.agent` and `vars.implementAgent` and keeps the existing subprocess workflow. Headless `spur workflow run` / `spur agent run` is unchanged. See the [execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
41
|
+
- `--auto` | `--agent <inline|auto|name>` — Skip objective HITL confirmations (taste/irreversible gates still pause). `--agent` names who does the model-bearing work. Interactive omit/`inline` keeps the controller and implement-only stages in this session; full mode reads `task-pipeline.yaml` as the SSOT and interprets its actions/guards through the inline driver, where **omitted** `--agent`'s eligible `agent.run` stages may dispatch once to a native subagent and otherwise run in the host (task 0508); explicit `--agent inline` is the zero-dispatch carve-out — every stage executes in the invoking session. It records `stage <id> executed inline in session <session-id>` or `stage <id> executed via subagent <agent-id> (host session <session-id>)` in the run log. `auto` or a name is merged into `vars.agent` and `vars.implementAgent` and keeps the existing subprocess workflow. Headless `spur workflow run` / `spur agent run` is unchanged. See the [execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
41
42
|
|
|
42
43
|
**Mode split (load-bearing — bug-742)**
|
|
43
44
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Run a batch of tasks through their pipelines in dependency-correct order — resolve a set, topo-sort, run each via task-pipeline.yaml, emit a batch report
|
|
3
|
+
role: planner
|
|
3
4
|
argument-hint: "--tasks <selector> [--feature <id>] [--mode <sequential|parallel>] [--keep-going] [--auto] [--agent <inline|auto|name>] [--json] [--wrap] [--next] [--continue] [--worktree [<name>]]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
6
|
---
|
|
@@ -17,7 +18,7 @@ Wraps the **sp:spur-dev** skill.
|
|
|
17
18
|
| `--mode` `<sequential\|parallel>` | Batch execution order. | sequential |
|
|
18
19
|
| `--keep-going` | Continue past per-task failures. | off |
|
|
19
20
|
| `--auto` | Skip objective HITL gates. | off |
|
|
20
|
-
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver — host-controlled
|
|
21
|
+
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver — host-controlled; **omit**'s eligible `agent.run` stages may use a native subagent (task 0508); explicit `--agent inline` is the zero-dispatch carve-out (every stage executes in the invoking session). `auto`, a name, parallel mode, and headless invocation use subprocesses. | omit |
|
|
21
22
|
| `--json` | Emit structured JSON. | off |
|
|
22
23
|
| `--wrap` | Run the wrap hop per task. The `--agent` selector is preserved into each `/sp:dev-wrap <wbs>` handoff when supplied; omission remains omission. | off |
|
|
23
24
|
| `--next` | Chain-to-completion via the next-router. | off |
|
|
@@ -80,6 +81,6 @@ full distinction.
|
|
|
80
81
|
|
|
81
82
|
## Implementation
|
|
82
83
|
|
|
83
|
-
- 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); eligible `agent.run` stages may dispatch once to a native subagent with host fallback (task 0508). `--agent auto`, a name, or parallel mode retains the isolated per-task workflow boundary.
|
|
84
|
+
- 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); **omitted** `--agent`'s eligible `agent.run` stages may dispatch once to a native subagent with host fallback (task 0508); explicit `--agent inline` is the zero-dispatch carve-out. `--agent auto`, a name, or parallel mode retains the isolated per-task workflow boundary.
|
|
84
85
|
- Interactive sequential omit/inline: `Skill(skill="sp:spur-dev", args="runall-inline $ARGUMENTS")`.
|
|
85
86
|
- Explicit executor or parallel mode: `Skill(skill="sp:spur-dev", args="runall $ARGUMENTS")` → `sp:super-planner` agent.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Simplify code for clarity without changing behavior — reduce complexity in recent changes (or a given scope), preserving behavior exactly
|
|
3
|
+
role: coder
|
|
3
4
|
argument-hint: "[<path-or-scope>] [--scope <recent|all|path>] [--check <cmd>] [--agent <inline|auto|name>] [--auto]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Edit", "Skill"]
|
|
5
6
|
---
|
|
@@ -15,7 +16,7 @@ Wraps the **sp:code-simplification** skill.
|
|
|
15
16
|
| `[<path-or-scope>]` | Path or scope to simplify. | recent |
|
|
16
17
|
| `--scope` `<recent\|all\|path>` | Scope of the simplification pass. | recent |
|
|
17
18
|
| `--check` `<cmd>` | Validation command to iterate against. | project gate |
|
|
18
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing simplification. |
|
|
19
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing simplification. | omit |
|
|
19
20
|
| `--auto` | Skip objective HITL gates (taste gates: over-engineering removal & cross-file utility extraction still require approval). | off |
|
|
20
21
|
|
|
21
22
|
For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Generate or extend tests until the unit target is met
|
|
3
|
+
role: coder
|
|
3
4
|
argument-hint: "<target> [--coverage <n>] [--agent <inline|auto|name>] [--auto]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Write", "Edit", "Skill"]
|
|
5
6
|
---
|
|
@@ -14,7 +15,7 @@ Wraps the **sp:code-testing** skill.
|
|
|
14
15
|
| --- | --- | --- |
|
|
15
16
|
| `<target>` | File / module / path to generate tests for. | required |
|
|
16
17
|
| `--coverage` `<n>` | Coverage percentage target. | configured |
|
|
17
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing test work. |
|
|
18
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing test work. | omit |
|
|
18
19
|
| `--auto` | Skip objective HITL gates. | off |
|
|
19
20
|
|
|
20
21
|
For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Verify a task against its requirements and Acceptance Criteria — traceability check producing a PASS/PARTIAL/FAIL verdict with evidence
|
|
3
|
+
role: reviewer
|
|
3
4
|
argument-hint: "<wbs> [--agent <inline|auto|name>] [--fix <none|blockers-first|all>] [--focus <lens>] [--bdd] [--auto] [--force] [--next] [--skip-shippable]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
6
|
---
|
|
@@ -13,7 +14,7 @@ Wraps the **sp:code-verification** skill.
|
|
|
13
14
|
| Flag | Description | Default |
|
|
14
15
|
| --- | --- | --- |
|
|
15
16
|
| `<wbs>` | Task WBS to verify. | required |
|
|
16
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing verification. |
|
|
17
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing verification. | omit |
|
|
17
18
|
| `--fix` `<none\|blockers-first\|all>` | Auto-fix policy on findings. | none |
|
|
18
19
|
| `--focus` `<lens>` | Verification lens. | omitted |
|
|
19
20
|
| `--bdd` | Run BDD scenarios. | off |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Verify a batch of tasks against their requirements and Acceptance Criteria — batch traceability check producing per-task verdicts and a summary report
|
|
3
|
+
role: reviewer
|
|
3
4
|
argument-hint: "--tasks <selector> [--feature <id>] [--agent <inline|auto|name>] [--fix <none|blockers-first|all>] [--focus <lens>] [--bdd] [--auto] [--force] [--next] [--json] [--skip-shippable] [--worktree [<name>]]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
5
6
|
---
|
|
@@ -14,7 +15,7 @@ Wraps the **sp:spur-dev** and **sp:code-verification** skills.
|
|
|
14
15
|
| --- | --- | --- |
|
|
15
16
|
| `--tasks` `<selector>` | Task selector to verify. | required |
|
|
16
17
|
| `--feature` `<id>` | Restrict to a feature. | omitted |
|
|
17
|
-
| `--agent` `<inline\|auto\|name>` | Who runs each verification. |
|
|
18
|
+
| `--agent` `<inline\|auto\|name>` | Who runs each verification. | omit |
|
|
18
19
|
| `--fix` `<none\|blockers-first\|all>` | Auto-fix policy on findings. | none |
|
|
19
20
|
| `--focus` `<lens>` | Verification lens. | omitted |
|
|
20
21
|
| `--bdd` | Run BDD scenarios. | off |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Wrap up a single completed task — learnings, metrics, doc-sync, optional feature transition and branch cleanup
|
|
3
|
+
role: coder
|
|
3
4
|
argument-hint: "<wbs> [--agent <inline|auto|name>] [--auto] [--merge] [--dry-run]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "AskUserQuestion"]
|
|
5
6
|
---
|
|
@@ -13,7 +14,7 @@ Wraps the **wrapup-pipeline.yaml** workflow.
|
|
|
13
14
|
| Flag | Description | Default |
|
|
14
15
|
| --- | --- | --- |
|
|
15
16
|
| `<wbs>` | Task WBS to wrap. | required |
|
|
16
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the wrap's model-bearing steps. Wrap is workflow-backed: omit
|
|
17
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the wrap's model-bearing steps. Wrap is workflow-backed (headless): `omit` resolves to `agent.default` (objective trigger 3 — durable auditable run record required); explicit `--agent inline` is rejected with the stable special error — a headless workflow surface cannot host a session; `auto` tier-resolves an executor; a name pins that executor into `vars.agent`. | agent.default |
|
|
17
18
|
| `--auto` | Skip objective HITL gates. | off |
|
|
18
19
|
| `--merge` | Merge the wrap branch. | off |
|
|
19
20
|
| `--dry-run` | Render the wrap without writing. | off |
|
|
@@ -31,13 +32,13 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
31
32
|
- Resolve the executor **before** launching the workflow:
|
|
32
33
|
- `--agent <name>` → pass the name unchanged into `vars.agent`.
|
|
33
34
|
- `--agent auto` → tier-resolve a concrete executor first, then merge it into `vars.agent`.
|
|
34
|
-
- omit
|
|
35
|
+
- omit → resolve to `agent.default`; explicit `--agent inline` → rejected with the stable special error (headless surface — no dispatch, no `agent.default` fallback).
|
|
35
36
|
- Emit a pre-dispatch notice naming the override before `spur workflow run`, exactly:
|
|
36
|
-
`execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: inline|auto|<name
|
|
37
|
+
`execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: inline|auto|<name>` (explicit `inline` rejects before dispatch — no executor resolves), `executor: agent.default|<resolved-name>`.
|
|
37
38
|
- The wrap workflow still creates its durable run record (task_run_links / trace) — the notice reports the override, it does not change the workflow.
|
|
38
39
|
|
|
39
40
|
```bash
|
|
40
|
-
AGENT=… # resolved above: agent.default for
|
|
41
|
+
AGENT=… # resolved above: agent.default for omitted, tier-resolved for auto, unchanged for <name>; explicit inline errors (headless)
|
|
41
42
|
echo "execution surface: subprocess; reason: trigger 3 — durable auditable run record required; requested agent: <inline|auto|name>; executor: $AGENT"
|
|
42
43
|
VARS=$(jq -nc --arg tasks "[\"$WBS\"]" --arg agent "$AGENT" --arg profile "$PROFILE" --arg merge "$MERGE" \
|
|
43
44
|
'{tasks:$tasks, agent:$agent, profile:$profile, merge:$merge}')
|
|
@@ -45,4 +46,5 @@ spur workflow run .spur/workflows/wrapup-pipeline.yaml --vars "$VARS" [--dry-run
|
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
The executor resolution is described in the bullets above; the snippet's `AGENT` variable carries
|
|
48
|
-
the resolved name (`agent.default` for
|
|
49
|
+
the resolved name (`agent.default` for omitted `--agent`; explicit `--agent inline` is rejected
|
|
50
|
+
before dispatch).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Wrap up a batch of completed tasks — learnings, metrics, doc-sync, feature transition, optional branch cleanup
|
|
3
|
+
role: coder
|
|
3
4
|
argument-hint: "[--since <iso-date>] [--feature <id>] [--status <s>] [--agent <inline|auto|name>] [--auto] [--merge] [--dry-run]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "AskUserQuestion"]
|
|
5
6
|
---
|
|
@@ -15,7 +16,7 @@ Wraps the **wrapup-pipeline.yaml** workflow.
|
|
|
15
16
|
| `--since` `<iso-date>` | Wrap tasks completed since a date. | configured |
|
|
16
17
|
| `--feature` `<id>` | Wrap tasks in a feature. | omitted |
|
|
17
18
|
| `--status` `<s>` | Only wrap tasks in a status. | done |
|
|
18
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the wrap's model-bearing steps. Wrap is workflow-backed: omit
|
|
19
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the wrap's model-bearing steps. Wrap is workflow-backed (headless): `omit` resolves to `agent.default` (objective trigger 3 — durable auditable run record required); explicit `--agent inline` is rejected with the stable special error — a headless workflow surface cannot host a session; `auto` tier-resolves an executor; a name pins that executor into `vars.agent`. | agent.default |
|
|
19
20
|
| `--auto` | Skip objective HITL gates. | off |
|
|
20
21
|
| `--merge` | Merge wrap branches. | off |
|
|
21
22
|
| `--dry-run` | Render wraps without writing. | off |
|
|
@@ -33,13 +34,13 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
33
34
|
- Resolve the executor **before** launching the workflow:
|
|
34
35
|
- `--agent <name>` → pass the name unchanged into `vars.agent`.
|
|
35
36
|
- `--agent auto` → tier-resolve a concrete executor first, then merge it into `vars.agent`.
|
|
36
|
-
- omit
|
|
37
|
+
- omit → resolve to `agent.default`; explicit `--agent inline` → rejected with the stable special error (headless surface — no dispatch, no `agent.default` fallback).
|
|
37
38
|
- Emit a pre-dispatch notice naming the override before `spur workflow run`, exactly:
|
|
38
|
-
`execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: inline|auto|<name
|
|
39
|
+
`execution surface: subprocess`, `reason: trigger 3 — durable auditable run record required`, `requested agent: inline|auto|<name>` (explicit `inline` rejects before dispatch — no executor resolves), `executor: agent.default|<resolved-name>`.
|
|
39
40
|
- The wrap workflow still creates its durable run record — the notice reports the override, it does not change the workflow.
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
|
-
AGENT=… # resolved above: agent.default for
|
|
43
|
+
AGENT=… # resolved above: agent.default for omitted, tier-resolved for auto, unchanged for <name>; explicit inline errors (headless)
|
|
43
44
|
echo "execution surface: subprocess; reason: trigger 3 — durable auditable run record required; requested agent: <inline|auto|name>; executor: $AGENT"
|
|
44
45
|
VARS=$(jq -nc --arg tasks "$TASKS" --arg feature "$FEATURE" --arg agent "$AGENT" --arg profile "$PROFILE" --arg merge "$MERGE" \
|
|
45
46
|
'{tasks:$tasks, feature:$feature, agent:$agent, profile:$profile, merge:$merge}')
|
|
@@ -47,5 +48,6 @@ spur workflow run .spur/workflows/wrapup-pipeline.yaml --vars "$VARS" [--dry-run
|
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
The executor resolution is described in the bullets above; the snippet's `AGENT` variable carries
|
|
50
|
-
the resolved name (`agent.default` for
|
|
51
|
+
the resolved name (`agent.default` for omitted `--agent`; explicit `--agent inline` is rejected
|
|
52
|
+
before dispatch).
|
|
51
53
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Refine a constraint rule or preset, then re-verify it
|
|
3
|
+
role: scribe
|
|
3
4
|
argument-hint: "<rule-file-or-preset> [--intent \"<goal>\"] [--severity <sev>] [--scope <glob>] [--exempt <path>] [--disable <id>] [--override <id>] [--dry-run]"
|
|
4
5
|
allowed-tools: ["Bash", "Read", "Edit", "Skill"]
|
|
5
6
|
---
|
|
@@ -16,8 +16,13 @@
|
|
|
16
16
|
* emits an `allow` decision. A broken guard must never wedge an agent tool call.
|
|
17
17
|
*
|
|
18
18
|
* **Escape hatch:** `SPUR_CAREFUL=off` short-circuits to allow.
|
|
19
|
+
*
|
|
20
|
+
* Classification itself lives in `destructive-policy.ts` — one policy, every platform
|
|
21
|
+
* adapter. This file owns the Claude Code payload/decision shape only.
|
|
19
22
|
*/
|
|
20
23
|
|
|
24
|
+
import { classifyCommand } from './destructive-policy';
|
|
25
|
+
|
|
21
26
|
interface ToolPayload {
|
|
22
27
|
tool_name?: string;
|
|
23
28
|
tool_input?: { command?: string };
|
|
@@ -37,86 +42,6 @@ function preToolUseDecision(decision: Decision, reason?: string): never {
|
|
|
37
42
|
process.exit(0);
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
/** Well-known rebuildable caches a `rm -rf` may target without a warning. */
|
|
41
|
-
const SAFE_RM_TARGET =
|
|
42
|
-
/^(?:\.?\/)?(?:[\w.@-]+\/)*(?:node_modules|dist|\.next|coverage|build|\.turbo|\.cache|\.parcel-cache|out)\/?\*?$/;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Expand an argument string into the set of flags it sets, splitting short-flag
|
|
46
|
-
* clusters into their individual letters: `-Rf --force` → `{R, f, --force}`.
|
|
47
|
-
*
|
|
48
|
-
* Matching flags with ad-hoc regexes per call site is what let `rm -R` through —
|
|
49
|
-
* `-\w*r` only ever matched the lowercase spelling, even though `man rm` defines
|
|
50
|
-
* `-r` as "Equivalent to -R". Parsing once, case-preserved, makes that class of
|
|
51
|
-
* miss unrepresentable: a caller names every spelling it cares about explicitly.
|
|
52
|
-
*/
|
|
53
|
-
function parseFlags(args: string): Set<string> {
|
|
54
|
-
const flags = new Set<string>();
|
|
55
|
-
for (const token of args.trim().split(/\s+/)) {
|
|
56
|
-
if (token.length < 2 || !token.startsWith('-') || token === '--') continue;
|
|
57
|
-
if (token.startsWith('--')) {
|
|
58
|
-
flags.add(token.split('=')[0] as string); // `--force=x` → `--force`
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
for (const ch of token.slice(1)) flags.add(ch);
|
|
62
|
-
}
|
|
63
|
-
return flags;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** True when a `rm` invocation is both recursive and forced (any flag spelling). */
|
|
67
|
-
function isRecursiveForceRm(args: string): boolean {
|
|
68
|
-
const flags = parseFlags(args);
|
|
69
|
-
// POSIX rm accepts -r and -R interchangeably; --recursive is the GNU long form.
|
|
70
|
-
const recursive = flags.has('r') || flags.has('R') || flags.has('--recursive');
|
|
71
|
-
const force = flags.has('f') || flags.has('--force');
|
|
72
|
-
return recursive && force;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** True when every non-flag target of a `rm` invocation is a known-safe cache path. */
|
|
76
|
-
function rmTargetsAllSafe(args: string): boolean {
|
|
77
|
-
const targets = args
|
|
78
|
-
.trim()
|
|
79
|
-
.split(/\s+/)
|
|
80
|
-
.filter((t) => t.length > 0 && !t.startsWith('-'));
|
|
81
|
-
if (targets.length === 0) return false;
|
|
82
|
-
return targets.every((t) => SAFE_RM_TARGET.test(t));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Always-warn destructive patterns (no safe exception). */
|
|
86
|
-
const DESTRUCTIVE: Array<{ label: string; re: RegExp }> = [
|
|
87
|
-
{
|
|
88
|
-
label: 'a SQL DROP/TRUNCATE (DROP TABLE/DATABASE, TRUNCATE)',
|
|
89
|
-
re: /\b(?:DROP\s+(?:TABLE|DATABASE)|TRUNCATE(?:\s+TABLE)?)\b/i,
|
|
90
|
-
},
|
|
91
|
-
{ label: 'a force push (git push --force / -f)', re: /\bgit\s+push\b[^\n]*(?:--force(?!-with-lease)|\s-f\b)/i },
|
|
92
|
-
{
|
|
93
|
-
// `git push origin +main` forces that ref without any --force flag.
|
|
94
|
-
label: 'a force push via a + refspec (git push … +ref)',
|
|
95
|
-
re: /\bgit\s+push\b[^\n]*\s\+[\w./-]+/i,
|
|
96
|
-
},
|
|
97
|
-
{ label: 'a hard reset (git reset --hard)', re: /\bgit\s+reset\b[^\n]*--hard\b/i },
|
|
98
|
-
{
|
|
99
|
-
label: 'a working-tree discard (git checkout . / git restore .)',
|
|
100
|
-
re: /\bgit\s+(?:checkout|restore)\s+(?:--\s+)?\.(?:\s|$)/i,
|
|
101
|
-
},
|
|
102
|
-
{ label: 'a cluster delete (kubectl delete)', re: /\bkubectl\s+delete\b/i },
|
|
103
|
-
{ label: 'a docker prune (docker system prune)', re: /\bdocker\s+system\s+prune\b/i },
|
|
104
|
-
];
|
|
105
|
-
|
|
106
|
-
/** Return a human label for the destructive command, or null when the command is safe. */
|
|
107
|
-
function classifyCommand(command: string): string | null {
|
|
108
|
-
for (const rmMatch of command.matchAll(/\brm\b([^\n&|;]*)/g)) {
|
|
109
|
-
const args = rmMatch[1] ?? '';
|
|
110
|
-
if (isRecursiveForceRm(args) && !rmTargetsAllSafe(args)) {
|
|
111
|
-
return 'a recursive force remove (rm -rf)';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
for (const { label, re } of DESTRUCTIVE) {
|
|
115
|
-
if (re.test(command)) return label;
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
45
|
async function main(): Promise<void> {
|
|
121
46
|
if (process.env.SPUR_CAREFUL === 'off') preToolUseDecision('allow');
|
|
122
47
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* destructive-policy — the single destructive-command classifier shared by every
|
|
3
|
+
* platform guard (task 0215 R3; extracted from `careful-guard.ts`).
|
|
4
|
+
*
|
|
5
|
+
* **Why this module exists.** The policy used to live inline in `careful-guard.ts`
|
|
6
|
+
* (Claude Code) and was re-implemented with hand-rolled regexes in
|
|
7
|
+
* `pi/guard-extension.ts` (Pi). The two drifted: the Pi copy allowed
|
|
8
|
+
* `rm -rf node_modules /etc/nginx`, `rm -R --force /var/data`, `git push -f`, and
|
|
9
|
+
* `git push origin +main` — all of which the Claude matrix pins as `ask` — while
|
|
10
|
+
* warning on `git push --force-with-lease` and `rm -f config.json`, which it pins as
|
|
11
|
+
* `allow`. Seven of ten pinned cases diverged. A safety control with one
|
|
12
|
+
* implementation per platform is a safety control that is wrong on all but one of
|
|
13
|
+
* them, so the policy is defined once, here, and every adapter imports it.
|
|
14
|
+
*
|
|
15
|
+
* Adapters own **I/O only** (payload shape, how a prompt is raised). They must not
|
|
16
|
+
* re-derive classification.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Well-known rebuildable caches a `rm -rf` may target without a warning.
|
|
21
|
+
*
|
|
22
|
+
* Project-relative only — the leading `/` this used to accept made the exception
|
|
23
|
+
* match by *basename anywhere on the filesystem*, so `rm -rf /Users/me/dist` was
|
|
24
|
+
* treated as routine. Escaping targets are rejected by {@link escapesProject}
|
|
25
|
+
* before this is consulted; keeping the anchor tight is belt-and-braces.
|
|
26
|
+
*/
|
|
27
|
+
const SAFE_RM_TARGET =
|
|
28
|
+
/^(?:\.\/)?(?:[\w.@-]+\/)*(?:node_modules|dist|\.next|coverage|build|\.turbo|\.cache|\.parcel-cache|out)\/?\*?$/;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* True when a target points outside the project tree: an absolute path, a `~`
|
|
32
|
+
* home path, or one that walks out via `..`.
|
|
33
|
+
*
|
|
34
|
+
* Recursive deletion inside the project is routine (build caches, scratch dirs) and
|
|
35
|
+
* recoverable from git; recursive deletion *outside* it is neither, whatever the
|
|
36
|
+
* directory happens to be named. This is the axis that decides a bare `rm -r`,
|
|
37
|
+
* which is otherwise unguarded — `rm -r ./tmpdir` stays routine while
|
|
38
|
+
* `rm -r /Users/me/photos` prompts.
|
|
39
|
+
*/
|
|
40
|
+
function escapesProject(target: string): boolean {
|
|
41
|
+
const t = target.replace(/^['"]|['"]$/g, '');
|
|
42
|
+
if (t.startsWith('/') || t.startsWith('~')) return true;
|
|
43
|
+
if (t.startsWith('$')) return true; // `$HOME/...`, `"$HOME"/...` — unknown expansion
|
|
44
|
+
return t.split('/').includes('..');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Expand an argument string into the set of flags it sets, splitting short-flag
|
|
49
|
+
* clusters into their individual letters: `-Rf --force` → `{R, f, --force}`.
|
|
50
|
+
*
|
|
51
|
+
* Matching flags with ad-hoc regexes per call site is what let `rm -R` through —
|
|
52
|
+
* `-\w*r` only ever matched the lowercase spelling, even though `man rm` defines
|
|
53
|
+
* `-r` as "Equivalent to -R". Parsing once, case-preserved, makes that class of
|
|
54
|
+
* miss unrepresentable: a caller names every spelling it cares about explicitly.
|
|
55
|
+
*/
|
|
56
|
+
export function parseFlags(args: string): Set<string> {
|
|
57
|
+
const flags = new Set<string>();
|
|
58
|
+
for (const token of args.trim().split(/\s+/)) {
|
|
59
|
+
if (token.length < 2 || !token.startsWith('-') || token === '--') continue;
|
|
60
|
+
if (token.startsWith('--')) {
|
|
61
|
+
flags.add(token.split('=')[0] as string); // `--force=x` → `--force`
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const ch of token.slice(1)) flags.add(ch);
|
|
65
|
+
}
|
|
66
|
+
return flags;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** True when a `rm` invocation is both recursive and forced (any flag spelling). */
|
|
70
|
+
export function isRecursiveForceRm(args: string): boolean {
|
|
71
|
+
const flags = parseFlags(args);
|
|
72
|
+
// POSIX rm accepts -r and -R interchangeably; --recursive is the GNU long form.
|
|
73
|
+
const recursive = flags.has('r') || flags.has('R') || flags.has('--recursive');
|
|
74
|
+
const force = flags.has('f') || flags.has('--force');
|
|
75
|
+
return recursive && force;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* True when **every** non-flag target of a `rm` invocation is a known-safe cache
|
|
80
|
+
* path. Every, not some: `rm -rf node_modules /etc/nginx` must still warn, and a
|
|
81
|
+
* substring test over the whole argument string (the Pi copy's approach) let one
|
|
82
|
+
* cache path whitelist every other target in the same command.
|
|
83
|
+
*/
|
|
84
|
+
export function rmTargetsAllSafe(args: string): boolean {
|
|
85
|
+
const targets = rmTargets(args);
|
|
86
|
+
if (targets.length === 0) return false;
|
|
87
|
+
return targets.every((t) => SAFE_RM_TARGET.test(t));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Non-flag targets of a `rm` invocation. */
|
|
91
|
+
function rmTargets(args: string): string[] {
|
|
92
|
+
return args
|
|
93
|
+
.trim()
|
|
94
|
+
.split(/\s+/)
|
|
95
|
+
.filter((t) => t.length > 0 && !t.startsWith('-'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Always-warn destructive patterns (no safe exception). */
|
|
99
|
+
const DESTRUCTIVE: Array<{ label: string; re: RegExp }> = [
|
|
100
|
+
{
|
|
101
|
+
label: 'a SQL DROP/TRUNCATE (DROP TABLE/DATABASE, TRUNCATE)',
|
|
102
|
+
re: /\b(?:DROP\s+(?:TABLE|DATABASE)|TRUNCATE(?:\s+TABLE)?)\b/i,
|
|
103
|
+
},
|
|
104
|
+
{ label: 'a force push (git push --force / -f)', re: /\bgit\s+push\b[^\n]*(?:--force(?!-with-lease)|\s-f\b)/i },
|
|
105
|
+
{
|
|
106
|
+
// `git push origin +main` forces that ref without any --force flag.
|
|
107
|
+
label: 'a force push via a + refspec (git push … +ref)',
|
|
108
|
+
re: /\bgit\s+push\b[^\n]*\s\+[\w./-]+/i,
|
|
109
|
+
},
|
|
110
|
+
{ label: 'a hard reset (git reset --hard)', re: /\bgit\s+reset\b[^\n]*--hard\b/i },
|
|
111
|
+
{
|
|
112
|
+
label: 'a working-tree discard (git checkout . / git restore .)',
|
|
113
|
+
re: /\bgit\s+(?:checkout|restore)\s+(?:--\s+)?\.(?:\s|$)/i,
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
// `git clean` destroys UNTRACKED files — nothing in git to recover them from,
|
|
117
|
+
// which makes it the least recoverable command in this family. `-n`/`--dry-run`
|
|
118
|
+
// only lists, so the prompt is gated on the force flag git itself requires.
|
|
119
|
+
label: 'an untracked-file delete (git clean -f)',
|
|
120
|
+
re: /\bgit\s+clean\b[^\n]*(?:\s-[a-zA-Z]*f|\s--force\b)/i,
|
|
121
|
+
},
|
|
122
|
+
{ label: 'a cluster delete (kubectl delete)', re: /\bkubectl\s+delete\b/i },
|
|
123
|
+
{ label: 'a docker prune (docker system prune)', re: /\bdocker\s+system\s+prune\b/i },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/** Return a human label for the destructive command, or null when the command is safe. */
|
|
127
|
+
export function classifyCommand(command: string): string | null {
|
|
128
|
+
for (const rmMatch of command.matchAll(/\brm\b([^\n&|;]*)/g)) {
|
|
129
|
+
const args = rmMatch[1] ?? '';
|
|
130
|
+
const flags = parseFlags(args);
|
|
131
|
+
const recursive = flags.has('r') || flags.has('R') || flags.has('--recursive');
|
|
132
|
+
// A recursive delete reaching outside the project prompts whether or not
|
|
133
|
+
// `--force` was passed: `rm -r` deletes a whole tree without prompting for
|
|
134
|
+
// any writable file, and nothing outside the project is recoverable from git.
|
|
135
|
+
if (recursive && rmTargets(args).some(escapesProject)) {
|
|
136
|
+
return 'a recursive remove outside the project (rm -r on an absolute, ~, or ../ path)';
|
|
137
|
+
}
|
|
138
|
+
if (isRecursiveForceRm(args) && !rmTargetsAllSafe(args)) {
|
|
139
|
+
return 'a recursive force remove (rm -rf)';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const { label, re } of DESTRUCTIVE) {
|
|
143
|
+
if (re.test(command)) return label;
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|