@gobing-ai/spur 0.3.84 → 0.3.86
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.global.yaml +11 -6
- package/config/pipeline-budgets.json +0 -25
- package/config/plugin-scripts.json +0 -5
- package/config/rules/boundary/env-var-hygiene.yaml +41 -0
- package/config/transition-shims.json +1 -1
- package/config/workflow-candidates.json +41 -0
- package/config/workflows/feature-lifecycle.yaml +4 -2
- package/config/workflows/feature-verification.yaml +70 -0
- package/config/workflows/idea-pipeline.yaml +25 -13
- package/config/workflows/task-pipeline.yaml +43 -12
- package/config/workflows/wrapup-pipeline.yaml +71 -8
- package/package.json +9 -9
- package/plugins/sp/README.md +0 -3
- package/plugins/sp/agents/super-planner.md +2 -1
- package/plugins/sp/hooks/agent-hint.ts +5 -4
- package/plugins/sp/hooks/careful-guard.ts +3 -1
- package/plugins/sp/hooks/context-post-tool.ts +2 -1
- package/plugins/sp/hooks/context-session-start.ts +4 -3
- package/plugins/sp/hooks/context-session-stop.ts +2 -1
- package/plugins/sp/hooks/pi/guard-extension.ts +4 -3
- package/plugins/sp/hooks/task-write-guard.ts +4 -3
- package/plugins/sp/lib/idea-handoff.generated.mjs +260 -260
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +13 -4
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +6 -4
- package/plugins/sp/scripts/feature-sync-bounded.mjs +10 -2
- package/plugins/sp/scripts/feature-sync-bounded.ts +2 -1
- package/plugins/sp/scripts/idea-handoff.mjs +6 -1
- package/plugins/sp/scripts/idea-handoff.ts +4 -1
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +1 -1
- package/plugins/sp/scripts/inline-run-setup.ts +185 -2
- package/plugins/sp/scripts/pr-reviewing.mjs +8 -1
- package/plugins/sp/scripts/pr-reviewing.ts +2 -1
- package/plugins/sp/scripts/quality-gate.mjs +8 -1
- package/plugins/sp/scripts/quality-gate.ts +2 -1
- package/plugins/sp/scripts/surface-drift-inventory.ts +2 -5
- package/plugins/sp/scripts/task-evidence-precheck.ts +2 -1
- package/plugins/sp/scripts/task-size-precheck.ts +6 -4
- package/plugins/sp/scripts/verify-answer-lint.ts +2 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +10 -2
- package/plugins/sp/scripts/workflow-step-profile.ts +2 -1
- package/plugins/sp/scripts/wrapup-steps.mjs +8 -1
- package/plugins/sp/scripts/wrapup-steps.ts +2 -1
- package/plugins/sp/skills/spur-cli/references/workflows.md +8 -7
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -5
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +59 -1
- package/schemas/state-machine-workflow.schema.json +5 -0
- package/schemas/transition-flow-workflow.schema.json +5 -0
- package/spur.js +21393 -20831
- package/web/_astro/BoardApp.CJiqp5pS.js +1 -0
- package/web/_astro/{BoardApp.D4mBUGLT.js → BoardApp.yBBcFXWP.js} +60 -60
- package/web/_astro/{TaskDetail.DhCWaOPR.js → TaskDetail.DqFJbRFc.js} +1 -1
- package/web/_astro/{arc.D3X0nfq-.js → arc.DL-BpHoi.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.BQDoiGme.js → architectureDiagram-3BPJPVTR.9IdQYyDq.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BjHekBbM.js → blockDiagram-GPEHLZMM.BKsFCqTl.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.BvHt171l.js → c4Diagram-AAUBKEIU.DhwI0dh1.js} +1 -1
- package/web/_astro/channel.CX5453qQ.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.CwxGXlDR.js → chunk-2J33WTMH.B3QVmQ9S.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.Dia1YtwU.js → chunk-4BX2VUAB.DBSuqs9F.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.Oz9epCXa.js → chunk-55IACEB6.BSYTWAYD.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.DKFYJjYJ.js → chunk-727SXJPM.cWVuxXfS.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.AD7w5oVz.js → chunk-AQP2D5EJ.DpU_Ob3d.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D0OqTj52.js → chunk-FMBD7UC4.BykFkyji.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.CU5ppR7j.js → chunk-ND2GUHAM.DwgHlMdY.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.Bu1rFztw.js → chunk-QZHKN3VN.CusXUGWM.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.C0Pgm5yE.js → classDiagram-4FO5ZUOK.fx0ObzkN.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js → classDiagram-v2-Q7XG4LA2.fx0ObzkN.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.C0ecLb1e.js → cose-bilkent-S5V4N54A.Z4HgOlsd.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.CpStVmhO.js → cynefin-OW5HDTMX.B5dIZHJu.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.BPWN3W9f.js → dagre-BM42HDAG.DT70Q_Yw.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DnXbEot4.js → diagram-2AECGRRQ.DqHA3XBF.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.BYJsgUiI.js → diagram-5GNKFQAL.BmCem957.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CU4Bqprx.js → diagram-KO2AKTUF.sn0-hrE0.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BQLOANIu.js → diagram-LMA3HP47.BSHe9tVc.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.Cn1UVhqj.js → diagram-OG6HWLK6.DHIc-86k.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.C8FdZyvA.js → erDiagram-TEJ5UH35.Bxayrs7v.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.QSZCR8Uo.js → flowDiagram-I6XJVG4X.BkzoE_5I.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.O1Py31MY.js → ganttDiagram-6RSMTGT7.okT6CvTo.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.DoeJRBfY.js → gitGraphDiagram-PVQCEYII.CJuYbhC7.js} +1 -1
- package/web/_astro/{index.TPF1FerP.css → index.CcU5weKX.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.9npLJecW.js → infoDiagram-5YYISTIA.RqLy7nBo.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.DXanvOr3.js → ishikawaDiagram-YF4QCWOH.BwIcoagw.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.DxbVjxAv.js → journeyDiagram-JHISSGLW.UB1VbWtH.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BDk6_iq0.js → kanban-definition-UN3LZRKU.AaxMKpTk.js} +1 -1
- package/web/_astro/{linear.C9cCDjT3.js → linear.Nv_xOUjP.js} +1 -1
- package/web/_astro/{mermaid.core.CpHRe5nH.js → mermaid.core.Bc4LqQgX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.D-6C85bn.js → mindmap-definition-RKZ34NQL.oKUvU_qi.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.B3Oxodty.js → pieDiagram-4H26LBE5.DQk0oo03.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.QZ_9Hshu.js → quadrantDiagram-W4KKPZXB.BdDjESDa.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.BimvuK53.js → requirementDiagram-4Y6WPE33.C2u9hUeH.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.D5Kkpx2C.js → sankeyDiagram-5OEKKPKP.CDEoiJST.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B4YR6fAE.js → sequenceDiagram-3UESZ5HK.D_hT_GAT.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.Bt2wkZok.js → stateDiagram-AJRCARHV.DI8RYG0b.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js → stateDiagram-v2-BHNVJYJU.Bkxz4DnP.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.DmiTBL8w.js → timeline-definition-PNZ67QCA.DSY-kH3-.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.KNJV40mf.js → vennDiagram-CIIHVFJN.CpaDtuGr.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BVAaKgWH.js → wardleyDiagram-YWT4CUSO.DujQWvo8.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.D5Jx7zB2.js → xychartDiagram-2RQKCTM6.DcM5Y4b9.js} +1 -1
- package/web/index.html +2 -2
- package/config/workflows/basic.yaml +0 -146
- package/config/workflows/docs-pipeline.yaml +0 -350
- package/config/workflows/feature-dev.yaml +0 -288
- package/plugins/sp/scripts/feature-dev-precheck.mjs +0 -146
- package/plugins/sp/scripts/feature-dev-precheck.ts +0 -238
- package/web/_astro/BoardApp.DFPYpI5O.js +0 -1
- package/web/_astro/channel.BlQwxyoI.js +0 -1
|
@@ -13,10 +13,12 @@
|
|
|
13
13
|
# pauses,
|
|
14
14
|
# even under --auto (Iron Law #6: irreversible action -> surface to human).
|
|
15
15
|
#
|
|
16
|
-
# Shape: start -> task-resolve -> doc-sync -> metrics-record
|
|
16
|
+
# Shape: start -> task-resolve -> doc-sync -> learnings-append -> metrics-record
|
|
17
17
|
# -> feature-transition (conditional: if vars.feature set)
|
|
18
18
|
# -> branch-cleanup (conditional: if vars.merge=true)
|
|
19
19
|
# -> done
|
|
20
|
+
# doc-sync routes a contract violation to `repair` (cheap, no
|
|
21
|
+
# re-dispatch — ADR-118 pilot) and an executor failure to `failed`;
|
|
20
22
|
# task-resolve with a VALIDATED empty list short-circuits to `skipped`;
|
|
21
23
|
# invalid wrap input, an unresolved/non-completed task, a failed metrics
|
|
22
24
|
# capture, a failed required sync, or a failed feature gate routes to
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
# merge — set --vars '{"merge":"true"}' to run branch cleanup (irreversible
|
|
32
34
|
# HITL)
|
|
33
35
|
# spurBin — PATH-independent spur invocation (overridden by CLI at run start)
|
|
34
|
-
# agent — agent for agent.run steps (default:
|
|
36
|
+
# agent — agent for agent.run steps (default: auto → `agent.default` in config)
|
|
35
37
|
#
|
|
36
38
|
# Reliability (aligned with task-pipeline / ADR-043):
|
|
37
39
|
# - Prefer pure slash commands when a command exists; free-form inputs remain
|
|
@@ -72,7 +74,7 @@
|
|
|
72
74
|
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
73
75
|
kind: state-machine
|
|
74
76
|
name: wrapup-pipeline
|
|
75
|
-
version: "
|
|
77
|
+
version: "4"
|
|
76
78
|
description: "Post-execution wrap-up: doc-sync (doc drift + learning capture), metrics, feature-transition, branch-cleanup"
|
|
77
79
|
iterationBound: 10
|
|
78
80
|
initialState: start
|
|
@@ -168,13 +170,16 @@ states:
|
|
|
168
170
|
learnings (task 0607 R2: two adjacent agent.run hops merged, count 2 -> 1;
|
|
169
171
|
no operator-decision pause or verdict boundary sits between them). No dedicated
|
|
170
172
|
slash command yet — free-form skill pointer until a /sp:doc-evolve command
|
|
171
|
-
exists (ADR-043 residual).
|
|
172
|
-
a
|
|
173
|
-
|
|
173
|
+
exists (ADR-043 residual). This is the expensive stage; a contract violation
|
|
174
|
+
(a clean exit that missed its declared answerFile/expectFile post-condition)
|
|
175
|
+
routes to the cheap `repair` edge instead of a full re-dispatch (ADR-118
|
|
176
|
+
pilot, 0871) — `onError: continue` lets the transition guards read the
|
|
177
|
+
agent's result instead of halting the run.
|
|
174
178
|
onEnter:
|
|
175
179
|
# (warn) free-form prompt: no /sp:doc-evolve command surface yet (ADR-043
|
|
176
180
|
# residual, 0607 R2 one-query merge); capture is bounded by expectFile.
|
|
177
181
|
- kind: agent.run
|
|
182
|
+
onError: continue
|
|
178
183
|
options:
|
|
179
184
|
agent: ${vars.agent}
|
|
180
185
|
input: >-
|
|
@@ -192,6 +197,13 @@ states:
|
|
|
192
197
|
answerFile: .spur/run/${vars.__runId}-wrapup-learnings.md
|
|
193
198
|
expectFile: .spur/run/${vars.__runId}-wrapup-learnings.md
|
|
194
199
|
timeoutMs: ${vars.stepTimeoutMs}
|
|
200
|
+
|
|
201
|
+
- id: learnings-append
|
|
202
|
+
description: >
|
|
203
|
+
Best-effort soft append of the captured learnings to .spur/memory/learnings.md
|
|
204
|
+
(0607 R2): an empty capture skips without aborting wrap-up. Runs only after
|
|
205
|
+
doc-sync's contract held; a contract violation never reaches this state.
|
|
206
|
+
onEnter:
|
|
195
207
|
# (warn) learnings append is intentionally soft (empty capture skips
|
|
196
208
|
# without aborting wrap-up, 0607 R2).
|
|
197
209
|
- kind: shell
|
|
@@ -207,6 +219,21 @@ states:
|
|
|
207
219
|
fi &&
|
|
208
220
|
exit 0
|
|
209
221
|
|
|
222
|
+
- id: repair
|
|
223
|
+
description: >
|
|
224
|
+
Contract-violation repair (ADR-118 pilot, 0871): doc-sync's agent.run missed
|
|
225
|
+
its declared post-condition after a clean exit, so wrap-up does NOT re-dispatch
|
|
226
|
+
the expensive agent on this attempt. The miss is recorded to a run-scoped
|
|
227
|
+
repair status and wrap-up proceeds to metrics-record — the run log and action
|
|
228
|
+
trace already name the violated contract and the observed value.
|
|
229
|
+
onEnter:
|
|
230
|
+
- kind: shell
|
|
231
|
+
options:
|
|
232
|
+
command: >-
|
|
233
|
+
mkdir -p .spur/run &&
|
|
234
|
+
printf 'contract-violation: doc-sync learnings capture missed its declared contract — repair skipped re-dispatch (ADR-118 pilot)\n' > ".spur/run/$__runId-wrapup-repair.status" &&
|
|
235
|
+
exit 0
|
|
236
|
+
|
|
210
237
|
- id: metrics-record
|
|
211
238
|
description: >
|
|
212
239
|
Append one JSONL row per task to .spur/memory/wrapup-metrics.jsonl deterministically.
|
|
@@ -354,10 +381,46 @@ transitions:
|
|
|
354
381
|
guard:
|
|
355
382
|
kind: always
|
|
356
383
|
|
|
357
|
-
# ──
|
|
384
|
+
# ── doc-sync: contract-first routing (ADR-118 pilot, 0871) ──
|
|
385
|
+
# Declaration order matters: the contract-violation edge is tried first, then
|
|
386
|
+
# success (action-ok), then the executor-failure defense (always) — a failed
|
|
387
|
+
# agent.run reaches these guards because doc-sync's agent.run declares
|
|
388
|
+
# onError: continue.
|
|
358
389
|
- from: doc-sync
|
|
390
|
+
to: repair
|
|
391
|
+
description: >
|
|
392
|
+
Contract violation (ADR-118): the agent exited 0 but missed its declared
|
|
393
|
+
post-condition. Route to the cheap repair edge — never re-dispatch the
|
|
394
|
+
full agent.run on this attempt.
|
|
395
|
+
trigger: contract-violation
|
|
396
|
+
guard:
|
|
397
|
+
kind: contract-violation
|
|
398
|
+
- from: doc-sync
|
|
399
|
+
to: learnings-append
|
|
400
|
+
description: Doc drift repair + learning capture succeeded — append learnings.
|
|
401
|
+
guard:
|
|
402
|
+
kind: action-ok
|
|
403
|
+
- from: doc-sync
|
|
404
|
+
to: failed
|
|
405
|
+
description: >
|
|
406
|
+
Executor failure (non-zero exit, signal, or dispatch error) — keep the
|
|
407
|
+
existing fail semantics; a contract violation never falls through here
|
|
408
|
+
because the contract-violation edge is declared first.
|
|
409
|
+
trigger: executor-failure
|
|
410
|
+
guard:
|
|
411
|
+
kind: always
|
|
412
|
+
|
|
413
|
+
# ── linear body: learnings-append -> metrics-record ──
|
|
414
|
+
- from: learnings-append
|
|
415
|
+
to: metrics-record
|
|
416
|
+
description: Learning capture appended (or soft-skipped) — record metrics.
|
|
417
|
+
guard:
|
|
418
|
+
kind: always
|
|
419
|
+
|
|
420
|
+
# ── repair: cheap path, no re-dispatch -> metrics-record ──
|
|
421
|
+
- from: repair
|
|
359
422
|
to: metrics-record
|
|
360
|
-
description:
|
|
423
|
+
description: Contract-violation repair recorded — proceed to metrics without re-dispatch.
|
|
361
424
|
guard:
|
|
362
425
|
kind: always
|
|
363
426
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/spur",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.86",
|
|
4
4
|
"description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"spur",
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@commander-js/extra-typings": "^14.0.0",
|
|
56
|
-
"@gobing-ai/ts-db": "^0.4.
|
|
57
|
-
"@gobing-ai/ts-ai-runner": "^0.4.
|
|
58
|
-
"@gobing-ai/ts-dual-workflow-engine": "^0.4.
|
|
59
|
-
"@gobing-ai/ts-infra": "^0.4.
|
|
60
|
-
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.
|
|
61
|
-
"@gobing-ai/ts-rule-engine": "^0.4.
|
|
62
|
-
"@gobing-ai/ts-runtime": "^0.4.
|
|
63
|
-
"@gobing-ai/ts-utils": "^0.4.
|
|
56
|
+
"@gobing-ai/ts-db": "^0.4.67",
|
|
57
|
+
"@gobing-ai/ts-ai-runner": "^0.4.67",
|
|
58
|
+
"@gobing-ai/ts-dual-workflow-engine": "^0.4.67",
|
|
59
|
+
"@gobing-ai/ts-infra": "^0.4.67",
|
|
60
|
+
"@gobing-ai/ts-llm-jsonl-importer": "^0.4.67",
|
|
61
|
+
"@gobing-ai/ts-rule-engine": "^0.4.67",
|
|
62
|
+
"@gobing-ai/ts-runtime": "^0.4.67",
|
|
63
|
+
"@gobing-ai/ts-utils": "^0.4.67",
|
|
64
64
|
"@types/bun": "1.3.14",
|
|
65
65
|
"@types/figlet": "^1.7.0",
|
|
66
66
|
"@types/node-notifier": "8.0.5",
|
package/plugins/sp/README.md
CHANGED
|
@@ -620,14 +620,11 @@ pipeline owns one lifecycle phase:
|
|
|
620
620
|
|
|
621
621
|
| Workflow | Phase | Entry command |
|
|
622
622
|
| --------------------------- | --------------------------------- | --------------------------------- |
|
|
623
|
-
| `basic.yaml` | Generic implement/check/fix | direct `spur workflow run` |
|
|
624
623
|
| `feature-lifecycle.yaml` | Feature status FSM | `spur feature update` |
|
|
625
624
|
| `task-lifecycle.yaml` | Task status FSM | `spur task update` |
|
|
626
625
|
| `task-pipeline.yaml` | Single-task execution | `/sp:dev-run` |
|
|
627
626
|
| `idea-pipeline.yaml` | Idea/planning → feature + tasks | `/sp:dev-idea`, `/sp:dev-plan` |
|
|
628
|
-
| `feature-dev.yaml` | Feature umbrella execution | `/sp:dev-runall --feature` |
|
|
629
627
|
| `wrapup-pipeline.yaml` | Post-execution wrap-up | `/sp:dev-wrap`, `/sp:dev-wrapall` |
|
|
630
|
-
| `docs-pipeline.yaml` | Docs-only task execution | `/sp:dev-run --mode implement` |
|
|
631
628
|
| `wayfinder-resolution.yaml` | Wayfinder ticket resolution loop | `spur workflow run` (free-form) |
|
|
632
629
|
|
|
633
630
|
### Lifecycle operations
|
|
@@ -97,7 +97,8 @@ You own the spaces **between** task runs:
|
|
|
97
97
|
You explicitly do **NOT** own step-level execution:
|
|
98
98
|
|
|
99
99
|
- How an `agent.run` step (implement/test/review/verify) runs is `vars.agent`'s concern - default
|
|
100
|
-
`
|
|
100
|
+
`auto` (`agent.default` in config resolves the executor), pinned in `task-pipeline.yaml`.
|
|
101
|
+
`--agent <value>` from the command flows into each
|
|
101
102
|
per-task `vars.agent`; you forward it, you do not interpret it.
|
|
102
103
|
- You never edit the pipeline YAML, never reach into a step, and never decide how a single
|
|
103
104
|
`agent.run` stage executes. The per-task pipeline is invoked **verbatim**.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getEnvVars } from '@gobing-ai/ts-utils';
|
|
1
2
|
/**
|
|
2
3
|
* Shared host-agent identification helpers — the **only** host-agent detection path
|
|
3
4
|
* in the sp plugin hooks.
|
|
@@ -22,11 +23,11 @@
|
|
|
22
23
|
* 3. `TERM_PROGRAM` — terminal identifier (e.g. `claude`, `ghostty`)
|
|
23
24
|
* 4. `SPUR_DEFAULT_AGENT` — configured default agent
|
|
24
25
|
*
|
|
25
|
-
* @param env - environment record (defaults to `
|
|
26
|
+
* @param env - environment record (defaults to `getEnvVars()`)
|
|
26
27
|
* @param fallback - value returned when no candidate matches (defaults to `undefined`)
|
|
27
28
|
*/
|
|
28
29
|
export function resolveAgentHint(
|
|
29
|
-
env: NodeJS.ProcessEnv =
|
|
30
|
+
env: NodeJS.ProcessEnv = getEnvVars(),
|
|
30
31
|
fallback: string | undefined = undefined,
|
|
31
32
|
): string | undefined {
|
|
32
33
|
const candidates = [env.SPUR_AGENT, env.CLAUDE_CODE_ENTRYPOINT, env.TERM_PROGRAM, env.SPUR_DEFAULT_AGENT];
|
|
@@ -45,11 +46,11 @@ export function resolveAgentHint(
|
|
|
45
46
|
* 3. `OPENAI_MODEL` — OpenAI API model
|
|
46
47
|
* 4. `CLAUDE_MODEL` — Claude Code model setting
|
|
47
48
|
*
|
|
48
|
-
* @param env - environment record (defaults to `
|
|
49
|
+
* @param env - environment record (defaults to `getEnvVars()`)
|
|
49
50
|
* @param fallback - value returned when no candidate matches (defaults to `undefined`)
|
|
50
51
|
*/
|
|
51
52
|
export function resolveModelHint(
|
|
52
|
-
env: NodeJS.ProcessEnv =
|
|
53
|
+
env: NodeJS.ProcessEnv = getEnvVars(),
|
|
53
54
|
fallback: string | undefined = undefined,
|
|
54
55
|
): string | undefined {
|
|
55
56
|
const candidates = [env.SPUR_MODEL, env.ANTHROPIC_MODEL, env.OPENAI_MODEL, env.CLAUDE_MODEL];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
|
|
2
3
|
/**
|
|
3
4
|
* careful-guard — PreToolUse guard for destructive shell commands (task 0215, R3).
|
|
4
5
|
*
|
|
@@ -21,6 +22,7 @@
|
|
|
21
22
|
* adapter. This file owns the Claude Code payload/decision shape only.
|
|
22
23
|
*/
|
|
23
24
|
|
|
25
|
+
import { getEnvVar } from '@gobing-ai/ts-utils';
|
|
24
26
|
import { classifyCommand } from './destructive-policy';
|
|
25
27
|
|
|
26
28
|
interface ToolPayload {
|
|
@@ -43,7 +45,7 @@ function preToolUseDecision(decision: Decision, reason?: string): never {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
async function main(): Promise<void> {
|
|
46
|
-
if (
|
|
48
|
+
if (getEnvVar('SPUR_CAREFUL') === 'off') preToolUseDecision('allow');
|
|
47
49
|
|
|
48
50
|
const stdinText = await Bun.stdin.text();
|
|
49
51
|
let payload: ToolPayload;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
import { execSync } from 'node:child_process';
|
|
24
24
|
import { appendFileSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { join, resolve, sep } from 'node:path';
|
|
26
|
+
import { getEnvVar } from '@gobing-ai/ts-utils';
|
|
26
27
|
|
|
27
28
|
/** Tools recorded by this hook (must match hooks.json PostToolUse matcher). */
|
|
28
29
|
export const ALLOWED_TOOLS = new Set(['Bash', 'Grep', 'Glob', 'Read', 'Write', 'Edit']);
|
|
@@ -411,7 +412,7 @@ export function readContextFreshness(contextDir: string): string | null {
|
|
|
411
412
|
// Entrypoint — thin wrapper; logic lives in {@link recordToolUseEvent} for unit coverage.
|
|
412
413
|
if (import.meta.main) {
|
|
413
414
|
void (async () => {
|
|
414
|
-
const dir = join(
|
|
415
|
+
const dir = join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context');
|
|
415
416
|
try {
|
|
416
417
|
const stdinText = await Bun.stdin.text();
|
|
417
418
|
const payload = JSON.parse(stdinText) as ToolPayload;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { execSync } from 'node:child_process';
|
|
16
16
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
17
|
import { join } from 'node:path';
|
|
18
|
+
import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
|
|
18
19
|
import { resolveAgentHint, resolveModelHint } from './agent-hint';
|
|
19
20
|
import { checkContextFreshness } from './context-post-tool';
|
|
20
21
|
|
|
@@ -82,7 +83,7 @@ interface SessionFileBody {
|
|
|
82
83
|
* a stale file is only reachable from inside a live agent run, where reusing it is the correct
|
|
83
84
|
* answer anyway.
|
|
84
85
|
*/
|
|
85
|
-
export function resolveActiveSession(dir: string, now: Date, env: NodeJS.ProcessEnv =
|
|
86
|
+
export function resolveActiveSession(dir: string, now: Date, env: NodeJS.ProcessEnv = getEnvVars()): string | null {
|
|
86
87
|
let raw: string;
|
|
87
88
|
try {
|
|
88
89
|
raw = readFileSync(join(dir, '.session.json'), 'utf-8');
|
|
@@ -123,7 +124,7 @@ export function resolveActiveSession(dir: string, now: Date, env: NodeJS.Process
|
|
|
123
124
|
*/
|
|
124
125
|
export function recordSessionStart(
|
|
125
126
|
dir: string,
|
|
126
|
-
env: NodeJS.ProcessEnv =
|
|
127
|
+
env: NodeJS.ProcessEnv = getEnvVars(),
|
|
127
128
|
now: () => Date = () => new Date(),
|
|
128
129
|
): string | null {
|
|
129
130
|
try {
|
|
@@ -195,7 +196,7 @@ export function recordSessionStart(
|
|
|
195
196
|
// Entrypoint — kept minimal so unit coverage focuses on pure helpers above.
|
|
196
197
|
if (import.meta.main) {
|
|
197
198
|
try {
|
|
198
|
-
recordSessionStart(join(
|
|
199
|
+
recordSessionStart(join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context'));
|
|
199
200
|
} catch {
|
|
200
201
|
/* fail-open */
|
|
201
202
|
}
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import { appendFileSync, existsSync, readFileSync, rmSync } from 'node:fs';
|
|
19
19
|
import { join } from 'node:path';
|
|
20
|
+
import { getEnvVar } from '@gobing-ai/ts-utils';
|
|
20
21
|
|
|
21
22
|
interface LedgerEvent {
|
|
22
23
|
session: string;
|
|
@@ -53,7 +54,7 @@ function exitOk(): never {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
async function main(): Promise<void> {
|
|
56
|
-
const dir = join(
|
|
57
|
+
const dir = join(getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd(), '.spur', 'context');
|
|
57
58
|
|
|
58
59
|
const sessionFile = join(dir, '.session.json');
|
|
59
60
|
if (!existsSync(sessionFile)) exitOk();
|
|
@@ -23,6 +23,7 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSy
|
|
|
23
23
|
import { homedir } from 'node:os';
|
|
24
24
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
25
25
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
26
|
+
import { getEnvVar, getEnvVars } from '@gobing-ai/ts-utils';
|
|
26
27
|
import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
|
|
27
28
|
import { classifyCommand } from '../destructive-policy';
|
|
28
29
|
import { couldBeTaskFile } from '../task-file-policy';
|
|
@@ -81,7 +82,7 @@ function resolveSpurTaskOwnership(filePath: string): TaskOwnership {
|
|
|
81
82
|
spawnSync(cmd, args, { cwd: process.cwd(), encoding: 'utf-8', timeout: 8000 });
|
|
82
83
|
|
|
83
84
|
// 1. SPUR_BIN env override (may include args) or `spur` on PATH
|
|
84
|
-
const envBin =
|
|
85
|
+
const envBin = getEnvVar('SPUR_BIN') || 'spur';
|
|
85
86
|
const envParts = envBin.split(' ');
|
|
86
87
|
let res = run(envParts[0] ?? 'spur', [...envParts.slice(1), 'task', 'resolve', filePath, '--strict', '--json']);
|
|
87
88
|
// Only 0 (owned) / 1 (unowned) are valid spur exit codes; 127 (interpreter
|
|
@@ -203,8 +204,8 @@ function initSession(): void {
|
|
|
203
204
|
const sessionId = generateSessionId();
|
|
204
205
|
const session = {
|
|
205
206
|
session_id: sessionId,
|
|
206
|
-
agent: resolveAgentHintShared(
|
|
207
|
-
model: resolveModelHintShared(
|
|
207
|
+
agent: resolveAgentHintShared(getEnvVars(), 'pi'),
|
|
208
|
+
model: resolveModelHintShared(getEnvVars()),
|
|
208
209
|
started_at: new Date().toISOString(),
|
|
209
210
|
};
|
|
210
211
|
writeFileSync(sessionFilePath(), `${JSON.stringify(session, null, 2)}\n`);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { spawnSync } from 'node:child_process';
|
|
23
|
+
import { getEnvVar } from '@gobing-ai/ts-utils';
|
|
23
24
|
import { couldBeTaskFile } from './task-file-policy';
|
|
24
25
|
|
|
25
26
|
interface ToolPayload {
|
|
@@ -40,7 +41,7 @@ function preToolUseDecision(decision: 'allow' | 'deny', reason?: string): never
|
|
|
40
41
|
|
|
41
42
|
/** Resolve whether a path is owned by a Spur task via `spur task resolve --strict --json`. */
|
|
42
43
|
function resolveSpurTaskOwnership(filePath: string, cwd: string): TaskOwnership {
|
|
43
|
-
const spurBin =
|
|
44
|
+
const spurBin = getEnvVar('SPUR_BIN') || 'spur';
|
|
44
45
|
const parts = spurBin.split(' ');
|
|
45
46
|
const cmd = parts[0] ?? 'spur';
|
|
46
47
|
const args = [...parts.slice(1), 'task', 'resolve', filePath, '--strict', '--json'];
|
|
@@ -54,7 +55,7 @@ function resolveSpurTaskOwnership(filePath: string, cwd: string): TaskOwnership
|
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
async function main(): Promise<void> {
|
|
57
|
-
if (
|
|
58
|
+
if (getEnvVar('SPUR_WRITE_GUARD') === 'off') preToolUseDecision('allow');
|
|
58
59
|
|
|
59
60
|
const stdinText = await Bun.stdin.text();
|
|
60
61
|
let payload: ToolPayload;
|
|
@@ -73,7 +74,7 @@ async function main(): Promise<void> {
|
|
|
73
74
|
// cannot name a task file — that is every ordinary source edit.
|
|
74
75
|
if (!couldBeTaskFile(filePath)) preToolUseDecision('allow');
|
|
75
76
|
|
|
76
|
-
const ownership = resolveSpurTaskOwnership(filePath,
|
|
77
|
+
const ownership = resolveSpurTaskOwnership(filePath, getEnvVar('CLAUDE_PROJECT_DIR') ?? process.cwd());
|
|
77
78
|
if (ownership === 'owned') {
|
|
78
79
|
preToolUseDecision(
|
|
79
80
|
'deny',
|