@gobing-ai/spur 0.4.0 → 0.4.2

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 (172) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +4 -2
  3. package/config/decisions/gates.yaml +36 -0
  4. package/config/decisions/history-anatomy.yaml +35 -0
  5. package/config/decisions/idea-pipeline.yaml +38 -0
  6. package/config/plugin-scripts.json +15 -0
  7. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  8. package/config/rules/recommended-pre-check.yaml +8 -3
  9. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  10. package/config/script-placement-baseline.json +2 -2
  11. package/config/templates/AGENTS.md +1 -1
  12. package/config/workflows/feature-lifecycle.yaml +9 -2
  13. package/config/workflows/history-anatomy.yaml +22 -0
  14. package/config/workflows/idea-pipeline.yaml +75 -0
  15. package/config/workflows/task-pipeline.yaml +216 -57
  16. package/config/workflows/wrapup-pipeline.yaml +107 -14
  17. package/package.json +10 -10
  18. package/plugins/sp/README.md +12 -3
  19. package/plugins/sp/agents/super-planner.md +16 -6
  20. package/plugins/sp/agents/super-reviewer.md +14 -0
  21. package/plugins/sp/bunfig.toml +8 -0
  22. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  23. package/plugins/sp/commands/dev-find-way.md +80 -0
  24. package/plugins/sp/commands/dev-idea.md +6 -0
  25. package/plugins/sp/commands/dev-plan.md +6 -0
  26. package/plugins/sp/commands/dev-review-session.md +4 -1
  27. package/plugins/sp/commands/dev-run.md +12 -2
  28. package/plugins/sp/commands/dev-runall.md +32 -15
  29. package/plugins/sp/hooks/context-owner.ts +56 -0
  30. package/plugins/sp/hooks/context-post-tool.ts +23 -0
  31. package/plugins/sp/hooks/context-session-start.ts +49 -27
  32. package/plugins/sp/hooks/context-session-stop.ts +131 -30
  33. package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
  34. package/plugins/sp/lib/idea-handoff.generated.mjs +43 -5
  35. package/plugins/sp/lib/inline-run-app.ts +28 -0
  36. package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
  37. package/plugins/sp/lib/inline-run.generated.mjs +351 -46
  38. package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
  39. package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
  40. package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
  41. package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
  42. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  43. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  44. package/plugins/sp/lib/run-summary-core.ts +561 -0
  45. package/plugins/sp/lib/transcript.ts +150 -7
  46. package/plugins/sp/plugin.json +1 -1
  47. package/plugins/sp/references/roles.md +1 -1
  48. package/plugins/sp/scripts/batch-plan.mjs +488 -0
  49. package/plugins/sp/scripts/batch-plan.ts +145 -0
  50. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  51. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  52. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  53. package/plugins/sp/scripts/commit-guard.ts +249 -0
  54. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  55. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  56. package/plugins/sp/scripts/inline-run-setup.mjs +562 -80
  57. package/plugins/sp/scripts/inline-run-setup.ts +52 -78
  58. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  59. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  60. package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
  61. package/plugins/sp/scripts/pr-reviewing.ts +34 -9
  62. package/plugins/sp/scripts/quality-gate.mjs +30213 -61
  63. package/plugins/sp/scripts/quality-gate.ts +21 -5
  64. package/plugins/sp/scripts/residual-scan.mjs +86 -31
  65. package/plugins/sp/scripts/residual-scan.ts +28 -30
  66. package/plugins/sp/scripts/run-summary.mjs +432 -14
  67. package/plugins/sp/scripts/run-summary.ts +41 -177
  68. package/plugins/sp/scripts/session-timeline.mjs +109 -11
  69. package/plugins/sp/scripts/session-timeline.ts +57 -19
  70. package/plugins/sp/scripts/task-diffstat.mjs +32 -29
  71. package/plugins/sp/scripts/task-diffstat.ts +34 -34
  72. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  73. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  74. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  75. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  76. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  77. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  78. package/plugins/sp/skills/code-verification/SKILL.md +3 -4
  79. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  80. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  81. package/plugins/sp/skills/fleet-join/SKILL.md +1 -1
  82. package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
  83. package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
  84. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  85. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
  86. package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
  87. package/plugins/sp/skills/session-review/SKILL.md +40 -7
  88. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  89. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  90. package/plugins/sp/skills/spur-cli/SKILL.md +5 -2
  91. package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
  92. package/plugins/sp/skills/spur-cli/references/decision.md +16 -8
  93. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  94. package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
  95. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -3
  96. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  97. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  98. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  99. package/plugins/sp/skills/spur-composer/SKILL.md +1 -1
  100. package/plugins/sp/skills/spur-dev/SKILL.md +20 -14
  101. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
  102. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  103. package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
  104. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  105. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
  107. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -0
  109. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  110. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  111. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +44 -12
  112. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  113. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
  114. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  115. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  116. package/schemas/state-machine-workflow.schema.json +23 -0
  117. package/spur.js +42402 -26889
  118. package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
  119. package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
  120. package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
  121. package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
  122. package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
  123. package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
  124. package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
  125. package/web/_astro/channel.D4nG9YLf.js +1 -0
  126. package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
  127. package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
  128. package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
  129. package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
  130. package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
  131. package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
  132. package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
  133. package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
  134. package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
  135. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
  136. package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
  137. package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
  138. package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
  139. package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
  140. package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
  141. package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
  142. package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
  143. package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
  144. package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
  145. package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
  146. package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
  147. package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
  148. package/web/_astro/index.BhSwN8yY.css +1 -0
  149. package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
  150. package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
  151. package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
  152. package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
  153. package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
  154. package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
  155. package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
  156. package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
  157. package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
  158. package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
  159. package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
  160. package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
  161. package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
  162. package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
  163. package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
  164. package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
  165. package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
  166. package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
  167. package/web/index.html +2 -2
  168. package/plugins/sp/commands/dev-parallel.md +0 -31
  169. package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
  170. package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
  171. package/web/_astro/channel.CUFdtWhm.js +0 -1
  172. package/web/_astro/index.Dg_3bOaJ.css +0 -1
@@ -16,14 +16,41 @@ export interface Span {
16
16
  tokens: Tokens;
17
17
  }
18
18
 
19
- type Block = Partial<Record<'type' | 'id' | 'name' | 'text' | 'tool_use_id', string>>;
19
+ export interface Block {
20
+ type?: string;
21
+ id?: string;
22
+ name?: string;
23
+ text?: string;
24
+ tool_use_id?: string;
25
+ /** Claude tool_result error flag (1146 R4): a failed subagent call is counted. */
26
+ is_error?: boolean;
27
+ }
20
28
 
21
29
  export interface Row {
22
30
  type?: string;
31
+ /** Claude `system` rows carry the compaction subtype (1146 R4). */
32
+ subtype?: string;
23
33
  timestamp?: string;
24
34
  isMeta?: boolean;
25
35
  isCompactSummary?: boolean;
26
- message?: { id?: string; content?: unknown; usage?: Record<string, number | undefined> };
36
+ id?: string; // pi: the row id doubles as the once-per-message usage key
37
+ /** pi `custom_message` body: a hook-relayed prompt's text. */
38
+ content?: string;
39
+ /** pi `custom_message` visibility; `false` keeps the row out of the operator-visible transcript. */
40
+ display?: boolean;
41
+ /** pi `custom_message` producer id (e.g. a hook), carried for diagnostics only. */
42
+ customType?: string;
43
+ message?: {
44
+ id?: string;
45
+ role?: string;
46
+ content?: unknown;
47
+ usage?: Record<string, number | undefined>;
48
+ toolCallId?: string;
49
+ /** pi toolResult rows name their tool here (1146 R4). */
50
+ toolName?: string;
51
+ /** pi toolResult error flag (1146 R4). */
52
+ isError?: boolean;
53
+ };
27
54
  }
28
55
 
29
56
  /** Running measurement of one segment or window. */
@@ -33,11 +60,13 @@ export interface Acc {
33
60
  asks: Map<string, number>;
34
61
  toolIds: Set<string>;
35
62
  messages: Map<string, Tokens>;
63
+ compactions: number;
36
64
  }
37
65
 
38
66
  const SESSION_ID = /^[A-Za-z0-9_-]+$/;
39
67
  /** Host tools whose call→result gap is the operator answering, not the agent working. */
40
- const OPERATOR_TOOLS = new Set(['AskUserQuestion']);
68
+ const OPERATOR_TOOLS = new Set(['AskUserQuestion', 'ask_user_question']);
69
+ const PI_ROLES = new Set(['user', 'assistant', 'toolResult']);
41
70
 
42
71
  export const zeroTokens = (): Tokens => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
43
72
 
@@ -65,8 +94,33 @@ export function formatTokenSplit(t: Tokens | null): string {
65
94
  return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
66
95
  }
67
96
 
68
- /** Operator prompt text, or undefined for tool results, meta rows and compaction summaries. */
97
+ /**
98
+ * Operator prompt text, or undefined for tool results, meta rows and compaction summaries.
99
+ *
100
+ * pi (1151 R1/R3): pi records the OPERATOR'S own `/skill` invocation as a user message that opens
101
+ * with `<skill name="X" …>` and whose arguments follow the closing tag — it does not inject skill
102
+ * bodies as user rows (a 2,452-row census of local transcripts found none; the one synthetic
103
+ * body-only fixture row had no real counterpart). Every such row is therefore an operator prompt,
104
+ * rendered `/X <args>`; an ARGUMENT-LESS wrapper is still an invocation and renders `/X` (the skill
105
+ * name is the stage label the review groups by, so arguments alone would lose which command ran).
106
+ *
107
+ * pi (1151 follow-up): a hook-relayed `custom_message` (`display: true`) is a real operator prompt —
108
+ * the host shows it and the agent answers it — so it opens a segment like typed text; a
109
+ * `display: false` row is a host-only notice and stays non-segmenting activity.
110
+ */
69
111
  export function promptText(row: Row): string | undefined {
112
+ if (row.type === 'custom_message') {
113
+ if (row.display === false) return undefined;
114
+ const text = typeof row.content === 'string' ? row.content.trim() : '';
115
+ return text === '' ? undefined : text;
116
+ }
117
+ if (row.type === 'message') {
118
+ // pi (1130): only a user-role message with text content is a prompt; toolResult rows are not.
119
+ if (row.message?.role !== 'user') return undefined;
120
+ const text = firstTextContent(row);
121
+ if (text === undefined) return undefined;
122
+ return skillInvocationPrompt(text) ?? text;
123
+ }
70
124
  if (row.type !== 'user' || row.isMeta || row.isCompactSummary) return undefined;
71
125
  const content = row.message?.content;
72
126
  if (typeof content === 'string') return content;
@@ -74,6 +128,31 @@ export function promptText(row: Row): string | undefined {
74
128
  return (content as Block[]).find((b) => b.type === 'text')?.text;
75
129
  }
76
130
 
131
+ /** First `type:text` block of a pi message, or its string content. */
132
+ function firstTextContent(row: Row): string | undefined {
133
+ const content = row.message?.content;
134
+ if (typeof content === 'string') return content;
135
+ if (!Array.isArray(content)) return undefined;
136
+ return (content as Block[]).find((b) => b.type === 'text')?.text;
137
+ }
138
+
139
+ /**
140
+ * 1151 R1: `/X <args>` for a pi operator `/skill` invocation wrapper, undefined for anything else.
141
+ *
142
+ * The wrapper is `^\s*<skill name="X"` — the anchored, complete quoted name is the only signal;
143
+ * text that merely CONTAINS `<skill` later in the body stays plain operator text. Arguments are the
144
+ * text after the LAST `</skill>` (a body may itself quote the tag); a wrapper with no closing tag
145
+ * renders `/X` alone rather than dumping the body as the prompt. A malformed name (no closing quote)
146
+ * is not a wrapper.
147
+ */
148
+ export function skillInvocationPrompt(text: string): string | undefined {
149
+ const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
150
+ if (name === undefined) return undefined;
151
+ const close = text.lastIndexOf('</skill>');
152
+ const args = close < 0 ? '' : text.slice(close + '</skill>'.length).trim();
153
+ return args === '' ? `/${name}` : `/${name} ${args}`;
154
+ }
155
+
77
156
  export function sumTokens(all: Tokens[]): Tokens {
78
157
  const total = zeroTokens();
79
158
  for (const t of all) for (const k of Object.keys(total) as (keyof Tokens)[]) total[k] += t[k];
@@ -95,11 +174,38 @@ export const newAcc = (ts: number): Acc => ({
95
174
  asks: new Map(),
96
175
  toolIds: new Set(),
97
176
  messages: new Map(),
177
+ compactions: 0,
98
178
  });
99
179
 
100
180
  /** Fold one timestamped non-prompt record into a segment or window. */
101
181
  export function accumulate(acc: Acc, row: Row, ts: number): void {
102
182
  acc.last = Math.max(acc.last, ts);
183
+ if (row.type === 'message') {
184
+ // pi (1130): mirrors the importer's field semantics (piRole / normalizeOmpToolCall /
185
+ // ompToolResultTiming) — toolCall blocks on assistant rows, results paired by toolCallId.
186
+ const m = row.message;
187
+ if (m?.role === 'assistant') {
188
+ const blocks = Array.isArray(m.content) ? (m.content as Block[]) : [];
189
+ for (const b of blocks) {
190
+ if (b.type === 'toolCall' && b.id) {
191
+ acc.toolIds.add(b.id);
192
+ if (b.name && OPERATOR_TOOLS.has(b.name)) acc.asks.set(b.id, ts);
193
+ }
194
+ }
195
+ const u = m.usage;
196
+ if (u && row.id)
197
+ acc.messages.set(row.id, {
198
+ input: u.input ?? 0,
199
+ cacheCreate: u.cacheWrite ?? 0,
200
+ cacheRead: u.cacheRead ?? 0,
201
+ output: u.output ?? 0,
202
+ });
203
+ } else if (m?.role === 'toolResult' && m.toolCallId && acc.asks.has(m.toolCallId)) {
204
+ acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
205
+ }
206
+ return;
207
+ }
208
+ if (row.type === 'compaction') acc.compactions++; // pi-only row type (1130 R4)
103
209
  const blocks = Array.isArray(row.message?.content) ? (row.message.content as Block[]) : [];
104
210
  for (const b of blocks) {
105
211
  if (b.type === 'tool_use' && b.id) {
@@ -121,8 +227,29 @@ export function accumulate(acc: Acc, row: Row, ts: number): void {
121
227
  }
122
228
  }
123
229
 
230
+ /**
231
+ * 1146 R4: a compaction boundary in either host shape — pi writes a `compaction` row, Claude a
232
+ * `system` row with `subtype:"compact_boundary"`.
233
+ */
234
+ export function isCompaction(row: Row): boolean {
235
+ return row.type === 'compaction' || (row.type === 'system' && row.subtype === 'compact_boundary');
236
+ }
237
+
238
+ /** First row matching a known shape decides: `type:"message"` with a pi role → pi, else Claude. */
239
+ export function sniffFormat(rows: Row[]): 'claude' | 'pi' | 'unknown' {
240
+ for (const row of rows) {
241
+ if (row.type === 'message' && PI_ROLES.has(row.message?.role ?? '')) return 'pi';
242
+ if (row.type === 'user' || row.type === 'assistant') return 'claude';
243
+ }
244
+ return 'unknown';
245
+ }
246
+
124
247
  /** Timestamped rows in file order; malformed lines counted, timestamp-less rows dropped. */
125
- export function parseRows(lines: string[]): { rows: [Row, number][]; skippedLines: number } {
248
+ export function parseRows(lines: string[]): {
249
+ rows: [Row, number][];
250
+ skippedLines: number;
251
+ format: 'claude' | 'pi' | 'unknown';
252
+ } {
126
253
  const rows: [Row, number][] = [];
127
254
  let skippedLines = 0;
128
255
  for (const line of lines) {
@@ -137,7 +264,7 @@ export function parseRows(lines: string[]): { rows: [Row, number][]; skippedLine
137
264
  const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
138
265
  if (!Number.isNaN(ts)) rows.push([row, ts]);
139
266
  }
140
- return { rows, skippedLines };
267
+ return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
141
268
  }
142
269
 
143
270
  export type Resolved = { ok: true; path: string } | { ok: false; reason: string };
@@ -149,7 +276,23 @@ export function resolveTranscript(
149
276
  ): Resolved {
150
277
  if (override) return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: 'no transcript' };
151
278
  const id = env.CLAUDE_CODE_SESSION_ID;
152
- if (!id) return { ok: false, reason: 'no host session id (CLAUDE_CODE_SESSION_ID); pass --transcript <path>' };
279
+ if (!id) {
280
+ // 1138 R1: pi exports PI_SESSION_FILE (an exact path) and no Claude session id. Order is
281
+ // explicit override → Claude id → PI_SESSION_FILE; PI_SESSION_ID is never used for lookup
282
+ // because pi's filename embeds a timestamp, so the path is not derivable from the id.
283
+ const piFile = env.PI_SESSION_FILE;
284
+ if (piFile !== undefined && piFile !== '') {
285
+ return existsSync(piFile)
286
+ ? { ok: true, path: piFile }
287
+ : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
288
+ }
289
+ return {
290
+ ok: false,
291
+ reason:
292
+ 'no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, ' +
293
+ 'or set PI_SESSION_FILE)',
294
+ };
295
+ }
153
296
  if (!SESSION_ID.test(id)) return { ok: false, reason: 'refusing a session id with path characters' };
154
297
  if (!existsSync(projectsRoot)) return { ok: false, reason: `no transcript root ${projectsRoot}` };
155
298
  for (const dir of readdirSync(projectsRoot)) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Spur — a local-first harness engineering toolkit that wraps mainstream coding agents with constraint checking, workflow orchestration, and history analytics.",
5
5
  "extensions": {
6
6
  "pi": ["./hooks/pi/guard-extension.ts"]
@@ -62,7 +62,7 @@ roles:
62
62
  stages: [verify, review, dogfood]
63
63
  - id: planner
64
64
  tier: capable-2
65
- commands: [dev-plan, dev-refine, dev-brainstorm, dev-idea, dev-runall, dev-parallel, dev-next, dev-job-resume, dev-arch, dev-refineall, dev-find-next, dev-feature-change]
65
+ commands: [dev-plan, dev-refine, dev-brainstorm, dev-find-way, dev-idea, dev-runall, dev-next, dev-job-resume, dev-arch, dev-refineall, dev-find-next, dev-feature-change]
66
66
  stages: [plan, refine, brainstorm]
67
67
  ```
68
68