@gobing-ai/spur 0.3.94 → 0.3.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +42 -0
  3. package/board/index.d.ts +38 -0
  4. package/config/plugin-scripts.json +3 -52
  5. package/config/rules/boundary/sp-script-placement.yaml +19 -0
  6. package/config/rules/strict/runtime-boundaries.yaml +5 -0
  7. package/config/rules/structure/test-location.yaml +2 -0
  8. package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
  9. package/config/rules/typescript/output-boundaries.yaml +15 -2
  10. package/config/script-placement-baseline.json +51 -0
  11. package/config/workflows/feature-verification.yaml +7 -4
  12. package/config/workflows/history-anatomy.yaml +23 -11
  13. package/config/workflows/idea-pipeline.yaml +87 -86
  14. package/config/workflows/pr-review.yaml +18 -9
  15. package/config/workflows/task-pipeline.yaml +84 -109
  16. package/config/workflows/wrapup-pipeline.yaml +173 -46
  17. package/package.json +15 -11
  18. package/plugins/sp/README.md +8 -10
  19. package/plugins/sp/agents/super-reviewer.md +43 -18
  20. package/plugins/sp/commands/dev-fixgha.md +83 -0
  21. package/plugins/sp/commands/dev-gitmsg.md +8 -6
  22. package/plugins/sp/commands/dev-gtd.md +2 -2
  23. package/plugins/sp/commands/dev-idea.md +22 -12
  24. package/plugins/sp/commands/dev-plan.md +8 -9
  25. package/plugins/sp/commands/dev-review.md +22 -13
  26. package/plugins/sp/commands/dev-verify.md +7 -4
  27. package/plugins/sp/commands/dev-verifyall.md +5 -4
  28. package/plugins/sp/commands/dev-wrap.md +1 -1
  29. package/plugins/sp/commands/spur-init.md +2 -2
  30. package/plugins/sp/hooks/context-post-tool.ts +2 -2
  31. package/plugins/sp/hooks/context-session-start.ts +2 -1
  32. package/plugins/sp/hooks/context-session-stop.ts +22 -14
  33. package/plugins/sp/hooks/pi/guard-extension.ts +109 -150
  34. package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
  35. package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
  36. package/plugins/sp/lib/idea-handoff.generated.mjs +6 -5
  37. package/plugins/sp/lib/inline-run.generated.d.mts +13 -2
  38. package/plugins/sp/lib/inline-run.generated.mjs +33 -16
  39. package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
  40. package/plugins/sp/lib/quality-gate.generated.mjs +438 -0
  41. package/plugins/sp/lib/residual-scan.generated.d.mts +62 -0
  42. package/plugins/sp/lib/residual-scan.generated.mjs +210 -0
  43. package/plugins/sp/lib/spur-bin.ts +36 -0
  44. package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
  45. package/plugins/sp/lib/step-profile.generated.mjs +174 -0
  46. package/plugins/sp/plugin.json +1 -1
  47. package/plugins/sp/references/roles.md +1 -1
  48. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +3 -3
  49. package/plugins/sp/scripts/daily-summary/daily-summary.ts +3 -3
  50. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -0
  51. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +1 -0
  52. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +9 -7
  53. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +8 -7
  54. package/plugins/sp/scripts/feature-verification-steps.ts +6 -6
  55. package/plugins/sp/scripts/history-anatomy-cache.mjs +23 -22
  56. package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
  57. package/plugins/sp/scripts/inline-run-setup.mjs +90 -315
  58. package/plugins/sp/scripts/inline-run-setup.ts +109 -639
  59. package/plugins/sp/scripts/pr-reviewing.mjs +2 -0
  60. package/plugins/sp/scripts/pr-reviewing.ts +1 -0
  61. package/plugins/sp/scripts/quality-gate.mjs +38 -21
  62. package/plugins/sp/scripts/quality-gate.ts +20 -660
  63. package/plugins/sp/scripts/residual-scan.mjs +138 -159
  64. package/plugins/sp/scripts/residual-scan.ts +102 -499
  65. package/plugins/sp/scripts/script-root.mjs +129 -0
  66. package/plugins/sp/scripts/script-root.ts +199 -0
  67. package/plugins/sp/scripts/workflow-step-profile.mjs +27 -16
  68. package/plugins/sp/scripts/workflow-step-profile.ts +22 -314
  69. package/plugins/sp/scripts/wrapup-drift-probe.mjs +12 -3
  70. package/plugins/sp/scripts/wrapup-drift-probe.ts +6 -3
  71. package/plugins/sp/scripts/wrapup-steps.mjs +42 -40
  72. package/plugins/sp/scripts/wrapup-steps.ts +64 -46
  73. package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
  74. package/plugins/sp/skills/code-verification/SKILL.md +46 -16
  75. package/plugins/sp/skills/code-verification/references/verdict-schema.md +13 -2
  76. package/plugins/sp/skills/functional-review/SKILL.md +8 -5
  77. package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
  78. package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
  79. package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
  80. package/plugins/sp/skills/next-router/references/routing-table.md +2 -2
  81. package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
  82. package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
  83. package/plugins/sp/skills/spur-cli/references/features.md +17 -6
  84. package/plugins/sp/skills/spur-cli/references/init.md +17 -16
  85. package/plugins/sp/skills/spur-cli/references/self.md +3 -2
  86. package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
  87. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +28 -5
  88. package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
  89. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
  90. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +11 -9
  91. package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
  92. package/plugins/sp/skills/spur-dev/references/dev-operations.md +86 -70
  93. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -3
  94. package/plugins/sp/skills/spur-dev/references/execution-batch.md +107 -44
  95. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +5 -9
  96. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
  97. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +31 -21
  98. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
  99. package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
  100. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +35 -6
  101. package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
  102. package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
  103. package/plugins/sp/tsconfig.json +8 -0
  104. package/spur.js +6638 -4775
  105. package/web/_astro/BoardApp.BIjMatT1.js +1 -0
  106. package/web/_astro/{BoardApp.FTEs3-N8.js → BoardApp.GvjIe9Z6.js} +150 -153
  107. package/web/_astro/TaskDetail.BZM3EAFx.js +1 -0
  108. package/web/_astro/_commonjsHelpers.CqkleIqs.js +1 -0
  109. package/web/_astro/{arc.uuAf51IT.js → arc.HkRiZnoI.js} +1 -1
  110. package/web/_astro/architectureDiagram-3BPJPVTR.BDdK-tgZ.js +36 -0
  111. package/web/_astro/{blockDiagram-GPEHLZMM.D1mGCq3p.js → blockDiagram-GPEHLZMM.CZ9kOBxx.js} +1 -1
  112. package/web/_astro/board-facade-react-dom-client.B2U5wMvk.js +1 -0
  113. package/web/_astro/board-facade-react-dom.Cd2MuMhF.js +1 -0
  114. package/web/_astro/board-facade-react-jsx-dev-runtime.B_L3JzfC.js +1 -0
  115. package/web/_astro/board-facade-react-jsx-runtime.BY2yfHRi.js +1 -0
  116. package/web/_astro/board-facade-react-router-dom.Dy5pjzOw.js +1 -0
  117. package/web/_astro/board-facade-react-router.DjjqU27_.js +1 -0
  118. package/web/_astro/board-facade-react.Dgf4GRyj.js +1 -0
  119. package/web/_astro/{c4Diagram-AAUBKEIU.CMsolcde.js → c4Diagram-AAUBKEIU.DXK9qeRD.js} +1 -1
  120. package/web/_astro/channel.DlhX2MEt.js +1 -0
  121. package/web/_astro/{chunk-2J33WTMH.CrGA3fik.js → chunk-2J33WTMH.BTD2WeX5.js} +1 -1
  122. package/web/_astro/{chunk-4BX2VUAB.DsLVVla0.js → chunk-4BX2VUAB.Ci6Qbcvk.js} +1 -1
  123. package/web/_astro/{chunk-55IACEB6.Dxc59Tfi.js → chunk-55IACEB6.yl3zsj7p.js} +1 -1
  124. package/web/_astro/{chunk-727SXJPM.CaEVE2Wy.js → chunk-727SXJPM.ClXZsfyR.js} +1 -1
  125. package/web/_astro/{chunk-AQP2D5EJ.BiJ4HXeI.js → chunk-AQP2D5EJ.BmKWZBcP.js} +1 -1
  126. package/web/_astro/{chunk-FMBD7UC4.Mxf1fru5.js → chunk-FMBD7UC4.By30fcb7.js} +1 -1
  127. package/web/_astro/chunk-JMJ3UQ3L.BQJEJHu4.js +34 -0
  128. package/web/_astro/{chunk-ND2GUHAM.pyOWQixH.js → chunk-ND2GUHAM.24NmrD-k.js} +1 -1
  129. package/web/_astro/{chunk-QZHKN3VN.BmEsg4vr.js → chunk-QZHKN3VN.BN4sKdcS.js} +1 -1
  130. package/web/_astro/chunk-YNUBSHFH.CxeAygfi.js +7 -0
  131. package/web/_astro/{classDiagram-4FO5ZUOK.BHhhMFTO.js → classDiagram-4FO5ZUOK.CJpoMPb5.js} +1 -1
  132. package/web/_astro/{classDiagram-v2-Q7XG4LA2.BHhhMFTO.js → classDiagram-v2-Q7XG4LA2.CJpoMPb5.js} +1 -1
  133. package/web/_astro/client.BhluLQqe.js +1 -0
  134. package/web/_astro/client.DBpSRLnx.js +9 -0
  135. package/web/_astro/cose-bilkent-S5V4N54A.BraxQ2Nt.js +1 -0
  136. package/web/_astro/{cynefin-OW5HDTMX.C2j1_lKL.js → cynefin-OW5HDTMX.gzoU73oL.js} +1 -1
  137. package/web/_astro/{dagre-BM42HDAG.hZ2NCTdT.js → dagre-BM42HDAG.wyDfWySU.js} +1 -1
  138. package/web/_astro/{diagram-2AECGRRQ.Bfw5_EzK.js → diagram-2AECGRRQ.h2_EbMxV.js} +1 -1
  139. package/web/_astro/{diagram-5GNKFQAL.Bt1V_Tmk.js → diagram-5GNKFQAL.Db6dcs89.js} +1 -1
  140. package/web/_astro/{diagram-KO2AKTUF.DEk-YFwp.js → diagram-KO2AKTUF.NVwzxu_U.js} +1 -1
  141. package/web/_astro/{diagram-LMA3HP47.CDjndtQm.js → diagram-LMA3HP47.B-eNgDQk.js} +1 -1
  142. package/web/_astro/{diagram-OG6HWLK6.cFnUHScG.js → diagram-OG6HWLK6.Bsa8ZRbJ.js} +1 -1
  143. package/web/_astro/{erDiagram-TEJ5UH35.DlhYp7NV.js → erDiagram-TEJ5UH35.BospRE5Q.js} +1 -1
  144. package/web/_astro/{flowDiagram-I6XJVG4X.DNTpsxfx.js → flowDiagram-I6XJVG4X.DMzvHwAA.js} +1 -1
  145. package/web/_astro/ganttDiagram-6RSMTGT7.Dmdxgd-M.js +292 -0
  146. package/web/_astro/{gitGraphDiagram-PVQCEYII.DkEqNI0P.js → gitGraphDiagram-PVQCEYII.CaqdO0rL.js} +1 -1
  147. package/web/_astro/index.5dn-Zm-2.js +1 -0
  148. package/web/_astro/index.C-IqPpW2.js +1 -0
  149. package/web/_astro/index.Cyc_GA4f.js +1 -0
  150. package/web/_astro/index.EoZlzLK-.css +1 -0
  151. package/web/_astro/index.tZfqwSut.js +1 -0
  152. package/web/_astro/{infoDiagram-5YYISTIA.CrjioCTG.js → infoDiagram-5YYISTIA.DDVD9Wn7.js} +1 -1
  153. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BcK0CN8n.js → ishikawaDiagram-YF4QCWOH.EKyCUFsL.js} +1 -1
  154. package/web/_astro/{journeyDiagram-JHISSGLW.p0CbBDX1.js → journeyDiagram-JHISSGLW.By2-RkYB.js} +1 -1
  155. package/web/_astro/jsx-runtime.DWOiEOII.js +1 -0
  156. package/web/_astro/{kanban-definition-UN3LZRKU.puHIFt6J.js → kanban-definition-UN3LZRKU.ar-WmlyR.js} +1 -1
  157. package/web/_astro/{linear.Bb-3a1d7.js → linear.BnzMBgo_.js} +1 -1
  158. package/web/_astro/{mermaid.core.CJDgXOJs.js → mermaid.core.DSqeFu2Y.js} +4 -4
  159. package/web/_astro/{mindmap-definition-RKZ34NQL.BoAZEI9t.js → mindmap-definition-RKZ34NQL.1XcF8Wh2.js} +1 -1
  160. package/web/_astro/{pieDiagram-4H26LBE5.CJNSdqY7.js → pieDiagram-4H26LBE5.DnItHOdK.js} +1 -1
  161. package/web/_astro/{quadrantDiagram-W4KKPZXB.DtTy7_0Z.js → quadrantDiagram-W4KKPZXB.BQQyrnBt.js} +1 -1
  162. package/web/_astro/{requirementDiagram-4Y6WPE33.BNYV98Xg.js → requirementDiagram-4Y6WPE33.Dmzxun82.js} +1 -1
  163. package/web/_astro/{sankeyDiagram-5OEKKPKP.DRq9DGHp.js → sankeyDiagram-5OEKKPKP.BBIeRKYB.js} +1 -1
  164. package/web/_astro/{sequenceDiagram-3UESZ5HK.B7KdBFCm.js → sequenceDiagram-3UESZ5HK.CUNVdBBf.js} +1 -1
  165. package/web/_astro/{stateDiagram-AJRCARHV.CD62ZJ2H.js → stateDiagram-AJRCARHV.DlFf2sJm.js} +1 -1
  166. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CeVk1TAj.js → stateDiagram-v2-BHNVJYJU.DXR7I1H5.js} +1 -1
  167. package/web/_astro/{timeline-definition-PNZ67QCA.C_ZPA5tT.js → timeline-definition-PNZ67QCA.BcO-GyX2.js} +1 -1
  168. package/web/_astro/{vennDiagram-CIIHVFJN.CrThO_FQ.js → vennDiagram-CIIHVFJN.ChCECmmW.js} +1 -1
  169. package/web/_astro/{wardleyDiagram-YWT4CUSO.DSZCA5nl.js → wardleyDiagram-YWT4CUSO.CRaT12mD.js} +1 -1
  170. package/web/_astro/{xychartDiagram-2RQKCTM6.KI7baTuk.js → xychartDiagram-2RQKCTM6.B3lsHVpj.js} +1 -1
  171. package/web/board-runtime.json +69 -0
  172. package/web/index.html +2 -2
  173. package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
  174. package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
  175. package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -298
  176. package/plugins/sp/scripts/feature-sync-bounded.ts +0 -479
  177. package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
  178. package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
  179. package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
  180. package/plugins/sp/scripts/record-feature-sync.ts +0 -84
  181. package/plugins/sp/scripts/script-contract-check.ts +0 -377
  182. package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
  183. package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
  184. package/plugins/sp/scripts/task-evidence-precheck.ts +0 -187
  185. package/plugins/sp/scripts/task-size-precheck.ts +0 -210
  186. package/plugins/sp/scripts/transition-shim-check.ts +0 -238
  187. package/plugins/sp/scripts/validate-commands.ts +0 -689
  188. package/plugins/sp/scripts/validate-flag-contracts.ts +0 -878
  189. package/plugins/sp/scripts/verify-answer-lint.ts +0 -547
  190. package/web/_astro/BoardApp.C02hAHPO.js +0 -1
  191. package/web/_astro/TaskDetail.C-GdsS-t.js +0 -1
  192. package/web/_astro/architectureDiagram-3BPJPVTR.CGe629A1.js +0 -36
  193. package/web/_astro/channel.fsgl7o5j.js +0 -1
  194. package/web/_astro/client.yhYJvxCU.js +0 -9
  195. package/web/_astro/cose-bilkent-S5V4N54A.2fH4YOlp.js +0 -1
  196. package/web/_astro/ganttDiagram-6RSMTGT7.DC_p36PI.js +0 -292
  197. package/web/_astro/index.De90oHcH.js +0 -1
  198. package/web/_astro/index.Hjbr15fG.css +0 -1
@@ -7,9 +7,12 @@
7
7
  * Implements:
8
8
  * - task-write-guard (tool_call → block Write/Edit to Spur task files)
9
9
  * - careful-guard (tool_call → warn on destructive Bash commands)
10
- * - context-post-tool (tool_result → append to token-ledger.jsonl)
11
- * - context-session-start (session_start → init .session.json)
12
- * - context-session-stop (session_shutdown → rollup + cleanup)
10
+ * - context-post-tool (tool_result → shared recordToolUseEvent)
11
+ * - context-session-start (session_start → shared recordSessionStart)
12
+ * - context-session-stop (session_shutdown → shared recordSessionEnd)
13
+ *
14
+ * The ledger and session cores are shared with the Claude hooks (task 0969): this
15
+ * extension only normalizes Pi event shapes and adapts them to those cores.
13
16
  *
14
17
  * Installation:
15
18
  * Add to ~/.pi/agent/settings.json packages:
@@ -19,13 +22,14 @@
19
22
  */
20
23
 
21
24
  import { spawnSync } from 'node:child_process';
22
- import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
25
+ import { existsSync, realpathSync } from 'node:fs';
23
26
  import { homedir } from 'node:os';
24
27
  import { isAbsolute, join, resolve } from 'node:path';
25
28
  import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
26
29
  import { getEnvVar, getEnvVars } from '../../lib/env';
27
- import { resolveAgentHint as resolveAgentHintShared, resolveModelHint as resolveModelHintShared } from '../agent-hint';
28
- import { cappedByteLength, truncateSummary } from '../context-post-tool';
30
+ import { recordToolUseEvent, type ToolPayload } from '../context-post-tool';
31
+ import { recordSessionStart } from '../context-session-start';
32
+ import { recordSessionEnd } from '../context-session-stop';
29
33
  import { classifyCommand } from '../destructive-policy';
30
34
  import { couldBeTaskFile } from '../task-file-policy';
31
35
 
@@ -35,8 +39,6 @@ import { couldBeTaskFile } from '../task-file-policy';
35
39
  // process lifetime, and lazy resolution keeps the extension testable (tests
36
40
  // chdir into a temp project before driving handlers).
37
41
  const spurContextDir = (): string => join(process.cwd(), '.spur', 'context');
38
- const sessionFilePath = (): string => join(spurContextDir(), '.session.json');
39
- const ledgerFilePath = (): string => join(spurContextDir(), 'token-ledger.jsonl');
40
42
 
41
43
  // ─── Helpers ─────────────────────────────────────────────────────────────
42
44
 
@@ -113,137 +115,86 @@ function resolveSpurTaskOwnership(filePath: string): TaskOwnership {
113
115
  // `git push origin +main`, and warned on `git push --force-with-lease`). Do not
114
116
  // re-introduce a local copy — add cases to `destructive-policy.test.ts` instead.
115
117
 
116
- // ─── Token ledger helpers (mirrors context-post-tool.ts) ──────────────────
117
-
118
- interface ToolEvent {
119
- session_id?: string;
120
- tool_name?: string;
121
- tool_input?: Record<string, unknown>;
122
- }
123
-
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`.
126
- function summarizeToolEvent(event: ToolEvent): string {
127
- const input = event.tool_input ?? {};
128
- const candidates = [input.file_path, input.command, input.pattern, input.glob_pattern, input.glob, input.path];
129
- for (const c of candidates) {
130
- if (typeof c === 'string' && c.trim()) return truncateSummary(c);
131
- }
132
- return `(${event.tool_name ?? 'unknown'})`;
133
- }
134
-
135
- function appendToLedger(event: ToolEvent, command: string | undefined): void {
136
- try {
137
- if (!existsSync(spurContextDir())) return;
138
- const sessionId = readSessionId();
139
- if (!sessionId) return;
140
-
141
- const summary = summarizeToolEvent(event);
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;
146
-
147
- const ledgerEntry = JSON.stringify({
148
- session: sessionId,
149
- type: event.tool_name?.toLowerCase() === 'read' ? 'read' : 'write',
150
- tool: event.tool_name,
151
- file: typeof path === 'string' ? path : undefined,
152
- summary,
153
- tokens,
154
- ts: new Date().toISOString(),
155
- });
156
-
157
- appendFileSync(ledgerFilePath(), `${ledgerEntry}\n`);
158
- } catch {
159
- // fail-open: skip ledger writes on error
160
- }
161
- }
162
-
163
- function readSessionId(): string | undefined {
164
- try {
165
- if (!existsSync(sessionFilePath())) return undefined;
166
- const data = JSON.parse(readFileSync(sessionFilePath(), 'utf-8')) as { session_id?: string };
167
- return data.session_id;
168
- } catch {
169
- return undefined;
170
- }
171
- }
172
-
173
- // ─── Session helpers (agent/model hints from agent-hint.ts; mirrors context-session-stop.ts) ─
174
-
175
- function generateSessionId(): string {
176
- const timestamp = Date.now().toString(36);
177
- const random = Math.random().toString(36).slice(2, 10);
178
- return `${timestamp}-${random}`;
179
- }
180
-
181
- function initSession(): void {
182
- try {
183
- mkdirSync(spurContextDir(), { recursive: true });
184
- const sessionId = generateSessionId();
185
- const session = {
186
- session_id: sessionId,
187
- agent: resolveAgentHintShared(getEnvVars(), 'pi'),
188
- model: resolveModelHintShared(getEnvVars()),
189
- started_at: new Date().toISOString(),
190
- };
191
- writeFileSync(sessionFilePath(), `${JSON.stringify(session, null, 2)}\n`);
192
-
193
- // Append session_start event to ledger
194
- const startEntry = JSON.stringify({
195
- session: sessionId,
196
- type: 'session_start',
197
- agent: session.agent,
198
- model: session.model,
199
- ts: session.started_at,
200
- });
201
- appendFileSync(ledgerFilePath(), `${startEntry}\n`);
202
- } catch {
203
- // fail-open
118
+ // ─── Pi → Claude payload normalization (task 0969 R1) ─────────────────────
119
+
120
+ /** Pi tool name → the Claude tool name the shared core records. */
121
+ const PI_TO_CLAUDE: Record<string, string> = {
122
+ read: 'Read',
123
+ write: 'Write',
124
+ edit: 'Edit',
125
+ bash: 'Bash',
126
+ grep: 'Grep',
127
+ find: 'Glob',
128
+ };
129
+
130
+ /** Join the text parts of a Pi tool_result `content` payload. */
131
+ function piContentText(content: unknown): string {
132
+ if (typeof content === 'string') return content;
133
+ if (!Array.isArray(content)) return '';
134
+ const parts: string[] = [];
135
+ for (const part of content) {
136
+ if (part && typeof part === 'object' && typeof (part as { text?: unknown }).text === 'string') {
137
+ parts.push((part as { text: string }).text);
138
+ }
204
139
  }
140
+ return parts.join('\n');
205
141
  }
206
142
 
207
- function cleanupSession(): void {
208
- try {
209
- if (!existsSync(sessionFilePath())) return;
210
- const session = JSON.parse(readFileSync(sessionFilePath(), 'utf-8')) as { session_id?: string };
211
- const sessionId = session.session_id;
212
-
213
- // Compute rollup totals from ledger
214
- let reads = 0;
215
- let writes = 0;
216
- let tokens = 0;
217
- if (sessionId && existsSync(ledgerFilePath())) {
218
- for (const line of readFileSync(ledgerFilePath(), 'utf-8').split('\n')) {
219
- if (!line.trim()) continue;
220
- try {
221
- const evt = JSON.parse(line) as { session?: string; type?: string; tokens?: number };
222
- if (evt.session !== sessionId) continue;
223
- if (evt.type === 'read') reads++;
224
- else if (evt.type === 'write') writes++;
225
- if (evt.tokens) tokens += evt.tokens;
226
- } catch {
227
- // skip unparseable lines
228
- }
229
- }
143
+ /**
144
+ * Pure Pi-event → Claude-{@link ToolPayload} adapter. An unmapped tool returns null so the
145
+ * shared core writes no row (R1's row filter). Search tools keep `path` (never `file_path`)
146
+ * because the core treats `file_path` as the written file, not a search root.
147
+ */
148
+ export function normalizePiToolEvent(
149
+ toolName: string,
150
+ input: Record<string, unknown> | undefined,
151
+ content?: unknown,
152
+ ): ToolPayload | null {
153
+ const mapped = PI_TO_CLAUDE[toolName];
154
+ if (mapped === undefined) return null;
155
+ const i = input ?? {};
156
+ const str = (value: unknown): string => (typeof value === 'string' ? value : '');
157
+ const path = str(i.path) || str(i.file_path);
158
+ const text = piContentText(content);
159
+ const tool_response = text.length > 0 ? { content: text } : undefined;
160
+
161
+ switch (mapped) {
162
+ case 'Read':
163
+ return { tool_name: mapped, tool_input: { file_path: path }, tool_response };
164
+ case 'Write':
165
+ return { tool_name: mapped, tool_input: { file_path: path, content: str(i.content) }, tool_response };
166
+ case 'Edit': {
167
+ const edits = Array.isArray(i.edits) ? (i.edits as Array<Record<string, unknown>>) : [];
168
+ return {
169
+ tool_name: mapped,
170
+ tool_input: {
171
+ file_path: path,
172
+ old_string: edits.map((e) => str(e.oldText)).join(''),
173
+ new_string: edits.map((e) => str(e.newText)).join(''),
174
+ },
175
+ tool_response,
176
+ };
230
177
  }
231
-
232
- // Append session_end event
233
- const endEntry = JSON.stringify({
234
- session: sessionId,
235
- type: 'session_end',
236
- reads,
237
- writes,
238
- tokens,
239
- ts: new Date().toISOString(),
240
- });
241
- appendFileSync(ledgerFilePath(), `${endEntry}\n`);
242
-
243
- // Cleanup session file
244
- rmSync(sessionFilePath(), { force: true });
245
- } catch {
246
- // fail-open
178
+ case 'Grep':
179
+ return {
180
+ tool_name: mapped,
181
+ tool_input: {
182
+ pattern: str(i.pattern),
183
+ ...(path ? { path } : {}),
184
+ ...(str(i.glob) ? { glob: str(i.glob) } : {}),
185
+ },
186
+ tool_response,
187
+ };
188
+ case 'Glob':
189
+ return {
190
+ tool_name: mapped,
191
+ tool_input: { pattern: str(i.pattern), ...(path ? { path } : {}) },
192
+ tool_response,
193
+ };
194
+ case 'Bash':
195
+ return { tool_name: mapped, tool_input: { command: str(i.command) }, tool_response };
196
+ default:
197
+ return null;
247
198
  }
248
199
  }
249
200
 
@@ -284,27 +235,35 @@ export default function (pi: ExtensionAPI): void {
284
235
  return {};
285
236
  });
286
237
 
287
- // ── context-post-tool: append to token-ledger.jsonl ───────────────
238
+ // ── context-post-tool: shared recordToolUseEvent (task 0969 R1) ──
288
239
  pi.on('tool_result', async (event) => {
289
- const input = event.input as Record<string, unknown> | undefined;
290
- const command = typeof input?.command === 'string' ? input.command : undefined;
291
- appendToLedger(
292
- {
293
- session_id: readSessionId(),
294
- tool_name: event.toolName,
295
- tool_input: input as Record<string, unknown>,
296
- },
297
- command,
298
- );
240
+ try {
241
+ const payload = normalizePiToolEvent(
242
+ event.toolName,
243
+ event.input as Record<string, unknown> | undefined,
244
+ (event as { content?: unknown }).content,
245
+ );
246
+ if (payload) recordToolUseEvent(spurContextDir(), payload);
247
+ } catch {
248
+ // fail-open
249
+ }
299
250
  });
300
251
 
301
- // ── context-session-start: init session tracking ─────────────────
252
+ // ── context-session-start: shared recordSessionStart (R2) ────────
302
253
  pi.on('session_start', async () => {
303
- initSession();
254
+ try {
255
+ recordSessionStart(spurContextDir(), getEnvVars(), undefined, 'pi');
256
+ } catch {
257
+ // fail-open
258
+ }
304
259
  });
305
260
 
306
- // ── context-session-stop: rollup + cleanup ───────────────────────
261
+ // ── context-session-stop: shared recordSessionEnd (R3) ──────────
307
262
  pi.on('session_shutdown', async () => {
308
- cleanupSession();
263
+ try {
264
+ recordSessionEnd(spurContextDir());
265
+ } catch {
266
+ // fail-open
267
+ }
309
268
  });
310
269
  }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * GENERATED by scripts/commands/bundle-plugin-lib.ts — do not edit.
3
+ * Source of truth: packages/app/src/services/history-anatomy.ts (task 1005 R2).
4
+ */
5
+ export declare interface CacheIdentity {
6
+ contractVersion: string;
7
+ mode: 'daily' | 'ad-hoc';
8
+ date: string;
9
+ timezone: string;
10
+ bounds: { since: string; until: string };
11
+ sources: string[];
12
+ }
13
+ export declare interface CacheProvenance {
14
+ identity: CacheIdentity;
15
+ windowState: 'provisional' | 'closed';
16
+ generatedAt: string;
17
+ validatedAt: string;
18
+ artifactDigest: string;
19
+ baselineArtifactDigest: string | null;
20
+ contractDigest: string;
21
+ skillDigest: string;
22
+ workflowDigest: string;
23
+ helperDigest: string;
24
+ coverage: Array<{ source: string; status: string; lastImportedAt: string | null }>;
25
+ runId?: string;
26
+ currentArtifactPath?: string;
27
+ baselineArtifactPath?: string | null;
28
+ spurVersion?: string;
29
+ schemaVersion?: number;
30
+ executor?: string;
31
+ model?: string;
32
+ cacheDisposition?: CacheDisposition;
33
+ }
34
+ export declare type CacheDisposition = 'hit' | 'miss' | 'forced-recompute';
35
+ export declare interface CacheDecision {
36
+ disposition: CacheDisposition;
37
+ reasons: string[];
38
+ }
39
+ export declare const REPORT_SECTIONS: string[];
40
+ export declare const FINDING_FIELDS: string[];
41
+ export declare function semanticArtifactDigest(artifactJson: unknown): string;
42
+ export declare function parseProvenance(reportMarkdown: string): CacheProvenance | null;
43
+ export declare function decideCache(
44
+ cached: CacheProvenance | null,
45
+ current: CacheProvenance,
46
+ opts: { recompute: boolean; dayClosed: boolean },
47
+ ): CacheDecision;
48
+ export declare function checkReportStructure(reportMarkdown: string): { ok: boolean; problems: string[] };
49
+ export declare function logicDigest(path: string | undefined): string;
50
+ export declare function importedSnapshotAsOf(coverage: CacheProvenance['coverage']): string;
51
+ export declare function resolvePaths(opts: {
52
+ helper: string;
53
+ reportDir: string;
54
+ date?: string;
55
+ output?: string;
56
+ now?: Date;
57
+ tz?: string;
58
+ mode?: string;
59
+ since?: string;
60
+ until?: string;
61
+ }): string;
62
+ export declare type SelectorValidation =
63
+ | {
64
+ ok: true;
65
+ mode: 'daily' | 'ad-hoc';
66
+ date: string | null;
67
+ focus: string | null;
68
+ since: string | null;
69
+ until: string | null;
70
+ }
71
+ | { ok: false; errors: string[] };
72
+ export declare function validateSelector(opts: {
73
+ mode?: string;
74
+ date?: string;
75
+ since?: string;
76
+ until?: string;
77
+ focus?: string;
78
+ recompute?: string;
79
+ output?: string;
80
+ }): SelectorValidation;
81
+ export declare interface ProbeOptions {
82
+ artifact: string;
83
+ target: string;
84
+ baseline?: string;
85
+ mode: 'daily' | 'ad-hoc';
86
+ date?: string;
87
+ recompute: boolean;
88
+ executor?: string;
89
+ model?: string;
90
+ skillDir?: string;
91
+ contractFile?: string;
92
+ workflowFile?: string;
93
+ helperFile?: string;
94
+ contractVersion?: string;
95
+ runId?: string;
96
+ spurVersion?: string;
97
+ now?: Date;
98
+ }
99
+ export declare function buildProvenance(opts: ProbeOptions): CacheProvenance;
100
+ export declare function probe(opts: ProbeOptions): { decision: CacheDecision; current: CacheProvenance };
101
+ export declare function renderProvenanceFrontmatter(p: CacheProvenance): string;
102
+ export declare function bannerLine(p: CacheProvenance): string;
103
+ export declare function stampReport(candidateMarkdown: string, p: CacheProvenance): string;
104
+ export declare function refreshReport(publishedMarkdown: string, validatedAt: string, disposition: CacheDisposition): string;
105
+ export declare function publishAtomically(candidatePath: string, targetPath: string): void;
106
+ export declare interface CacheCliResult {
107
+ exitCode: number;
108
+ stdout: string;
109
+ stderr: string;
110
+ }
111
+ export declare function porcelainPaths(text: string): Set<string>;
112
+ export declare function diffPorcelain(before: string, now: string, expects: Set<string>): string[];