@gobing-ai/spur 0.3.92 → 0.3.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/pipeline-budgets.json +0 -7
- package/config/plugin-scripts.json +10 -0
- package/config/rules/boundary/test-subpath-boundary.yaml +48 -0
- package/config/rules/strict/runtime-boundaries.yaml +2 -1
- package/config/rules/structure/protected-files.yaml +4 -0
- package/config/templates/AGENTS.md +4 -0
- package/config/templates/docs/02_ROADMAP.md +2 -0
- package/config/templates/docs/03_ARCHITECTURE.md +3 -1
- package/config/templates/docs/04_DESIGN.md +2 -0
- package/config/templates/docs/99_PROJECT_CONSTITUTION.md +10 -2
- package/config/workflow-candidates.json +72 -1
- package/config/workflows/feature-verification.yaml +1 -0
- package/config/workflows/history-anatomy.yaml +2 -0
- package/config/workflows/idea-pipeline.yaml +67 -18
- package/config/workflows/pr-review.yaml +8 -0
- package/config/workflows/task-pipeline.yaml +171 -39
- package/config/workflows/wayfinder-resolution.yaml +5 -0
- package/config/workflows/wrapup-pipeline.yaml +55 -14
- package/package.json +9 -9
- package/plugins/sp/README.md +8 -3
- package/plugins/sp/commands/dev-review-session.md +4 -4
- package/plugins/sp/commands/dev-review.md +16 -5
- package/plugins/sp/commands/dev-run.md +2 -2
- package/plugins/sp/commands/dev-runall.md +2 -2
- package/plugins/sp/hooks/pi/guard-extension.ts +17 -36
- package/plugins/sp/lib/idea-handoff.generated.mjs +8 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -0
- package/plugins/sp/lib/inline-run.generated.mjs +48 -19
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.mjs +117 -7
- package/plugins/sp/scripts/inline-run-setup.ts +211 -7
- package/plugins/sp/scripts/quality-gate.mjs +255 -6
- package/plugins/sp/scripts/quality-gate.ts +425 -9
- package/plugins/sp/scripts/residual-scan.mjs +13 -5
- package/plugins/sp/scripts/residual-scan.ts +34 -7
- package/plugins/sp/scripts/task-diffstat.mjs +156 -0
- package/plugins/sp/scripts/task-diffstat.ts +229 -0
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +181 -0
- package/plugins/sp/scripts/wrapup-drift-probe.ts +258 -0
- package/plugins/sp/scripts/wrapup-steps.mjs +60 -1
- package/plugins/sp/scripts/wrapup-steps.ts +89 -4
- package/plugins/sp/skills/brainstorm/SKILL.md +2 -0
- package/plugins/sp/skills/brainstorm/references/workflows.md +17 -2
- package/plugins/sp/skills/code-verification/SKILL.md +2 -2
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -2
- package/plugins/sp/skills/session-review/SKILL.md +11 -9
- package/plugins/sp/skills/spur-check/SKILL.md +112 -0
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -1
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -2
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +8 -2
- package/plugins/sp/skills/spur-dev/references/document-authoring.md +85 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +46 -18
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +24 -3
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +3 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +34 -2
- package/plugins/sp/skills/spur-dev/references/planning-workflow.md +5 -5
- package/plugins/sp/skills/spur-dev/templates/design.md +31 -0
- package/plugins/sp/skills/spur-dev/templates/plan.md +32 -0
- package/plugins/sp/skills/spur-doctor/SKILL.md +60 -14
- package/schemas/state-machine-workflow.schema.json +4 -0
- package/spur.js +19577 -19822
- package/web/_astro/BoardApp.C02hAHPO.js +1 -0
- package/web/_astro/{BoardApp.CerSBgis.js → BoardApp.FTEs3-N8.js} +107 -105
- package/web/_astro/{TaskDetail.DCqiC-OZ.js → TaskDetail.C-GdsS-t.js} +1 -1
- package/web/_astro/arc.uuAf51IT.js +1 -0
- package/web/_astro/{architectureDiagram-3BPJPVTR.DM_vp_hO.js → architectureDiagram-3BPJPVTR.CGe629A1.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DXVIiv0p.js → blockDiagram-GPEHLZMM.D1mGCq3p.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BbF_zCxW.js → c4Diagram-AAUBKEIU.CMsolcde.js} +1 -1
- package/web/_astro/channel.fsgl7o5j.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CAgQHpPC.js → chunk-2J33WTMH.CrGA3fik.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BN-5tpw4.js → chunk-4BX2VUAB.DsLVVla0.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.CnPkEEr0.js → chunk-55IACEB6.Dxc59Tfi.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.BQzQeMVm.js → chunk-727SXJPM.CaEVE2Wy.js} +4 -4
- package/web/_astro/{chunk-AQP2D5EJ.B6xNyDnL.js → chunk-AQP2D5EJ.BiJ4HXeI.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.C7f9Ih78.js → chunk-FMBD7UC4.Mxf1fru5.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CNV1dFXT.js → chunk-ND2GUHAM.pyOWQixH.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Cudn2TkJ.js → chunk-QZHKN3VN.BmEsg4vr.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D1NwP50q.js → classDiagram-4FO5ZUOK.BHhhMFTO.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D1NwP50q.js → classDiagram-v2-Q7XG4LA2.BHhhMFTO.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.B1wSL-Xb.js → cose-bilkent-S5V4N54A.2fH4YOlp.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BmK52w8G.js → cynefin-OW5HDTMX.C2j1_lKL.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Bfy5CTDT.js → dagre-BM42HDAG.hZ2NCTdT.js} +2 -2
- package/web/_astro/diagram-2AECGRRQ.Bfw5_EzK.js +43 -0
- package/web/_astro/diagram-5GNKFQAL.Bt1V_Tmk.js +10 -0
- package/web/_astro/{diagram-KO2AKTUF.CW_vMJ4z.js → diagram-KO2AKTUF.DEk-YFwp.js} +3 -3
- package/web/_astro/{diagram-LMA3HP47.B_8ZGF67.js → diagram-LMA3HP47.CDjndtQm.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.BppnHsdS.js → diagram-OG6HWLK6.cFnUHScG.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BEuHXcjJ.js → erDiagram-TEJ5UH35.DlhYp7NV.js} +5 -5
- package/web/_astro/{flowDiagram-I6XJVG4X.CH-UlnGr.js → flowDiagram-I6XJVG4X.DNTpsxfx.js} +4 -4
- package/web/_astro/{ganttDiagram-6RSMTGT7.BO81S85v.js → ganttDiagram-6RSMTGT7.DC_p36PI.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.XnPxPPZN.js → gitGraphDiagram-PVQCEYII.DkEqNI0P.js} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.JyjYRu_T.js → infoDiagram-5YYISTIA.CrjioCTG.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BBRBF-Fo.js → ishikawaDiagram-YF4QCWOH.BcK0CN8n.js} +5 -5
- package/web/_astro/{journeyDiagram-JHISSGLW.C_iymSyp.js → journeyDiagram-JHISSGLW.p0CbBDX1.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DdfW-Oqt.js → kanban-definition-UN3LZRKU.puHIFt6J.js} +7 -7
- package/web/_astro/{linear.C2_IkbZT.js → linear.Bb-3a1d7.js} +1 -1
- package/web/_astro/mermaid.core.CJDgXOJs.js +301 -0
- package/web/_astro/{mindmap-definition-RKZ34NQL.DAZIxQSK.js → mindmap-definition-RKZ34NQL.BoAZEI9t.js} +2 -2
- package/web/_astro/{pieDiagram-4H26LBE5.CN8sIhKM.js → pieDiagram-4H26LBE5.CJNSdqY7.js} +3 -3
- package/web/_astro/{quadrantDiagram-W4KKPZXB.3dGcX5GP.js → quadrantDiagram-W4KKPZXB.DtTy7_0Z.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BV2y4dd6.js → requirementDiagram-4Y6WPE33.BNYV98Xg.js} +3 -3
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Cqo15Tvo.js → sankeyDiagram-5OEKKPKP.DRq9DGHp.js} +4 -4
- package/web/_astro/{sequenceDiagram-3UESZ5HK.CROCPMJB.js → sequenceDiagram-3UESZ5HK.B7KdBFCm.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.RfXZrkFE.js → stateDiagram-AJRCARHV.CD62ZJ2H.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CPXmbBs9.js → stateDiagram-v2-BHNVJYJU.CeVk1TAj.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DdgKTiO8.js → timeline-definition-PNZ67QCA.C_ZPA5tT.js} +3 -3
- package/web/_astro/{vennDiagram-CIIHVFJN.CPNVSHF1.js → vennDiagram-CIIHVFJN.CrThO_FQ.js} +5 -5
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CQhA0Jyr.js → wardleyDiagram-YWT4CUSO.DSZCA5nl.js} +3 -3
- package/web/_astro/{xychartDiagram-2RQKCTM6.n61BWyy4.js → xychartDiagram-2RQKCTM6.KI7baTuk.js} +1 -1
- package/web/index.html +1 -1
- package/config/workflows/decision-routing-example.yaml +0 -134
- package/web/_astro/BoardApp.eoTz0pZs.js +0 -1
- package/web/_astro/arc.CPwg6Rw0.js +0 -1
- package/web/_astro/channel.MYZLKNwy.js +0 -1
- package/web/_astro/diagram-2AECGRRQ.DhNnvUvX.js +0 -43
- package/web/_astro/diagram-5GNKFQAL.lTX5KwnS.js +0 -10
- package/web/_astro/mermaid.core.GAOYeSR0.js +0 -303
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Review code for a task or path — multi-dimensional review across functional traceability, SECUA quality, and architectural depth. Triggers: \"review this\", \"check the code\", \"SECUA review\", \"dev review\", \"audit this\"."
|
|
2
|
+
description: "Review code for a task or path — multi-dimensional review across functional traceability, SECUA quality, and architectural depth. With --triage: fix small findings directly, then file the rest as implement-ready tasks. Triggers: \"review this\", \"check the code\", \"SECUA review\", \"dev review\", \"audit this\", \"triage review findings\"."
|
|
3
3
|
role: reviewer
|
|
4
|
-
argument-hint: "[<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--
|
|
4
|
+
argument-hint: "[<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--triage] [--worktree [<name>]]"
|
|
5
5
|
allowed-tools: ["Bash", "Read", "Skill"]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -16,17 +16,28 @@ Wraps the **sp:functional-review**, **sp:code-verification**, and **sp:code-impr
|
|
|
16
16
|
| `[<wbs\|path>]` | Task WBS or source path to review. | cwd |
|
|
17
17
|
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing review. | omit |
|
|
18
18
|
| `--focus` `<dims>` | Review dimensions (functional / SECUA / architecture). | all |
|
|
19
|
-
| `--
|
|
19
|
+
| `--triage` | After the review: fix small findings directly, file the rest as one or more implement-ready tasks. | off (report-only) |
|
|
20
|
+
| `--worktree` `[<name>]` | Run the triage in an isolated git worktree; FF-merge on success, retain on failure. Bare `--worktree` creates a fresh tree; `--worktree <name>` adopts an existing worktree by name/path/branch. Requires `--triage`. | off |
|
|
20
21
|
|
|
21
22
|
For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag-glossary.md).
|
|
22
23
|
|
|
23
24
|
## Usage
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
```
|
|
27
|
+
/sp:dev-review [<wbs|path>] [--agent <inline|auto|name>] [--focus <dims>] [--triage] [--worktree [<name>]]
|
|
28
|
+
/sp:dev-review scripts --triage
|
|
29
|
+
/sp:dev-review 0962 --triage --worktree
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Put the target first: a bare token after `--worktree` is read as the worktree name.
|
|
26
33
|
|
|
27
34
|
## Implementation
|
|
28
35
|
|
|
29
36
|
- Apply the [inline-default execution-surface contract](../skills/spur-dev/references/cross-cutting.md#inline-default-execution-surface).
|
|
30
37
|
- WBS mode (`<wbs>`): `Skill(skill="sp:functional-review", args="<wbs> $ARGUMENTS")` + `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<wbs> $ARGUMENTS")` (functional traceability + SECUA framework + architectural depth; the three skills return review fragments and the coordinator (`sp:super-reviewer`) writes the combined `## Review` — F92 0593 R1)
|
|
31
38
|
- Path mode (`<path>`): `Skill(skill="sp:code-verification", args="review $ARGUMENTS")` + `Skill(skill="sp:code-improvement", args="<path> $ARGUMENTS")` (advisory SECUA quality + architectural depth; performs no task mutation)
|
|
32
|
-
- `--
|
|
39
|
+
- `--triage`: after the review returns, run the triage protocol in [dev-operations.md § 2. review](../skills/spur-dev/references/dev-operations.md#2-review) **inline in this session** — the review skills and `sp:super-reviewer` stay report-only.
|
|
40
|
+
- Without `--worktree`, every write lands in the current working tree on the current branch: no branch creation, no checkout, no commit.
|
|
41
|
+
- `--worktree [<name>]`: the [execution-batch.md § Worktree isolation](../skills/spur-dev/references/execution-batch.md#worktree-isolation---worktree-name) lifecycle as a run of one (marker `command` = `dev-review`, `selector` = the target). Rejected without `--triage`.
|
|
42
|
+
While it runs, fixes and filed tasks land in the worktree copy; the main tree shows them only after the FF-merge.
|
|
43
|
+
- `--fix`: Deprecated (still accepted: no-op + warning; route remediation to `--triage` or `/sp:dev-verify --fix`). **`--next` removed** (feature H8, 2026-07-31) — it was a deprecated no-op; route progression to `/sp:dev-next`. **was: `--next` deprecated no-op.**
|
|
@@ -15,7 +15,7 @@ Wraps the **sp:spur-dev** and **sp:code-implementation** skills.
|
|
|
15
15
|
| --- | --- | --- |
|
|
16
16
|
| `<wbs>` | Task WBS to run. | required |
|
|
17
17
|
| `--mode` `<full\|implement>` | Full pipeline or single implement step. | full |
|
|
18
|
-
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. omit and explicit `--agent inline` resolve identically (task 0687): eligible stages dispatch once to a native subagent with host-session fallback (0508 eligibility); otherwise every stage executes in the invoking session. `auto` or a name keeps subprocess dispatch.. | omit |
|
|
18
|
+
| `--agent` `<inline\|auto\|name>` | Who runs the model-bearing stages. omit and explicit `--agent inline` resolve identically (task 0687): eligible stages dispatch once to a native subagent with host-session fallback (0508 eligibility); otherwise every stage executes in the invoking session. `auto` or a name keeps subprocess dispatch.. The opt-in `fleet` selector (0942) maps the run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
|
|
19
19
|
| `--auto` | Skip objective HITL confirmations. | off |
|
|
20
20
|
| `--next` | Chain-to-completion via the next-router. | off |
|
|
21
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 |
|
|
@@ -40,7 +40,7 @@ For shared semantics, see the [flag glossary](../skills/spur-dev/references/flag
|
|
|
40
40
|
|
|
41
41
|
**Flags:**
|
|
42
42
|
|
|
43
|
-
- `--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, whose eligible `agent.run` stages dispatch once to a native subagent and otherwise run in the host (0508 eligibility — task 0687 resolved-inline) 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).
|
|
43
|
+
- `--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, whose eligible `agent.run` stages dispatch once to a native subagent and otherwise run in the host (0508 eligibility — task 0687 resolved-inline) 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; `--agent fleet` maps the run to `vars.executor: 'fleet'` instead (fleet dispatch, 0942). 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).
|
|
44
44
|
- `--escalation-file <path>` (implement step only, 0933) — names the Q/A escalation transcript
|
|
45
45
|
(default `.spur/run/<wbs>-escalation.md`). Absent on a first attempt means "no prior
|
|
46
46
|
escalations". When the implement agent needs an operator decision it appends its question to
|
|
@@ -18,7 +18,7 @@ Wraps the **sp:spur-dev** skill.
|
|
|
18
18
|
| `--mode` `<sequential\|parallel>` | Batch execution order. | sequential |
|
|
19
19
|
| `--keep-going` | Continue past per-task failures. | off |
|
|
20
20
|
| `--auto` | Skip objective HITL gates. | off |
|
|
21
|
-
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below)). `auto`, a name, parallel mode, and headless invocation use subprocesses. | omit |
|
|
21
|
+
| `--agent` `<inline\|auto\|name>` | Who runs each task's pipeline stages. Interactive sequential omit/`inline` uses the host-session driver with unified inline semantics (task 0687 — see below)). `auto`, a name, parallel mode, and headless invocation use subprocesses. The opt-in `fleet` selector (0942) maps each task run to `executor: 'fleet'` so `agent.run` stages dispatch through the fleet control plane (ADR-126) instead of a subprocess. | omit |
|
|
22
22
|
| `--json` | Emit structured JSON. | off |
|
|
23
23
|
| `--wrap` | Run the wrap hop **once for the batch** over the `done` subset only (Step 6 of execution-batch.md): `vars.feature` is passed only when every frozen task is `done`/`cancelled`; an empty done subset skips the wrap with a reason. The `--agent` selector is preserved into the `/sp:dev-wrap` handoff when supplied; omission remains omission. | off |
|
|
24
24
|
| `--next` | Chain-to-completion via the next-router. | off |
|
|
@@ -49,7 +49,7 @@ only after its in-set dependencies are **integrated** onto the base ref), `--kee
|
|
|
49
49
|
halts on first failure), `--auto`
|
|
50
50
|
(sets `profile=auto` on each per-task run, skipping the HITL approve gate), `--agent <inline|auto|name>`
|
|
51
51
|
(names who runs the pipeline's stages — interactive sequential omit/`inline` uses the host driver;
|
|
52
|
-
`auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; parallel mode
|
|
52
|
+
`auto` or a name is merged into each per-task `vars.agent` and `vars.implementAgent`; `fleet` maps each task run to `executor: 'fleet'` instead (fleet dispatch, 0942); parallel mode
|
|
53
53
|
retains isolated subprocesses, ADR-047), `--json` (emit the
|
|
54
54
|
report as JSON), `--wrap` (trigger
|
|
55
55
|
`wrapup-pipeline.yaml` after the batch completes), `--next`
|
|
@@ -25,6 +25,7 @@ import { isAbsolute, join, resolve } from 'node:path';
|
|
|
25
25
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
26
26
|
import { getEnvVar, getEnvVars } from '../../lib/env';
|
|
27
27
|
import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
|
|
28
|
+
import { cappedByteLength, truncateSummary } from '../context-post-tool';
|
|
28
29
|
import { classifyCommand } from '../destructive-policy';
|
|
29
30
|
import { couldBeTaskFile } from '../task-file-policy';
|
|
30
31
|
|
|
@@ -36,8 +37,6 @@ import { couldBeTaskFile } from '../task-file-policy';
|
|
|
36
37
|
const spurContextDir = (): string => join(process.cwd(), '.spur', 'context');
|
|
37
38
|
const sessionFilePath = (): string => join(spurContextDir(), '.session.json');
|
|
38
39
|
const ledgerFilePath = (): string => join(spurContextDir(), 'token-ledger.jsonl');
|
|
39
|
-
const REDACTION_CAP = 4096;
|
|
40
|
-
const SUMMARY_MAX_CHARS = 200;
|
|
41
40
|
|
|
42
41
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
43
42
|
|
|
@@ -122,39 +121,17 @@ interface ToolEvent {
|
|
|
122
121
|
tool_input?: Record<string, unknown>;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// Rough estimate: ~4 chars per token for English text
|
|
128
|
-
return Math.ceil(text.length / 4);
|
|
129
|
-
}
|
|
130
|
-
|
|
124
|
+
// Summaries persist to disk, so they go through the same scrubber as the Claude hook —
|
|
125
|
+
// the local copy this replaced stored the raw command (secrets included) as `summary`.
|
|
131
126
|
function summarizeToolEvent(event: ToolEvent): string {
|
|
132
127
|
const input = event.tool_input ?? {};
|
|
133
128
|
const candidates = [input.file_path, input.command, input.pattern, input.glob_pattern, input.glob, input.path];
|
|
134
129
|
for (const c of candidates) {
|
|
135
|
-
if (typeof c === 'string' && c.trim())
|
|
136
|
-
const s = c.trim();
|
|
137
|
-
return s.length > SUMMARY_MAX_CHARS ? `${s.slice(0, SUMMARY_MAX_CHARS - 3)}...` : s;
|
|
138
|
-
}
|
|
130
|
+
if (typeof c === 'string' && c.trim()) return truncateSummary(c);
|
|
139
131
|
}
|
|
140
132
|
return `(${event.tool_name ?? 'unknown'})`;
|
|
141
133
|
}
|
|
142
134
|
|
|
143
|
-
function redactText(text: string): string {
|
|
144
|
-
// Strip obvious secret patterns
|
|
145
|
-
let result = text.slice(0, REDACTION_CAP);
|
|
146
|
-
result = result.replace(
|
|
147
|
-
/-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----[\s\S]*?-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----/g,
|
|
148
|
-
'[REDACTED: PRIVATE KEY]',
|
|
149
|
-
);
|
|
150
|
-
result = result.replace(/ghp_[A-Za-z0-9]{36}/g, '[REDACTED: GITHUB_TOKEN]');
|
|
151
|
-
result = result.replace(/gho_[A-Za-z0-9]{36}/g, '[REDACTED: GITHUB_TOKEN]');
|
|
152
|
-
result = result.replace(/sk-[A-Za-z0-9]{20,}/g, '[REDACTED: API_KEY]');
|
|
153
|
-
result = result.replace(/api[_-]key['"]?\s*[:=]\s*['"][A-Za-z0-9_-]{16,}['"]/gi, '[REDACTED: API_KEY]');
|
|
154
|
-
result = result.replace(/AKIA[0-9A-Z]{16}/g, '[REDACTED: AWS_KEY]');
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
135
|
function appendToLedger(event: ToolEvent, command: string | undefined): void {
|
|
159
136
|
try {
|
|
160
137
|
if (!existsSync(spurContextDir())) return;
|
|
@@ -162,16 +139,19 @@ function appendToLedger(event: ToolEvent, command: string | undefined): void {
|
|
|
162
139
|
if (!sessionId) return;
|
|
163
140
|
|
|
164
141
|
const summary = summarizeToolEvent(event);
|
|
165
|
-
const tokens = command ?
|
|
142
|
+
const tokens = command ? Math.ceil(cappedByteLength(command) / 4) : 0;
|
|
143
|
+
const input = event.tool_input ?? {};
|
|
144
|
+
// Pi tool names are lowercase (`read`) and its write/edit tools take `path`, not `file_path`.
|
|
145
|
+
const path = typeof input.file_path === 'string' ? input.file_path : input.path;
|
|
166
146
|
|
|
167
147
|
const ledgerEntry = JSON.stringify({
|
|
168
148
|
session: sessionId,
|
|
169
|
-
type: event.tool_name === '
|
|
149
|
+
type: event.tool_name?.toLowerCase() === 'read' ? 'read' : 'write',
|
|
170
150
|
tool: event.tool_name,
|
|
171
|
-
|
|
151
|
+
file: typeof path === 'string' ? path : undefined,
|
|
172
152
|
summary,
|
|
173
153
|
tokens,
|
|
174
|
-
|
|
154
|
+
ts: new Date().toISOString(),
|
|
175
155
|
});
|
|
176
156
|
|
|
177
157
|
appendFileSync(ledgerFilePath(), `${ledgerEntry}\n`);
|
|
@@ -216,7 +196,7 @@ function initSession(): void {
|
|
|
216
196
|
type: 'session_start',
|
|
217
197
|
agent: session.agent,
|
|
218
198
|
model: session.model,
|
|
219
|
-
|
|
199
|
+
ts: session.started_at,
|
|
220
200
|
});
|
|
221
201
|
appendFileSync(ledgerFilePath(), `${startEntry}\n`);
|
|
222
202
|
} catch {
|
|
@@ -256,7 +236,7 @@ function cleanupSession(): void {
|
|
|
256
236
|
reads,
|
|
257
237
|
writes,
|
|
258
238
|
tokens,
|
|
259
|
-
|
|
239
|
+
ts: new Date().toISOString(),
|
|
260
240
|
});
|
|
261
241
|
appendFileSync(ledgerFilePath(), `${endEntry}\n`);
|
|
262
242
|
|
|
@@ -272,8 +252,9 @@ function cleanupSession(): void {
|
|
|
272
252
|
export default function (pi: ExtensionAPI): void {
|
|
273
253
|
// ── task-write-guard + careful-guard ──────────────────────────────
|
|
274
254
|
pi.on('tool_call', async (event, ctx) => {
|
|
275
|
-
// task-write-guard: block Write/Edit to Spur task files
|
|
276
|
-
|
|
255
|
+
// task-write-guard: block Write/Edit to Spur task files.
|
|
256
|
+
// Both guards honor the same `off` escape hatches as the Claude hooks.
|
|
257
|
+
if ((event.toolName === 'write' || event.toolName === 'edit') && getEnvVar('SPUR_WRITE_GUARD') !== 'off') {
|
|
277
258
|
const input = event.input as Record<string, unknown> | undefined;
|
|
278
259
|
// Pi's write/edit tools use `path` (not Claude Code's `file_path`)
|
|
279
260
|
const filePath = resolveInputPath(input);
|
|
@@ -287,7 +268,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
287
268
|
}
|
|
288
269
|
|
|
289
270
|
// careful-guard: warn on destructive Bash commands
|
|
290
|
-
if (event.toolName === 'bash') {
|
|
271
|
+
if (event.toolName === 'bash' && getEnvVar('SPUR_CAREFUL') !== 'off') {
|
|
291
272
|
const input = event.input as Record<string, unknown> | undefined;
|
|
292
273
|
const command = typeof input?.command === 'string' ? input.command : '';
|
|
293
274
|
const hit = command ? classifyCommand(command) : null;
|