@gobing-ai/spur 0.3.99 → 0.4.1

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 +17 -0
  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/decisions/task-pipeline.yaml +55 -0
  7. package/config/plugin-scripts.json +25 -0
  8. package/config/rules/boundary/sp-runtime-path.yaml +2 -0
  9. package/config/rules/docs/sourced-api-claims.yaml +60 -0
  10. package/config/rules/quality/coverage-gate.yaml +5 -3
  11. package/config/rules/recommended-pre-check.yaml +8 -3
  12. package/config/rules/strict/runtime-boundaries.yaml +11 -0
  13. package/config/rules/structure/protected-files.yaml +3 -0
  14. package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
  15. package/config/script-placement-baseline.json +2 -2
  16. package/config/workflows/feature-lifecycle.yaml +9 -2
  17. package/config/workflows/history-anatomy.yaml +22 -0
  18. package/config/workflows/idea-pipeline.yaml +75 -0
  19. package/config/workflows/task-pipeline.yaml +368 -49
  20. package/config/workflows/wrapup-pipeline.yaml +106 -13
  21. package/package.json +9 -9
  22. package/plugins/sp/README.md +25 -2
  23. package/plugins/sp/agents/super-planner.md +11 -0
  24. package/plugins/sp/agents/super-reviewer.md +39 -11
  25. package/plugins/sp/bunfig.toml +8 -0
  26. package/plugins/sp/commands/dev-brainstorm.md +29 -16
  27. package/plugins/sp/commands/dev-changelog.md +5 -4
  28. package/plugins/sp/commands/dev-find-way.md +80 -0
  29. package/plugins/sp/commands/dev-idea.md +6 -0
  30. package/plugins/sp/commands/dev-parallel.md +8 -2
  31. package/plugins/sp/commands/dev-plan.md +6 -0
  32. package/plugins/sp/commands/dev-review-session.md +14 -7
  33. package/plugins/sp/commands/dev-run.md +17 -4
  34. package/plugins/sp/commands/dev-runall.md +21 -10
  35. package/plugins/sp/hooks/agent-lifecycle.ts +155 -0
  36. package/plugins/sp/hooks/context-session-start.ts +1 -1
  37. package/plugins/sp/hooks/fleet-guest-stop.ts +180 -0
  38. package/plugins/sp/hooks/hooks.json +45 -1
  39. package/plugins/sp/hooks/pi/guard-extension.ts +21 -0
  40. package/plugins/sp/lib/history-anatomy.generated.mjs +5 -0
  41. package/plugins/sp/lib/idea-handoff.generated.mjs +72 -11
  42. package/plugins/sp/lib/inline-run-app.ts +28 -0
  43. package/plugins/sp/lib/inline-run.generated.d.mts +6 -0
  44. package/plugins/sp/lib/inline-run.generated.mjs +364 -46
  45. package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
  46. package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
  47. package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
  48. package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
  49. package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
  50. package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
  51. package/plugins/sp/lib/run-summary-core.ts +561 -0
  52. package/plugins/sp/lib/transcript.ts +275 -0
  53. package/plugins/sp/plugin.json +1 -1
  54. package/plugins/sp/references/roles.md +1 -1
  55. package/plugins/sp/scripts/batch-plan.mjs +335 -0
  56. package/plugins/sp/scripts/batch-plan.ts +100 -0
  57. package/plugins/sp/scripts/batch-preflight.mjs +51 -3
  58. package/plugins/sp/scripts/batch-preflight.ts +72 -1
  59. package/plugins/sp/scripts/commit-guard.mjs +216 -0
  60. package/plugins/sp/scripts/commit-guard.ts +249 -0
  61. package/plugins/sp/scripts/history-anatomy-cache.mjs +5 -0
  62. package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
  63. package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
  64. package/plugins/sp/scripts/inline-run-setup.mjs +563 -91
  65. package/plugins/sp/scripts/inline-run-setup.ts +65 -97
  66. package/plugins/sp/scripts/persist-out-check.mjs +90 -41
  67. package/plugins/sp/scripts/persist-out-check.ts +112 -57
  68. package/plugins/sp/scripts/quality-gate.mjs +30407 -73
  69. package/plugins/sp/scripts/quality-gate.ts +21 -5
  70. package/plugins/sp/scripts/residual-scan.mjs +88 -38
  71. package/plugins/sp/scripts/residual-scan.ts +35 -38
  72. package/plugins/sp/scripts/run-summary.mjs +693 -0
  73. package/plugins/sp/scripts/run-summary.ts +105 -0
  74. package/plugins/sp/scripts/session-timeline.mjs +331 -0
  75. package/plugins/sp/scripts/session-timeline.ts +204 -0
  76. package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
  77. package/plugins/sp/scripts/wrapup-steps.ts +203 -1
  78. package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
  79. package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
  80. package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
  81. package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
  82. package/plugins/sp/skills/code-verification/SKILL.md +8 -4
  83. package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
  84. package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
  85. package/plugins/sp/skills/fleet-join/SKILL.md +63 -0
  86. package/plugins/sp/skills/session-review/SKILL.md +77 -24
  87. package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
  88. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
  89. package/plugins/sp/skills/spur-cli/SKILL.md +6 -2
  90. package/plugins/sp/skills/spur-cli/references/agent.md +124 -17
  91. package/plugins/sp/skills/spur-cli/references/decision.md +98 -0
  92. package/plugins/sp/skills/spur-cli/references/message.md +7 -7
  93. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +41 -3
  94. package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
  95. package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
  96. package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
  97. package/plugins/sp/skills/spur-composer/SKILL.md +28 -2
  98. package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
  99. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
  100. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
  101. package/plugins/sp/skills/spur-dev/references/dev-operations.md +51 -18
  102. package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
  103. package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
  104. package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -963
  105. package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
  106. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +20 -0
  107. package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
  108. package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
  109. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +51 -9
  110. package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
  111. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +361 -156
  112. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +6 -0
  113. package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
  114. package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
  115. package/schemas/decision-catalog.schema.json +226 -0
  116. package/schemas/spur-config.schema.json +25 -0
  117. package/schemas/state-machine-workflow.schema.json +23 -0
  118. package/spur.js +28165 -19051
  119. package/web/_astro/{BoardApp.i4eh_fHA.js → BoardApp.D66k_s_T.js} +1 -1
  120. package/web/_astro/BoardApp.D6sjAJr_.js +324 -0
  121. package/web/_astro/{TaskDetail.C5Cu8LdB.js → TaskDetail.BynLnMNc.js} +1 -1
  122. package/web/_astro/{arc.Bu5XpBEz.js → arc._BUadrdH.js} +1 -1
  123. package/web/_astro/{architectureDiagram-3BPJPVTR.9vHQTvhF.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
  124. package/web/_astro/{blockDiagram-GPEHLZMM.Cn2Xl7D6.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
  125. package/web/_astro/{c4Diagram-AAUBKEIU.7o0P_QDo.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
  126. package/web/_astro/channel.DZVfto-r.js +1 -0
  127. package/web/_astro/{chunk-2J33WTMH.C3zZFsNt.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
  128. package/web/_astro/{chunk-4BX2VUAB.CNy_M7fO.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
  129. package/web/_astro/{chunk-55IACEB6.2TiPnLBL.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
  130. package/web/_astro/{chunk-727SXJPM.5CGSyKAp.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
  131. package/web/_astro/{chunk-AQP2D5EJ.w-ZIgfsK.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
  132. package/web/_astro/{chunk-FMBD7UC4.BkcEyhKa.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
  133. package/web/_astro/{chunk-ND2GUHAM.mxKKbBG4.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
  134. package/web/_astro/{chunk-QZHKN3VN.cyA0z9aT.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
  135. package/web/_astro/{classDiagram-4FO5ZUOK.CqtUzwhX.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
  136. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CqtUzwhX.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
  137. package/web/_astro/{cose-bilkent-S5V4N54A.BExPTopI.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
  138. package/web/_astro/{cynefin-OW5HDTMX.Bq2i10Cn.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
  139. package/web/_astro/{dagre-BM42HDAG.By_HsOuv.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
  140. package/web/_astro/{diagram-2AECGRRQ.BtU0b7hz.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
  141. package/web/_astro/{diagram-5GNKFQAL.D00ztMdT.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
  142. package/web/_astro/{diagram-KO2AKTUF.DMVJWaUN.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
  143. package/web/_astro/{diagram-LMA3HP47.Cxqrjg5-.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
  144. package/web/_astro/{diagram-OG6HWLK6.2lz_1B5h.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
  145. package/web/_astro/{erDiagram-TEJ5UH35.BVQEuJ3T.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
  146. package/web/_astro/{flowDiagram-I6XJVG4X.Qlyo90ky.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
  147. package/web/_astro/{ganttDiagram-6RSMTGT7.DRwb8HHZ.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
  148. package/web/_astro/{gitGraphDiagram-PVQCEYII.YfoPthfS.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
  149. package/web/_astro/index.COh_kBxN.css +1 -0
  150. package/web/_astro/{infoDiagram-5YYISTIA.DvFREck1.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
  151. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BzCj9Q0-.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
  152. package/web/_astro/{journeyDiagram-JHISSGLW.BDMT_AFb.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
  153. package/web/_astro/{kanban-definition-UN3LZRKU.D6Fh3gj9.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
  154. package/web/_astro/{linear.Dxw00fGQ.js → linear.MzmRrNER.js} +1 -1
  155. package/web/_astro/{mermaid.core.CsoxUWa0.js → mermaid.core.fK-WNswS.js} +4 -4
  156. package/web/_astro/{mindmap-definition-RKZ34NQL.CN8oeHLu.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
  157. package/web/_astro/{pieDiagram-4H26LBE5.C7ADqQ_7.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
  158. package/web/_astro/{quadrantDiagram-W4KKPZXB.Cw9lKyHP.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
  159. package/web/_astro/{requirementDiagram-4Y6WPE33.B1ni-z4q.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
  160. package/web/_astro/{sankeyDiagram-5OEKKPKP.BhexdjVr.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
  161. package/web/_astro/{sequenceDiagram-3UESZ5HK.C3Rm7dpQ.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
  162. package/web/_astro/{stateDiagram-AJRCARHV.CkuMt27u.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
  163. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Dc_aazUA.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
  164. package/web/_astro/{timeline-definition-PNZ67QCA.ja7sUFhH.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
  165. package/web/_astro/{vennDiagram-CIIHVFJN.BxIshX_K.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
  166. package/web/_astro/{wardleyDiagram-YWT4CUSO.DfHlC0sh.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
  167. package/web/_astro/{xychartDiagram-2RQKCTM6.DSnhHwCh.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
  168. package/web/board-runtime.json +1 -1
  169. package/web/index.html +2 -2
  170. package/web/_astro/BoardApp.BVp-LYi6.js +0 -322
  171. package/web/_astro/channel.6IFIKBhu.js +0 -1
  172. package/web/_astro/index.CbNz17Sx.css +0 -1
@@ -0,0 +1,693 @@
1
+ #!/usr/bin/env node
2
+ // @bun
3
+
4
+ // plugins/sp/scripts/run-summary.ts
5
+ import { readFileSync as readFileSync2 } from "fs";
6
+
7
+ // plugins/sp/lib/env.ts
8
+ function getEnvVars() {
9
+ return process.env;
10
+ }
11
+
12
+ // plugins/sp/lib/run-summary-core.ts
13
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { join as join2 } from "node:path";
15
+
16
+ // plugins/sp/lib/transcript.ts
17
+ import { existsSync, readdirSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ var SESSION_ID = /^[A-Za-z0-9_-]+$/;
21
+ var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
22
+ var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
23
+ var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
24
+ function formatDuration(ms) {
25
+ const s = Math.round(ms / 1000);
26
+ const h = Math.floor(s / 3600);
27
+ const m = Math.floor(s % 3600 / 60);
28
+ const ss = String(s % 60).padStart(2, "0");
29
+ return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${ss}` : `${m}:${ss}`;
30
+ }
31
+ function formatTokens(n) {
32
+ if (n >= 999500)
33
+ return `${(n / 1e6).toFixed(1)}M`;
34
+ return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n);
35
+ }
36
+ function formatTokenSplit(t) {
37
+ if (!t)
38
+ return "n/a";
39
+ const nonCached = t.input + t.cacheCreate + t.output;
40
+ return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
41
+ }
42
+ function promptText(row) {
43
+ if (row.type === "message") {
44
+ if (row.message?.role !== "user")
45
+ return;
46
+ if (isInjectedPrompt(row))
47
+ return;
48
+ const content2 = row.message.content;
49
+ if (typeof content2 === "string")
50
+ return content2;
51
+ if (!Array.isArray(content2))
52
+ return;
53
+ return content2.find((b) => b.type === "text")?.text;
54
+ }
55
+ if (row.type !== "user" || row.isMeta || row.isCompactSummary)
56
+ return;
57
+ const content = row.message?.content;
58
+ if (typeof content === "string")
59
+ return content;
60
+ if (!Array.isArray(content) || content.some((b) => b.type === "tool_result"))
61
+ return;
62
+ return content.find((b) => b.type === "text")?.text;
63
+ }
64
+ function isInjectedPrompt(row) {
65
+ if (row.type !== "message" || row.message?.role !== "user")
66
+ return false;
67
+ const content = row.message.content;
68
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((b) => b.type === "text")?.text : undefined;
69
+ return typeof text === "string" && text.replace(/^\s+/, "").startsWith('<skill name="');
70
+ }
71
+ function sumTokens(all) {
72
+ const total = zeroTokens();
73
+ for (const t of all)
74
+ for (const k of Object.keys(total))
75
+ total[k] += t[k];
76
+ return total;
77
+ }
78
+ function sumSpans(spans) {
79
+ return {
80
+ workMs: spans.reduce((n, s) => n + s.workMs, 0),
81
+ waitMs: spans.reduce((n, s) => n + s.waitMs, 0),
82
+ toolCalls: spans.reduce((n, s) => n + s.toolCalls, 0),
83
+ tokens: sumTokens(spans.map((s) => s.tokens))
84
+ };
85
+ }
86
+ var newAcc = (ts) => ({
87
+ last: ts,
88
+ askMs: 0,
89
+ asks: new Map,
90
+ toolIds: new Set,
91
+ messages: new Map,
92
+ compactions: 0
93
+ });
94
+ function accumulate(acc, row, ts) {
95
+ acc.last = Math.max(acc.last, ts);
96
+ if (row.type === "message") {
97
+ const m = row.message;
98
+ if (m?.role === "assistant") {
99
+ const blocks2 = Array.isArray(m.content) ? m.content : [];
100
+ for (const b of blocks2) {
101
+ if (b.type === "toolCall" && b.id) {
102
+ acc.toolIds.add(b.id);
103
+ if (b.name && OPERATOR_TOOLS.has(b.name))
104
+ acc.asks.set(b.id, ts);
105
+ }
106
+ }
107
+ const u2 = m.usage;
108
+ if (u2 && row.id)
109
+ acc.messages.set(row.id, {
110
+ input: u2.input ?? 0,
111
+ cacheCreate: u2.cacheWrite ?? 0,
112
+ cacheRead: u2.cacheRead ?? 0,
113
+ output: u2.output ?? 0
114
+ });
115
+ } else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
116
+ acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
117
+ }
118
+ return;
119
+ }
120
+ if (row.type === "compaction")
121
+ acc.compactions++;
122
+ const blocks = Array.isArray(row.message?.content) ? row.message.content : [];
123
+ for (const b of blocks) {
124
+ if (b.type === "tool_use" && b.id) {
125
+ acc.toolIds.add(b.id);
126
+ if (b.name && OPERATOR_TOOLS.has(b.name))
127
+ acc.asks.set(b.id, ts);
128
+ } else if (b.type === "tool_result" && b.tool_use_id && acc.asks.has(b.tool_use_id)) {
129
+ acc.askMs += ts - (acc.asks.get(b.tool_use_id) ?? ts);
130
+ }
131
+ }
132
+ const u = row.message?.usage;
133
+ if (row.type === "assistant" && u && row.message?.id) {
134
+ acc.messages.set(row.message.id, {
135
+ input: u.input_tokens ?? 0,
136
+ cacheCreate: u.cache_creation_input_tokens ?? 0,
137
+ cacheRead: u.cache_read_input_tokens ?? 0,
138
+ output: u.output_tokens ?? 0
139
+ });
140
+ }
141
+ }
142
+ function isCompaction(row) {
143
+ return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
144
+ }
145
+ function sniffFormat(rows) {
146
+ for (const row of rows) {
147
+ if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
148
+ return "pi";
149
+ if (row.type === "user" || row.type === "assistant")
150
+ return "claude";
151
+ }
152
+ return "unknown";
153
+ }
154
+ function parseRows(lines) {
155
+ const rows = [];
156
+ let skippedLines = 0;
157
+ for (const line of lines) {
158
+ if (!line.trim())
159
+ continue;
160
+ let row;
161
+ try {
162
+ row = JSON.parse(line);
163
+ } catch {
164
+ skippedLines++;
165
+ continue;
166
+ }
167
+ const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
168
+ if (!Number.isNaN(ts))
169
+ rows.push([row, ts]);
170
+ }
171
+ return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
172
+ }
173
+ function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
174
+ if (override)
175
+ return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
176
+ const id = env.CLAUDE_CODE_SESSION_ID;
177
+ if (!id) {
178
+ const piFile = env.PI_SESSION_FILE;
179
+ if (piFile !== undefined && piFile !== "") {
180
+ return existsSync(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
181
+ }
182
+ return {
183
+ ok: false,
184
+ reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
185
+ };
186
+ }
187
+ if (!SESSION_ID.test(id))
188
+ return { ok: false, reason: "refusing a session id with path characters" };
189
+ if (!existsSync(projectsRoot))
190
+ return { ok: false, reason: `no transcript root ${projectsRoot}` };
191
+ for (const dir of readdirSync(projectsRoot)) {
192
+ const path = join(projectsRoot, dir, `${id}.jsonl`);
193
+ if (existsSync(path))
194
+ return { ok: true, path };
195
+ }
196
+ return { ok: false, reason: `no transcript for session ${id}` };
197
+ }
198
+
199
+ // plugins/sp/lib/env.ts
200
+ function getEnvVars2() {
201
+ return process.env;
202
+ }
203
+
204
+ // plugins/sp/lib/run-summary-core.ts
205
+ var IDLE_GAP_MS = 300000;
206
+ var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
207
+ var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
208
+ var OPERATOR_TOOLS2 = new Set(["askuserquestion", "ask_user_question"]);
209
+ var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
210
+ function actorOf(tool) {
211
+ const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
212
+ if (OPERATOR_TOOLS2.has(t))
213
+ return "operator";
214
+ if (SUBAGENT_TOOLS.has(t))
215
+ return "subagent";
216
+ if (SHELL_TOOLS.has(t))
217
+ return "shell";
218
+ return "other";
219
+ }
220
+ function unionMs(spans, start, end) {
221
+ const clipped = spans.map(([a, b]) => [Math.max(a, start), Math.min(b, end)]).filter(([a, b]) => b > a).sort((x, y) => x[0] - y[0]);
222
+ let total = 0;
223
+ let cursor = Number.NaN;
224
+ let openUntil = Number.NaN;
225
+ for (const [a, b] of clipped) {
226
+ if (!Number.isFinite(cursor)) {
227
+ cursor = a;
228
+ openUntil = b;
229
+ continue;
230
+ }
231
+ if (a > openUntil) {
232
+ total += openUntil - cursor;
233
+ cursor = a;
234
+ openUntil = b;
235
+ } else {
236
+ openUntil = Math.max(openUntil, b);
237
+ }
238
+ }
239
+ if (Number.isFinite(cursor))
240
+ total += openUntil - cursor;
241
+ return total;
242
+ }
243
+ function subtract(spans, claimed) {
244
+ let free = spans;
245
+ for (const [ca, cb] of claimed) {
246
+ const next = [];
247
+ for (const [a, b] of free) {
248
+ if (cb <= a || ca >= b) {
249
+ next.push([a, b]);
250
+ continue;
251
+ }
252
+ if (ca > a)
253
+ next.push([a, ca]);
254
+ if (cb < b)
255
+ next.push([cb, b]);
256
+ }
257
+ free = next;
258
+ }
259
+ return free;
260
+ }
261
+ function actorSplit(rows, start, end) {
262
+ const empty = {
263
+ operatorMs: 0,
264
+ longestOperatorWaitMs: 0,
265
+ modelMs: 0,
266
+ shellMs: 0,
267
+ subagentMs: 0,
268
+ otherToolMs: 0,
269
+ idleMs: 0,
270
+ compactions: 0,
271
+ subagentErrors: 0
272
+ };
273
+ const window = end - start;
274
+ if (window <= 0)
275
+ return empty;
276
+ const calls = new Map;
277
+ const results = new Map;
278
+ const eventTs = [];
279
+ let compactions = 0;
280
+ for (const [row, ts] of rows) {
281
+ if (ts < start || ts > end)
282
+ continue;
283
+ eventTs.push(ts);
284
+ if (isCompaction(row))
285
+ compactions++;
286
+ const blocks = Array.isArray(row.message?.content) ? row.message?.content : [];
287
+ if (row.message?.role === "toolResult" && row.message.toolCallId !== undefined) {
288
+ results.set(row.message.toolCallId, { ts, isError: row.message.isError === true });
289
+ }
290
+ for (const b of blocks) {
291
+ if ((b.type === "tool_use" || b.type === "toolCall") && b.id !== undefined && b.name !== undefined) {
292
+ calls.set(b.id, { id: b.id, actor: actorOf(b.name), ts });
293
+ } else if (b.type === "tool_result" && b.tool_use_id !== undefined) {
294
+ results.set(b.tool_use_id, { ts, isError: b.is_error === true });
295
+ }
296
+ }
297
+ }
298
+ let subagentErrors = 0;
299
+ const resolved = [];
300
+ for (const call of calls.values()) {
301
+ const result = results.get(call.id);
302
+ if (result?.isError === true && call.actor === "subagent")
303
+ subagentErrors++;
304
+ resolved.push({ ...call, end: Math.min(result?.ts ?? end, end) });
305
+ }
306
+ const claimed = [];
307
+ const byActor = new Map;
308
+ for (const actor of ACTOR_PRECEDENCE) {
309
+ const mine = resolved.filter((r) => r.actor === actor).map((r) => [r.ts, r.end]);
310
+ const free = subtract(mine, claimed);
311
+ for (const span of free)
312
+ claimed.push(span);
313
+ const ms = unionMs(free, start, end);
314
+ byActor.set(actor, ms);
315
+ if (actor === "operator") {
316
+ empty.longestOperatorWaitMs = Math.max(0, ...mine.map(([a, b]) => b - a));
317
+ }
318
+ }
319
+ const boundaries = [...new Set([start, end, ...eventTs, ...claimed.flat()])].sort((a, b) => a - b);
320
+ const claimedSorted = [...claimed].sort((a, b) => a[0] - b[0]);
321
+ let idleMs = 0;
322
+ for (let i = 0;i < boundaries.length - 1; i++) {
323
+ const a = boundaries[i];
324
+ const b = boundaries[i + 1];
325
+ if (b - a < IDLE_GAP_MS)
326
+ continue;
327
+ if (claimedSorted.some(([ca, cb]) => ca < b && cb > a))
328
+ continue;
329
+ idleMs += b - a;
330
+ }
331
+ const accounted = (byActor.get("operator") ?? 0) + (byActor.get("subagent") ?? 0) + (byActor.get("shell") ?? 0) + (byActor.get("other") ?? 0) + idleMs;
332
+ return {
333
+ ...empty,
334
+ operatorMs: byActor.get("operator") ?? 0,
335
+ subagentMs: byActor.get("subagent") ?? 0,
336
+ shellMs: byActor.get("shell") ?? 0,
337
+ otherToolMs: byActor.get("other") ?? 0,
338
+ idleMs,
339
+ modelMs: Math.max(0, window - accounted),
340
+ compactions,
341
+ subagentErrors
342
+ };
343
+ }
344
+ function attemptWindow(attempts) {
345
+ const starts = attempts.map((a) => Date.parse(a.startedAt ?? "")).filter((n) => !Number.isNaN(n));
346
+ const ends = attempts.map((a) => Date.parse(a.completedAt ?? "")).filter((n) => !Number.isNaN(n));
347
+ return starts.length && ends.length ? [Math.min(...starts), Math.max(...ends)] : undefined;
348
+ }
349
+ function summaryRow(stage, status, span, measured) {
350
+ return {
351
+ stage,
352
+ status,
353
+ workMs: span.workMs,
354
+ waitMs: span.waitMs,
355
+ toolCalls: measured ? span.toolCalls : null,
356
+ tokens: measured ? span.tokens : null,
357
+ time: formatDuration(span.workMs),
358
+ wait: formatDuration(span.waitMs),
359
+ token: formatTokenSplit(measured ? span.tokens : null)
360
+ };
361
+ }
362
+ function buildRunSummary(lines, inputs, since, until) {
363
+ const windows = [];
364
+ for (const { label, progress } of inputs) {
365
+ const states = (progress.states ?? []).flatMap((s) => {
366
+ const w = attemptWindow((s.actions ?? []).flatMap((a) => a.attempts ?? []));
367
+ return w ? [{ s, w }] : [];
368
+ });
369
+ if (!states.length)
370
+ throw new Error(`progress for run ${progress.runId} has no timed attempts`);
371
+ if (inputs.length === 1) {
372
+ for (const { s, w } of states) {
373
+ const stage = (s.visit ?? 1) > 1 ? `${s.state} (visit ${s.visit})` : s.state;
374
+ windows.push({ stage, status: s.status ?? "", start: w[0], end: w[1] });
375
+ }
376
+ } else {
377
+ const stage = label ?? `${progress.workflow ?? "run"} ${progress.runId.slice(0, 8)}`;
378
+ const start = Math.min(...states.map(({ w }) => w[0]));
379
+ windows.push({
380
+ stage,
381
+ status: progress.status ?? "",
382
+ start,
383
+ end: Math.max(...states.map(({ w }) => w[1]))
384
+ });
385
+ }
386
+ }
387
+ windows.sort((a, b) => a.start - b.start);
388
+ const { rows, skippedLines } = parseRows(lines);
389
+ const measure = (start, end) => {
390
+ const acc = newAcc(start);
391
+ for (const [row, ts] of rows)
392
+ if (ts >= start && ts <= end && promptText(row) === undefined)
393
+ accumulate(acc, row, ts);
394
+ const span = {
395
+ workMs: end - start - acc.askMs,
396
+ waitMs: acc.askMs,
397
+ toolCalls: acc.toolIds.size,
398
+ tokens: sumTokens([...acc.messages.values()])
399
+ };
400
+ return { span, measured: acc.toolIds.size > 0 || acc.messages.size > 0 };
401
+ };
402
+ const sinceMs = since ? Date.parse(since) : Number.NaN;
403
+ const totalStart = Number.isNaN(sinceMs) ? Math.min(...windows.map((w) => w.start)) : sinceMs;
404
+ let totalEnd = Math.max(...windows.map((w) => w.end));
405
+ if (!Number.isNaN(sinceMs)) {
406
+ for (const [, ts] of rows)
407
+ if (ts >= sinceMs)
408
+ totalEnd = Math.max(totalEnd, ts);
409
+ }
410
+ const untilMs = until ? Date.parse(until) : Number.NaN;
411
+ if (!Number.isNaN(untilMs) && untilMs > totalStart)
412
+ totalEnd = untilMs;
413
+ const stageRows = windows.map((w) => {
414
+ const { span, measured } = measure(w.start, w.end);
415
+ return summaryRow(w.stage, w.status, span, measured);
416
+ });
417
+ const totalMeasure = measure(totalStart, totalEnd);
418
+ const statuses = [...new Set(inputs.map((i) => i.progress.status ?? ""))].join("/");
419
+ const total = summaryRow("Total", statuses, totalMeasure.span, totalMeasure.measured);
420
+ const overlaps = windows.some((w, i) => i > 0 && w.start < (windows[i - 1]?.end ?? 0));
421
+ const sum = sumSpans(stageRows.map((r) => ({ ...r, toolCalls: r.toolCalls ?? 0, tokens: r.tokens ?? zeroTokens() })));
422
+ const overhead = {
423
+ workMs: total.workMs - sum.workMs,
424
+ waitMs: total.waitMs - sum.waitMs,
425
+ toolCalls: (total.toolCalls ?? 0) - sum.toolCalls,
426
+ tokens: Object.fromEntries(Object.keys(sum.tokens).map((k) => [k, (total.tokens?.[k] ?? 0) - sum.tokens[k]]))
427
+ };
428
+ if (!overlaps && overhead.workMs > 0)
429
+ stageRows.push(summaryRow("driver overhead", "", overhead, totalMeasure.measured));
430
+ return {
431
+ available: true,
432
+ rows: stageRows,
433
+ total,
434
+ actors: actorSplit(rows, totalStart, totalEnd),
435
+ gate: { ms: null, reason: "no matching check receipt" },
436
+ skippedLines
437
+ };
438
+ }
439
+ function renderSummaryMarkdown(summary) {
440
+ const line = (r, bold = false) => {
441
+ const stage = bold ? `**${r.stage}**` : r.stage;
442
+ return `| ${stage} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} |`;
443
+ };
444
+ const a = summary.actors;
445
+ const window = summary.total.workMs + summary.total.waitMs;
446
+ const share = (ms) => window > 0 ? `${Math.round(ms / window * 100)}%` : "n/a";
447
+ const gate = summary.gate.ms === null ? `n/a (${summary.gate.reason})` : formatDuration(summary.gate.ms);
448
+ return [
449
+ "| Stage | Status | Time | Wait | Tool calls | Token (total / non-cached) |",
450
+ "| --- | --- | ---: | ---: | ---: | ---: |",
451
+ ...summary.rows.map((r) => line(r)),
452
+ line(summary.total, true),
453
+ "",
454
+ "| Actor | Time | Share |",
455
+ "| --- | ---: | ---: |",
456
+ `| operator wait (longest ${formatDuration(a.longestOperatorWaitMs)}) | ${formatDuration(a.operatorMs)} | ${share(a.operatorMs)} |`,
457
+ `| model | ${formatDuration(a.modelMs)} | ${share(a.modelMs)} |`,
458
+ `| shell | ${formatDuration(a.shellMs)} | ${share(a.shellMs)} |`,
459
+ `| of which gate (informational) | ${gate} | — |`,
460
+ `| subagent | ${formatDuration(a.subagentMs)} | ${share(a.subagentMs)} |`,
461
+ `| other tools | ${formatDuration(a.otherToolMs)} | ${share(a.otherToolMs)} |`,
462
+ `| idle / unattributed | ${formatDuration(a.idleMs)} | ${share(a.idleMs)} |`,
463
+ "",
464
+ `Compactions: ${a.compactions} · Subagent errors: ${a.subagentErrors}`
465
+ ].join(`
466
+ `);
467
+ }
468
+ function renderRollupMarkdown(rows) {
469
+ const line = (label, r, actors, bold = false) => {
470
+ const name = bold ? `**${label}**` : label;
471
+ return `| ${name} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} | ${formatDuration(actors.operatorMs)} | ${formatDuration(actors.subagentMs)} |`;
472
+ };
473
+ const total = rows.reduce((acc, { summary }) => ({
474
+ workMs: acc.workMs + summary.total.workMs,
475
+ waitMs: acc.waitMs + summary.total.waitMs,
476
+ toolCalls: (acc.toolCalls ?? 0) + (summary.total.toolCalls ?? 0),
477
+ tokens: sumTokens([acc.tokens, summary.total.tokens ?? zeroTokens()])
478
+ }), { workMs: 0, waitMs: 0, toolCalls: 0, tokens: zeroTokens() });
479
+ const totalRow = {
480
+ stage: "Batch total",
481
+ status: "",
482
+ workMs: total.workMs,
483
+ waitMs: total.waitMs,
484
+ toolCalls: total.toolCalls,
485
+ tokens: total.tokens,
486
+ time: formatDuration(total.workMs),
487
+ wait: formatDuration(total.waitMs),
488
+ token: formatTokenSplit(total.tokens)
489
+ };
490
+ const totalActors = {
491
+ operatorMs: 0,
492
+ longestOperatorWaitMs: 0,
493
+ modelMs: 0,
494
+ shellMs: 0,
495
+ subagentMs: 0,
496
+ otherToolMs: 0,
497
+ idleMs: 0,
498
+ compactions: 0,
499
+ subagentErrors: 0
500
+ };
501
+ for (const { summary } of rows) {
502
+ totalActors.operatorMs += summary.actors.operatorMs;
503
+ totalActors.subagentMs += summary.actors.subagentMs;
504
+ totalActors.compactions += summary.actors.compactions;
505
+ totalActors.subagentErrors += summary.actors.subagentErrors;
506
+ }
507
+ const out = [
508
+ "| Run | Status | Time | Wait | Tool calls | Token (total / non-cached) | Operator wait | Subagent |",
509
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
510
+ ...rows.map(({ label, summary }) => line(label, summary.total, summary.actors)),
511
+ line("Batch total", totalRow, totalActors, true)
512
+ ];
513
+ const starts = rows.map((r) => Date.parse(r.summary.startedAt ?? "")).filter((n) => !Number.isNaN(n));
514
+ const ends = rows.map((r) => Date.parse(r.summary.completedAt ?? "")).filter((n) => !Number.isNaN(n));
515
+ const overlaps = starts.length === rows.length && rows.some((r, i) => rows.some((o, j) => i !== j && Date.parse(r.summary.startedAt ?? "") < Date.parse(o.summary.completedAt ?? "") && Date.parse(o.summary.startedAt ?? "") < Date.parse(r.summary.completedAt ?? "")));
516
+ if (overlaps && starts.length > 0 && ends.length > 0) {
517
+ out.push("", `Wall span: ${new Date(Math.min(...starts)).toISOString()} → ${new Date(Math.max(...ends)).toISOString()}`);
518
+ }
519
+ return out.join(`
520
+ `);
521
+ }
522
+ function writeCloseSummary(run, opts = {}) {
523
+ const cwd = opts.cwd ?? process.cwd();
524
+ const runDir = join2(cwd, ".spur", "run");
525
+ const rel = join2(".spur", "run", `${run.runId}-summary.md`);
526
+ const abs = join2(cwd, rel);
527
+ const na = (reason) => {
528
+ mkdirSync(runDir, { recursive: true });
529
+ writeFileSync(abs, `Execution summary: n/a (${reason})
530
+ `);
531
+ return rel;
532
+ };
533
+ try {
534
+ if (run.startedAt === undefined)
535
+ return na("run row has no started_at");
536
+ const resolved = resolveTranscript(opts.env ?? getEnvVars2(), opts.projectsRoot);
537
+ if (!resolved.ok)
538
+ return na(resolved.reason);
539
+ const lines = readFileSync(resolved.path, "utf8").split(`
540
+ `);
541
+ const summary = buildRunSummary(lines, [{ progress: run.progress }], run.startedAt, run.completedAt);
542
+ if (summary.rows.length === 0)
543
+ return na("no timed attempts");
544
+ mkdirSync(runDir, { recursive: true });
545
+ writeFileSync(abs, `${renderSummaryMarkdown(summary)}
546
+ `);
547
+ writeFileSync(join2(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
548
+ return rel;
549
+ } catch (error) {
550
+ return na(error instanceof Error ? error.message : String(error));
551
+ }
552
+ }
553
+ function gateFromReceipt(cwd, runId) {
554
+ const path = join2(cwd, ".spur", "run", `${runId}-check-receipt.json`);
555
+ if (!existsSync2(path))
556
+ return { ms: null, reason: "no matching check receipt" };
557
+ try {
558
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
559
+ if (parsed.runId !== runId)
560
+ return { ms: null, reason: "check receipt belongs to another run" };
561
+ const ms = parsed.gateRuntimeMs;
562
+ if (typeof ms !== "number" || !Number.isFinite(ms))
563
+ return { ms: null, reason: "check receipt has no gate time" };
564
+ return { ms };
565
+ } catch {
566
+ return { ms: null, reason: "check receipt is unreadable" };
567
+ }
568
+ }
569
+
570
+ // plugins/sp/lib/transcript.ts
571
+ import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
572
+ import { homedir as homedir2 } from "node:os";
573
+ import { join as join3 } from "node:path";
574
+ var SESSION_ID2 = /^[A-Za-z0-9_-]+$/;
575
+ var OPERATOR_TOOLS3 = new Set(["AskUserQuestion", "ask_user_question"]);
576
+ var PI_ROLES2 = new Set(["user", "assistant", "toolResult"]);
577
+ function resolveTranscript2(env, projectsRoot = join3(homedir2(), ".claude", "projects"), override) {
578
+ if (override)
579
+ return existsSync3(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
580
+ const id = env.CLAUDE_CODE_SESSION_ID;
581
+ if (!id) {
582
+ const piFile = env.PI_SESSION_FILE;
583
+ if (piFile !== undefined && piFile !== "") {
584
+ return existsSync3(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
585
+ }
586
+ return {
587
+ ok: false,
588
+ reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
589
+ };
590
+ }
591
+ if (!SESSION_ID2.test(id))
592
+ return { ok: false, reason: "refusing a session id with path characters" };
593
+ if (!existsSync3(projectsRoot))
594
+ return { ok: false, reason: `no transcript root ${projectsRoot}` };
595
+ for (const dir of readdirSync2(projectsRoot)) {
596
+ const path = join3(projectsRoot, dir, `${id}.jsonl`);
597
+ if (existsSync3(path))
598
+ return { ok: true, path };
599
+ }
600
+ return { ok: false, reason: `no transcript for session ${id}` };
601
+ }
602
+
603
+ // plugins/sp/scripts/run-summary.ts
604
+ var RUN_SUMMARY_USAGE = "usage: run-summary (--progress [<label>=]<file> [--progress ...] | --rollup [<label>=]<summary.json> [--rollup ...]) [--transcript <path>] [--since <iso>] [--markdown]";
605
+ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s), projectsRoot) {
606
+ let transcript;
607
+ let since;
608
+ let markdown = false;
609
+ const progressArgs = [];
610
+ const rollupArgs = [];
611
+ for (let i = 0;i < argv.length; i++) {
612
+ const arg = argv[i];
613
+ if (arg === "--transcript" && argv[i + 1])
614
+ transcript = argv[++i];
615
+ else if (arg === "--progress" && argv[i + 1])
616
+ progressArgs.push(argv[++i]);
617
+ else if (arg === "--rollup" && argv[i + 1])
618
+ rollupArgs.push(argv[++i]);
619
+ else if (arg === "--since" && argv[i + 1])
620
+ since = argv[++i];
621
+ else if (arg === "--markdown")
622
+ markdown = true;
623
+ else if (arg === "--spur-bin" && argv[i + 1])
624
+ i++;
625
+ else {
626
+ process.stderr.write(`${RUN_SUMMARY_USAGE}
627
+ `);
628
+ return 2;
629
+ }
630
+ }
631
+ if (rollupArgs.length > 0 && (progressArgs.length > 0 || since !== undefined || transcript !== undefined)) {
632
+ process.stderr.write(`${RUN_SUMMARY_USAGE}
633
+ `);
634
+ return 2;
635
+ }
636
+ if (rollupArgs.length > 0) {
637
+ try {
638
+ const inputs = rollupArgs.map((arg) => {
639
+ const eq = arg.indexOf("=");
640
+ const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
641
+ const summary = JSON.parse(readFileSync2(file, "utf8"));
642
+ return { label: label ?? summary.runId ?? file, summary };
643
+ });
644
+ write(`${renderRollupMarkdown(inputs)}
645
+ `);
646
+ return 0;
647
+ } catch (error) {
648
+ process.stderr.write(`run-summary: ${error.message}
649
+ `);
650
+ return 2;
651
+ }
652
+ }
653
+ if (!progressArgs.length) {
654
+ process.stderr.write(`${RUN_SUMMARY_USAGE}
655
+ `);
656
+ return 2;
657
+ }
658
+ const resolved = resolveTranscript2(env, projectsRoot, transcript);
659
+ if (!resolved.ok) {
660
+ const out = markdown ? `Execution summary: n/a (${resolved.reason})` : JSON.stringify({ available: false, reason: resolved.reason });
661
+ write(`${out}
662
+ `);
663
+ return 0;
664
+ }
665
+ try {
666
+ const inputs = progressArgs.map((arg) => {
667
+ const eq = arg.indexOf("=");
668
+ const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
669
+ return { label, progress: JSON.parse(readFileSync2(file, "utf8")) };
670
+ });
671
+ const summary = buildRunSummary(readFileSync2(resolved.path, "utf8").split(`
672
+ `), inputs, since);
673
+ summary.gate = gateFromReceipt(process.cwd(), inputs[0]?.progress.runId ?? "");
674
+ write(`${markdown ? renderSummaryMarkdown(summary) : JSON.stringify({ ...summary, transcript: resolved.path })}
675
+ `);
676
+ return 0;
677
+ } catch (error) {
678
+ process.stderr.write(`run-summary: ${error.message}
679
+ `);
680
+ return 2;
681
+ }
682
+ }
683
+ process.exit(main(process.argv.slice(2)));
684
+ export {
685
+ writeCloseSummary,
686
+ renderSummaryMarkdown,
687
+ renderRollupMarkdown,
688
+ main,
689
+ gateFromReceipt,
690
+ buildRunSummary,
691
+ actorSplit,
692
+ RUN_SUMMARY_USAGE
693
+ };