@gobing-ai/spur 0.4.2 → 0.4.4

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 (168) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/decisions/maker-liveness.yaml +39 -0
  3. package/config/plugin-scripts.json +0 -4
  4. package/config/rules/README.md +1 -0
  5. package/config/rules/docs/sourced-api-claims.yaml +56 -12
  6. package/config/rules/docs/verdict-confidence.yaml +68 -0
  7. package/config/rules/quality/coverage-gate.yaml +13 -0
  8. package/config/rules/recommended-pre-check.yaml +6 -2
  9. package/config/rules/strict/runtime-boundaries.yaml +10 -0
  10. package/config/rules/surface/check-cli-surface.yaml +1 -3
  11. package/config/script-placement-baseline.json +1 -19
  12. package/config/templates/AGENTS.md +7 -0
  13. package/config/workflows/feature-lifecycle.yaml +46 -1
  14. package/config/workflows/feature-verification.yaml +20 -1
  15. package/config/workflows/task-lifecycle.yaml +50 -0
  16. package/config/workflows/task-pipeline.yaml +15 -9
  17. package/package.json +10 -10
  18. package/plugins/sp/README.md +22 -16
  19. package/plugins/sp/agents/super-reviewer.md +46 -0
  20. package/plugins/sp/bunfig.toml +8 -5
  21. package/plugins/sp/commands/dev-changelog.md +1 -1
  22. package/plugins/sp/commands/dev-gitmsg.md +6 -2
  23. package/plugins/sp/commands/dev-handover.md +1 -0
  24. package/plugins/sp/commands/dev-job-dump.md +2 -1
  25. package/plugins/sp/commands/dev-refine.md +14 -2
  26. package/plugins/sp/commands/dev-refineall.md +7 -1
  27. package/plugins/sp/commands/dev-review.md +1 -0
  28. package/plugins/sp/hooks/careful-guard.ts +1 -1
  29. package/plugins/sp/hooks/context-post-tool.ts +7 -427
  30. package/plugins/sp/hooks/context-session-start.ts +5 -213
  31. package/plugins/sp/hooks/context-session-stop.ts +5 -191
  32. package/plugins/sp/hooks/pi/guard-extension.ts +6 -5
  33. package/plugins/sp/hooks/task-write-guard.ts +1 -1
  34. package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
  35. package/plugins/sp/lib/context-post-tool.ts +432 -0
  36. package/plugins/sp/lib/context-session-start.ts +214 -0
  37. package/plugins/sp/lib/context-session-stop.ts +196 -0
  38. package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
  39. package/plugins/sp/lib/dogfood-report.ts +278 -0
  40. package/plugins/sp/lib/history-anatomy.generated.mjs +18 -18
  41. package/plugins/sp/lib/idea-handoff.generated.mjs +91 -91
  42. package/plugins/sp/lib/inline-run-app.ts +40 -0
  43. package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
  44. package/plugins/sp/lib/inline-run.generated.mjs +1425 -212
  45. package/plugins/sp/lib/plan-projection.generated.d.mts +150 -0
  46. package/plugins/sp/lib/plan-projection.generated.mjs +623 -9
  47. package/plugins/sp/lib/quality-gate.generated.d.mts +1 -0
  48. package/plugins/sp/lib/quality-gate.generated.mjs +13809 -13969
  49. package/plugins/sp/lib/residual-scan.generated.d.mts +20 -1
  50. package/plugins/sp/lib/residual-scan.generated.mjs +120 -25
  51. package/plugins/sp/lib/run-summary-core.ts +34 -370
  52. package/plugins/sp/lib/step-profile.generated.mjs +8 -8
  53. package/plugins/sp/lib/transcript.ts +37 -228
  54. package/plugins/sp/plugin.json +1 -1
  55. package/plugins/sp/scripts/batch-plan.mjs +619 -11
  56. package/plugins/sp/scripts/batch-preflight.mjs +57 -13
  57. package/plugins/sp/scripts/batch-preflight.ts +35 -295
  58. package/plugins/sp/scripts/commit-guard.mjs +6 -6
  59. package/plugins/sp/scripts/daily-summary/daily-summary.mjs +127 -108
  60. package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
  61. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +13 -12
  62. package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
  63. package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +7 -4
  64. package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
  65. package/plugins/sp/scripts/feature-verification-steps.mjs +20 -125
  66. package/plugins/sp/scripts/feature-verification-steps.ts +57 -181
  67. package/plugins/sp/scripts/history-anatomy-cache.mjs +19 -19
  68. package/plugins/sp/scripts/idea-handoff.mjs +2 -2
  69. package/plugins/sp/scripts/inline-run-dispatch.mjs +25 -24
  70. package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
  71. package/plugins/sp/scripts/inline-run-setup.mjs +164 -137
  72. package/plugins/sp/scripts/inline-run-setup.ts +1 -21
  73. package/plugins/sp/scripts/persist-out-check.mjs +5 -5
  74. package/plugins/sp/scripts/pr-reviewing.mjs +13 -13
  75. package/plugins/sp/scripts/quality-gate.mjs +13730 -13887
  76. package/plugins/sp/scripts/residual-scan.mjs +129 -31
  77. package/plugins/sp/scripts/residual-scan.ts +16 -13
  78. package/plugins/sp/scripts/run-summary.mjs +166 -112
  79. package/plugins/sp/scripts/run-summary.ts +7 -5
  80. package/plugins/sp/scripts/script-root.mjs +6 -6
  81. package/plugins/sp/scripts/session-timeline.mjs +62 -35
  82. package/plugins/sp/scripts/task-diffstat.mjs +19 -9
  83. package/plugins/sp/scripts/task-diffstat.ts +8 -39
  84. package/plugins/sp/scripts/workflow-step-profile.mjs +12 -12
  85. package/plugins/sp/scripts/wrapup-drift-probe.mjs +9 -9
  86. package/plugins/sp/scripts/wrapup-steps.mjs +25 -25
  87. package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
  88. package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
  89. package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
  90. package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
  91. package/plugins/sp/skills/next-feature/SKILL.md +1 -1
  92. package/plugins/sp/skills/next-router/SKILL.md +3 -1
  93. package/plugins/sp/skills/next-router/references/routing-table.md +9 -3
  94. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
  95. package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
  96. package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
  97. package/plugins/sp/skills/spur-cli/references/decision.md +32 -7
  98. package/plugins/sp/skills/spur-cli/references/features.md +46 -5
  99. package/plugins/sp/skills/spur-cli/references/tasks.md +24 -4
  100. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +8 -1
  101. package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -11
  102. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +1 -1
  103. package/plugins/sp/skills/spur-dev/references/execution-batch.md +19 -6
  104. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
  105. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
  106. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +4 -2
  107. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +49 -12
  108. package/schemas/section-matrix.schema.json +3 -2
  109. package/spur.js +66591 -73262
  110. package/web/_astro/{BoardApp.B3GQZvj8.js → BoardApp.Niubpe6z.js} +1 -1
  111. package/web/_astro/{BoardApp.BT7L2kSb.js → BoardApp.hF0RAIrg.js} +150 -150
  112. package/web/_astro/TaskDetail.DJpapt38.js +1 -0
  113. package/web/_astro/{arc.vaT7tPOB.js → arc.BgKcK0i8.js} +1 -1
  114. package/web/_astro/{architectureDiagram-3BPJPVTR.DRMmTjkN.js → architectureDiagram-3BPJPVTR.ChP-Ig0V.js} +1 -1
  115. package/web/_astro/{blockDiagram-GPEHLZMM.cla0LO_q.js → blockDiagram-GPEHLZMM.BVaCBoeN.js} +1 -1
  116. package/web/_astro/{c4Diagram-AAUBKEIU.D4YAIBKn.js → c4Diagram-AAUBKEIU.CHSDpEO5.js} +1 -1
  117. package/web/_astro/channel.DkpvN2tD.js +1 -0
  118. package/web/_astro/{chunk-2J33WTMH.BrSJbMRZ.js → chunk-2J33WTMH.MYRcYVRa.js} +1 -1
  119. package/web/_astro/{chunk-4BX2VUAB.CuLMgKF5.js → chunk-4BX2VUAB.S5j22Zqa.js} +1 -1
  120. package/web/_astro/{chunk-55IACEB6.zkvgu54F.js → chunk-55IACEB6.gciRPvmZ.js} +1 -1
  121. package/web/_astro/{chunk-727SXJPM.DxWkACh8.js → chunk-727SXJPM.CdqrSQkX.js} +1 -1
  122. package/web/_astro/{chunk-AQP2D5EJ.42XrmIlH.js → chunk-AQP2D5EJ.CyokdHT3.js} +1 -1
  123. package/web/_astro/{chunk-FMBD7UC4.Bao2wMK9.js → chunk-FMBD7UC4.Bg9KJhjW.js} +1 -1
  124. package/web/_astro/{chunk-ND2GUHAM.BhydojD1.js → chunk-ND2GUHAM.1gxxI-vD.js} +1 -1
  125. package/web/_astro/{chunk-QZHKN3VN.CmZ82g-H.js → chunk-QZHKN3VN.DDEYtCKU.js} +1 -1
  126. package/web/_astro/{classDiagram-4FO5ZUOK.Blr83Gt_.js → classDiagram-4FO5ZUOK.Ch3zi37T.js} +1 -1
  127. package/web/_astro/{classDiagram-v2-Q7XG4LA2.Blr83Gt_.js → classDiagram-v2-Q7XG4LA2.Ch3zi37T.js} +1 -1
  128. package/web/_astro/{cose-bilkent-S5V4N54A.qmfv0SPn.js → cose-bilkent-S5V4N54A.D2q5AINI.js} +1 -1
  129. package/web/_astro/{cynefin-OW5HDTMX.BtELhXPR.js → cynefin-OW5HDTMX.BA9kC2YT.js} +1 -1
  130. package/web/_astro/{dagre-BM42HDAG.AWmaAvXo.js → dagre-BM42HDAG.CHO08nRp.js} +1 -1
  131. package/web/_astro/{diagram-2AECGRRQ.DdH34KDi.js → diagram-2AECGRRQ.BfFQt0LW.js} +1 -1
  132. package/web/_astro/{diagram-5GNKFQAL.BmNPix1k.js → diagram-5GNKFQAL.CHMwMTe4.js} +1 -1
  133. package/web/_astro/{diagram-KO2AKTUF.BNQd_Ppo.js → diagram-KO2AKTUF.BJ-7gK-w.js} +1 -1
  134. package/web/_astro/{diagram-LMA3HP47.BAPgDi0e.js → diagram-LMA3HP47.C5xqcc0t.js} +1 -1
  135. package/web/_astro/{diagram-OG6HWLK6.DZ4zyLdC.js → diagram-OG6HWLK6.C2pQjfrC.js} +1 -1
  136. package/web/_astro/{erDiagram-TEJ5UH35.Q1TySMmw.js → erDiagram-TEJ5UH35._FoNyxgC.js} +1 -1
  137. package/web/_astro/{flowDiagram-I6XJVG4X.BXhFBicG.js → flowDiagram-I6XJVG4X.B_ENkT2I.js} +1 -1
  138. package/web/_astro/{ganttDiagram-6RSMTGT7.vHSgcuo2.js → ganttDiagram-6RSMTGT7.B3q8h77C.js} +1 -1
  139. package/web/_astro/{gitGraphDiagram-PVQCEYII.Bi1f73C7.js → gitGraphDiagram-PVQCEYII.QWZFDboZ.js} +1 -1
  140. package/web/_astro/index.BFyW13JJ.css +1 -0
  141. package/web/_astro/{infoDiagram-5YYISTIA.DPvXZEWm.js → infoDiagram-5YYISTIA.Cs6eEc92.js} +1 -1
  142. package/web/_astro/{ishikawaDiagram-YF4QCWOH.Cz829fsV.js → ishikawaDiagram-YF4QCWOH.BU9DJMHA.js} +1 -1
  143. package/web/_astro/{journeyDiagram-JHISSGLW.enRnB9Me.js → journeyDiagram-JHISSGLW.D35k6Jpe.js} +1 -1
  144. package/web/_astro/{kanban-definition-UN3LZRKU.DnTNk6d2.js → kanban-definition-UN3LZRKU.DBqcAT4m.js} +1 -1
  145. package/web/_astro/{linear.BzGnBwNL.js → linear.D-LSNK8W.js} +1 -1
  146. package/web/_astro/{mermaid.core.CSbTpIVz.js → mermaid.core.CSruWVOa.js} +4 -4
  147. package/web/_astro/{mindmap-definition-RKZ34NQL.DGJDy8zT.js → mindmap-definition-RKZ34NQL.BiFaUsfj.js} +1 -1
  148. package/web/_astro/{pieDiagram-4H26LBE5.HSPdi5_8.js → pieDiagram-4H26LBE5.D1KTKqb9.js} +1 -1
  149. package/web/_astro/{quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js → quadrantDiagram-W4KKPZXB.DBHflsMG.js} +1 -1
  150. package/web/_astro/{requirementDiagram-4Y6WPE33.CnBCtFwa.js → requirementDiagram-4Y6WPE33.hwX9CNdk.js} +1 -1
  151. package/web/_astro/{sankeyDiagram-5OEKKPKP.5M8VWAu8.js → sankeyDiagram-5OEKKPKP.DkI8mJiE.js} +1 -1
  152. package/web/_astro/{sequenceDiagram-3UESZ5HK.DN41XN-U.js → sequenceDiagram-3UESZ5HK.DMz7jBDx.js} +1 -1
  153. package/web/_astro/{stateDiagram-AJRCARHV.ErOO3W8j.js → stateDiagram-AJRCARHV.B6Ef2xJ1.js} +1 -1
  154. package/web/_astro/{stateDiagram-v2-BHNVJYJU.B_ZHICM-.js → stateDiagram-v2-BHNVJYJU.CgoaufB9.js} +1 -1
  155. package/web/_astro/{timeline-definition-PNZ67QCA.CWdosr07.js → timeline-definition-PNZ67QCA.B3c8h1t-.js} +1 -1
  156. package/web/_astro/{vennDiagram-CIIHVFJN.BHzYjNV5.js → vennDiagram-CIIHVFJN.BdHPAxAb.js} +1 -1
  157. package/web/_astro/{wardleyDiagram-YWT4CUSO.h8x4IwPX.js → wardleyDiagram-YWT4CUSO.BrJXPPAj.js} +1 -1
  158. package/web/_astro/{xychartDiagram-2RQKCTM6.RTapW2X8.js → xychartDiagram-2RQKCTM6.CMH5yFsV.js} +1 -1
  159. package/web/index.html +2 -2
  160. package/web/spur_logo.svg +1 -1
  161. package/config/proportional-route-table.ts +0 -155
  162. package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
  163. package/web/_astro/TaskDetail.Cxov1_N_.js +0 -1
  164. package/web/_astro/channel.D4nG9YLf.js +0 -1
  165. package/web/_astro/index.BhSwN8yY.css +0 -1
  166. /package/plugins/sp/{hooks → lib}/context-owner.ts +0 -0
  167. /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
  168. /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
@@ -162,13 +162,13 @@ function formatStepProfileHuman(profile) {
162
162
  `;
163
163
  }
164
164
  export {
165
- rowFlags,
166
- nonDryRuns,
167
- nearestRankP50,
168
- formatStepProfileHuman,
169
- extractExecutions,
170
- buildStepProfile,
171
- buildRows,
165
+ DEFAULT_LAST,
172
166
  DEFAULT_WINDOW_SEC,
173
- DEFAULT_LAST
167
+ buildRows,
168
+ buildStepProfile,
169
+ extractExecutions,
170
+ formatStepProfileHuman,
171
+ nearestRankP50,
172
+ nonDryRuns,
173
+ rowFlags
174
174
  };
@@ -1,240 +1,49 @@
1
1
  /**
2
- * Host-transcript measurement primitives shared by session-timeline (session review) and
3
- * run-summary (dev-run / dev-runall execution summary). Reads Claude Code JSONL rows read-only;
4
- * tokens are counted once per message id (a response spans several records). Node builtins only.
2
+ * Host-transcript adapter shared by session-timeline (session review) and run-summary (dev-run /
3
+ * dev-runall execution summary). Owns the host side: resolving the active transcript path from the
4
+ * environment, detecting the host row format, and parsing raw JSONL lines into timestamped rows.
5
+ * Reads Claude Code / pi JSONL rows read-only. Node builtins only.
6
+ *
7
+ * Task 1178 R2: the pure measurement (row/token/actor aggregation and its normalized types) lives
8
+ * in `packages/app/src/workflow/run-summary.ts` and is reached through the generated pure
9
+ * plan-projection entry. Normalize once here (`parseRows`) and hand the rows to that owner; the
10
+ * re-exports below keep the plugin's one import path for the whole surface.
5
11
  */
6
12
  import { existsSync, readdirSync } from 'node:fs';
7
13
  import { homedir } from 'node:os';
8
14
  import { join } from 'node:path';
9
-
10
- export type Tokens = Record<'input' | 'cacheCreate' | 'cacheRead' | 'output', number>;
11
-
12
- export interface Span {
13
- workMs: number;
14
- waitMs: number;
15
- toolCalls: number;
16
- tokens: Tokens;
17
- }
18
-
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
- }
28
-
29
- export interface Row {
30
- type?: string;
31
- /** Claude `system` rows carry the compaction subtype (1146 R4). */
32
- subtype?: string;
33
- timestamp?: string;
34
- isMeta?: boolean;
35
- isCompactSummary?: boolean;
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
- };
54
- }
55
-
56
- /** Running measurement of one segment or window. */
57
- export interface Acc {
58
- last: number;
59
- askMs: number;
60
- asks: Map<string, number>;
61
- toolIds: Set<string>;
62
- messages: Map<string, Tokens>;
63
- compactions: number;
64
- }
15
+ import type { Row } from './plan-projection.generated.mjs';
16
+
17
+ export {
18
+ type Acc,
19
+ type ActorSplit,
20
+ accumulate,
21
+ actorSplit,
22
+ type Block,
23
+ buildRunSummary,
24
+ formatDuration,
25
+ formatTokenSplit,
26
+ formatTokens,
27
+ type GateTime,
28
+ isCompaction,
29
+ newAcc,
30
+ type Progress,
31
+ promptText,
32
+ type Row,
33
+ type RunSummary,
34
+ type Span,
35
+ type SummaryRow,
36
+ skillInvocationPrompt,
37
+ sumSpans,
38
+ sumTokens,
39
+ type Tokens,
40
+ zeroTokens,
41
+ } from './plan-projection.generated.mjs';
65
42
 
66
43
  const SESSION_ID = /^[A-Za-z0-9_-]+$/;
67
- /** Host tools whose call→result gap is the operator answering, not the agent working. */
68
- const OPERATOR_TOOLS = new Set(['AskUserQuestion', 'ask_user_question']);
44
+ /** pi roles that prove a `type:"message"` row is a pi transcript, not Claude's `user`/`assistant`. */
69
45
  const PI_ROLES = new Set(['user', 'assistant', 'toolResult']);
70
46
 
71
- export const zeroTokens = (): Tokens => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
72
-
73
- export function formatDuration(ms: number): string {
74
- const s = Math.round(ms / 1000);
75
- const h = Math.floor(s / 3600);
76
- const m = Math.floor((s % 3600) / 60);
77
- const ss = String(s % 60).padStart(2, '0');
78
- return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;
79
- }
80
-
81
- /** Compact token units: `950`, `41k`, `28.4M`. */
82
- export function formatTokens(n: number): string {
83
- if (n >= 999_500) return `${(n / 1e6).toFixed(1)}M`;
84
- return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
85
- }
86
-
87
- /**
88
- * `<total> / <non-cached>`: total = every input class + output; non-cached drops cache reads, the
89
- * share re-served from the prompt cache, leaving the fresh input, cache writes and output.
90
- */
91
- export function formatTokenSplit(t: Tokens | null): string {
92
- if (!t) return 'n/a';
93
- const nonCached = t.input + t.cacheCreate + t.output;
94
- return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
95
- }
96
-
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
- */
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
- }
124
- if (row.type !== 'user' || row.isMeta || row.isCompactSummary) return undefined;
125
- const content = row.message?.content;
126
- if (typeof content === 'string') return content;
127
- if (!Array.isArray(content) || (content as Block[]).some((b) => b.type === 'tool_result')) return undefined;
128
- return (content as Block[]).find((b) => b.type === 'text')?.text;
129
- }
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
-
156
- export function sumTokens(all: Tokens[]): Tokens {
157
- const total = zeroTokens();
158
- for (const t of all) for (const k of Object.keys(total) as (keyof Tokens)[]) total[k] += t[k];
159
- return total;
160
- }
161
-
162
- export function sumSpans(spans: Span[]): Span {
163
- return {
164
- workMs: spans.reduce((n, s) => n + s.workMs, 0),
165
- waitMs: spans.reduce((n, s) => n + s.waitMs, 0),
166
- toolCalls: spans.reduce((n, s) => n + s.toolCalls, 0),
167
- tokens: sumTokens(spans.map((s) => s.tokens)),
168
- };
169
- }
170
-
171
- export const newAcc = (ts: number): Acc => ({
172
- last: ts,
173
- askMs: 0,
174
- asks: new Map(),
175
- toolIds: new Set(),
176
- messages: new Map(),
177
- compactions: 0,
178
- });
179
-
180
- /** Fold one timestamped non-prompt record into a segment or window. */
181
- export function accumulate(acc: Acc, row: Row, ts: number): void {
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)
209
- const blocks = Array.isArray(row.message?.content) ? (row.message.content as Block[]) : [];
210
- for (const b of blocks) {
211
- if (b.type === 'tool_use' && b.id) {
212
- acc.toolIds.add(b.id);
213
- if (b.name && OPERATOR_TOOLS.has(b.name)) acc.asks.set(b.id, ts);
214
- } else if (b.type === 'tool_result' && b.tool_use_id && acc.asks.has(b.tool_use_id)) {
215
- acc.askMs += ts - (acc.asks.get(b.tool_use_id) ?? ts);
216
- }
217
- }
218
- const u = row.message?.usage;
219
- // Split records of one response repeat its usage: keyed by message id, overwrite, never add.
220
- if (row.type === 'assistant' && u && row.message?.id) {
221
- acc.messages.set(row.message.id, {
222
- input: u.input_tokens ?? 0,
223
- cacheCreate: u.cache_creation_input_tokens ?? 0,
224
- cacheRead: u.cache_read_input_tokens ?? 0,
225
- output: u.output_tokens ?? 0,
226
- });
227
- }
228
- }
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
47
  /** First row matching a known shape decides: `type:"message"` with a pi role → pi, else Claude. */
239
48
  export function sniffFormat(rows: Row[]): 'claude' | 'pi' | 'unknown' {
240
49
  for (const row of rows) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sp",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
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"]