@gobing-ai/spur 0.4.0 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/decisions/gates.yaml +36 -0
- package/config/decisions/history-anatomy.yaml +35 -0
- package/config/decisions/idea-pipeline.yaml +38 -0
- package/config/plugin-scripts.json +15 -0
- package/config/rules/docs/sourced-api-claims.yaml +60 -0
- package/config/rules/recommended-pre-check.yaml +8 -3
- package/config/rules/structure/scratch-evidence-pointer.yaml +35 -0
- package/config/script-placement-baseline.json +2 -2
- package/config/workflows/feature-lifecycle.yaml +9 -2
- package/config/workflows/history-anatomy.yaml +22 -0
- package/config/workflows/idea-pipeline.yaml +75 -0
- package/config/workflows/task-pipeline.yaml +216 -57
- package/config/workflows/wrapup-pipeline.yaml +106 -13
- package/package.json +9 -9
- package/plugins/sp/README.md +11 -1
- package/plugins/sp/agents/super-planner.md +11 -0
- package/plugins/sp/agents/super-reviewer.md +14 -0
- package/plugins/sp/bunfig.toml +8 -0
- package/plugins/sp/commands/dev-brainstorm.md +29 -16
- package/plugins/sp/commands/dev-find-way.md +80 -0
- package/plugins/sp/commands/dev-idea.md +6 -0
- package/plugins/sp/commands/dev-parallel.md +8 -2
- package/plugins/sp/commands/dev-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +3 -1
- package/plugins/sp/commands/dev-run.md +12 -2
- package/plugins/sp/commands/dev-runall.md +18 -10
- package/plugins/sp/hooks/context-session-start.ts +1 -1
- package/plugins/sp/lib/idea-handoff.generated.mjs +41 -5
- package/plugins/sp/lib/inline-run-app.ts +28 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +5 -0
- package/plugins/sp/lib/inline-run.generated.mjs +333 -45
- package/plugins/sp/lib/plan-projection.generated.d.mts +28 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +255 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +47 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30489 -82
- package/plugins/sp/lib/residual-scan.generated.d.mts +19 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +66 -0
- package/plugins/sp/lib/run-summary-core.ts +561 -0
- package/plugins/sp/lib/transcript.ts +121 -6
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +335 -0
- package/plugins/sp/scripts/batch-plan.ts +100 -0
- package/plugins/sp/scripts/batch-preflight.mjs +51 -3
- package/plugins/sp/scripts/batch-preflight.ts +72 -1
- package/plugins/sp/scripts/commit-guard.mjs +216 -0
- package/plugins/sp/scripts/commit-guard.ts +249 -0
- package/plugins/sp/scripts/inline-run-dispatch.mjs +130 -0
- package/plugins/sp/scripts/inline-run-dispatch.ts +135 -0
- package/plugins/sp/scripts/inline-run-setup.mjs +551 -80
- package/plugins/sp/scripts/inline-run-setup.ts +52 -78
- package/plugins/sp/scripts/persist-out-check.mjs +90 -41
- package/plugins/sp/scripts/persist-out-check.ts +112 -57
- package/plugins/sp/scripts/quality-gate.mjs +30407 -73
- package/plugins/sp/scripts/quality-gate.ts +21 -5
- package/plugins/sp/scripts/residual-scan.mjs +86 -31
- package/plugins/sp/scripts/residual-scan.ts +28 -30
- package/plugins/sp/scripts/run-summary.mjs +421 -14
- package/plugins/sp/scripts/run-summary.ts +41 -177
- package/plugins/sp/scripts/session-timeline.mjs +109 -12
- package/plugins/sp/scripts/session-timeline.ts +71 -19
- package/plugins/sp/scripts/wrapup-steps.mjs +150 -2
- package/plugins/sp/scripts/wrapup-steps.ts +203 -1
- package/plugins/sp/skills/brainstorm/SKILL.md +9 -47
- package/plugins/sp/skills/branch-workflow/SKILL.md +1 -0
- package/plugins/sp/skills/branch-workflow/references/worktree-patterns.md +1 -0
- package/plugins/sp/skills/code-implementation/SKILL.md +48 -1
- package/plugins/sp/skills/code-verification/SKILL.md +3 -4
- package/plugins/sp/skills/code-verification/references/secu-review.md +3 -0
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +68 -0
- package/plugins/sp/skills/fleet-join/SKILL.md +1 -1
- package/plugins/sp/skills/session-review/SKILL.md +38 -7
- package/plugins/sp/skills/source-driven-development/SKILL.md +5 -0
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +5 -3
- package/plugins/sp/skills/spur-cli/SKILL.md +5 -2
- package/plugins/sp/skills/spur-cli/references/agent.md +20 -15
- package/plugins/sp/skills/spur-cli/references/decision.md +7 -6
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +19 -3
- package/plugins/sp/skills/spur-cli/references/tasks.md +10 -7
- package/plugins/sp/skills/spur-cli/references/workflows/authoring-workflows.md +7 -0
- package/plugins/sp/skills/spur-cli/references/workflows.md +7 -2
- package/plugins/sp/skills/spur-composer/SKILL.md +1 -1
- package/plugins/sp/skills/spur-dev/SKILL.md +18 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +6 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -16
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +194 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +113 -1020
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +130 -0
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +6 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +471 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +379 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +35 -5
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +343 -163
- package/plugins/sp/skills/spur-dev/references/structured-trace-emission.md +178 -0
- package/plugins/sp/skills/wayfinder/SKILL.md +10 -14
- package/schemas/state-machine-workflow.schema.json +23 -0
- package/spur.js +20728 -16630
- package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.D66k_s_T.js} +1 -1
- package/web/_astro/{BoardApp.Bzr4OXrW.js → BoardApp.D6sjAJr_.js} +173 -173
- package/web/_astro/{TaskDetail.LAyR_Pvn.js → TaskDetail.BynLnMNc.js} +1 -1
- package/web/_astro/{arc.f4e0OYLg.js → arc._BUadrdH.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.Dh7xS1Pp.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.DFDVpfXm.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.V7_3_glh.js} +1 -1
- package/web/_astro/channel.DZVfto-r.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.De1yx7Jz.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.BDhHFuxx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.DDLkWnmC.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.CwfiLnA7.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.CtGDmUnM.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.BOLjpw73.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BDhQls6O.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.76qBynDF.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.N0mpPHT0.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.N0mpPHT0.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.CKknFfjB.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.Cw3ZWBAh.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.D3yIKCRD.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.D51dO83Z.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.Bb-FViDj.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.D3mN8S6x.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.dMMyPcbJ.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.AzbvzY4C.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.BR5I4OZb.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.DFqvhGkn.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.ByJbhH6J.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.CInHjQR3.js} +1 -1
- package/web/_astro/index.COh_kBxN.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.Cm0dGVjJ.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.4IvLa2MW.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DS87Cl-k.js} +1 -1
- package/web/_astro/{linear.DguUUe6V.js → linear.MzmRrNER.js} +1 -1
- package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.fK-WNswS.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.BqipOHhY.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.0nBiPqFG.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.Szj7Zpx5.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.D4UR6TWo.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.DAE7BdOs.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.BP9ZVq25.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.BQW7mIxk.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.PbK4nDih.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.Ci_GvUpo.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.DNw1Y5qx.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.CwlkaaNe.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.MI6ZuiH_.js} +1 -1
- package/web/index.html +2 -2
- package/web/_astro/channel.CUFdtWhm.js +0 -1
- package/web/_astro/index.Dg_3bOaJ.css +0 -1
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// plugins/sp/scripts/run-summary.ts
|
|
5
|
-
import { readFileSync } from "fs";
|
|
5
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
6
6
|
|
|
7
7
|
// plugins/sp/lib/env.ts
|
|
8
8
|
function getEnvVars() {
|
|
9
9
|
return process.env;
|
|
10
10
|
}
|
|
11
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
|
+
|
|
12
16
|
// plugins/sp/lib/transcript.ts
|
|
13
17
|
import { existsSync, readdirSync } from "node:fs";
|
|
14
18
|
import { homedir } from "node:os";
|
|
15
19
|
import { join } from "node:path";
|
|
16
20
|
var SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
17
|
-
var OPERATOR_TOOLS = new Set(["AskUserQuestion"]);
|
|
21
|
+
var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
22
|
+
var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
|
|
18
23
|
var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
|
|
19
24
|
function formatDuration(ms) {
|
|
20
25
|
const s = Math.round(ms / 1000);
|
|
@@ -35,6 +40,18 @@ function formatTokenSplit(t) {
|
|
|
35
40
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
36
41
|
}
|
|
37
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
|
+
}
|
|
38
55
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
39
56
|
return;
|
|
40
57
|
const content = row.message?.content;
|
|
@@ -44,6 +61,13 @@ function promptText(row) {
|
|
|
44
61
|
return;
|
|
45
62
|
return content.find((b) => b.type === "text")?.text;
|
|
46
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
|
+
}
|
|
47
71
|
function sumTokens(all) {
|
|
48
72
|
const total = zeroTokens();
|
|
49
73
|
for (const t of all)
|
|
@@ -64,10 +88,37 @@ var newAcc = (ts) => ({
|
|
|
64
88
|
askMs: 0,
|
|
65
89
|
asks: new Map,
|
|
66
90
|
toolIds: new Set,
|
|
67
|
-
messages: new Map
|
|
91
|
+
messages: new Map,
|
|
92
|
+
compactions: 0
|
|
68
93
|
});
|
|
69
94
|
function accumulate(acc, row, ts) {
|
|
70
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++;
|
|
71
122
|
const blocks = Array.isArray(row.message?.content) ? row.message.content : [];
|
|
72
123
|
for (const b of blocks) {
|
|
73
124
|
if (b.type === "tool_use" && b.id) {
|
|
@@ -88,6 +139,18 @@ function accumulate(acc, row, ts) {
|
|
|
88
139
|
});
|
|
89
140
|
}
|
|
90
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
|
+
}
|
|
91
154
|
function parseRows(lines) {
|
|
92
155
|
const rows = [];
|
|
93
156
|
let skippedLines = 0;
|
|
@@ -105,14 +168,22 @@ function parseRows(lines) {
|
|
|
105
168
|
if (!Number.isNaN(ts))
|
|
106
169
|
rows.push([row, ts]);
|
|
107
170
|
}
|
|
108
|
-
return { rows, skippedLines };
|
|
171
|
+
return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
|
|
109
172
|
}
|
|
110
173
|
function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
|
|
111
174
|
if (override)
|
|
112
175
|
return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
113
176
|
const id = env.CLAUDE_CODE_SESSION_ID;
|
|
114
|
-
if (!id)
|
|
115
|
-
|
|
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
|
+
}
|
|
116
187
|
if (!SESSION_ID.test(id))
|
|
117
188
|
return { ok: false, reason: "refusing a session id with path characters" };
|
|
118
189
|
if (!existsSync(projectsRoot))
|
|
@@ -125,7 +196,151 @@ function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "proje
|
|
|
125
196
|
return { ok: false, reason: `no transcript for session ${id}` };
|
|
126
197
|
}
|
|
127
198
|
|
|
128
|
-
// plugins/sp/
|
|
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
|
+
}
|
|
129
344
|
function attemptWindow(attempts) {
|
|
130
345
|
const starts = attempts.map((a) => Date.parse(a.startedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
131
346
|
const ends = attempts.map((a) => Date.parse(a.completedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
@@ -144,7 +359,7 @@ function summaryRow(stage, status, span, measured) {
|
|
|
144
359
|
token: formatTokenSplit(measured ? span.tokens : null)
|
|
145
360
|
};
|
|
146
361
|
}
|
|
147
|
-
function buildRunSummary(lines, inputs, since) {
|
|
362
|
+
function buildRunSummary(lines, inputs, since, until) {
|
|
148
363
|
const windows = [];
|
|
149
364
|
for (const { label, progress } of inputs) {
|
|
150
365
|
const states = (progress.states ?? []).flatMap((s) => {
|
|
@@ -192,6 +407,9 @@ function buildRunSummary(lines, inputs, since) {
|
|
|
192
407
|
if (ts >= sinceMs)
|
|
193
408
|
totalEnd = Math.max(totalEnd, ts);
|
|
194
409
|
}
|
|
410
|
+
const untilMs = until ? Date.parse(until) : Number.NaN;
|
|
411
|
+
if (!Number.isNaN(untilMs) && untilMs > totalStart)
|
|
412
|
+
totalEnd = untilMs;
|
|
195
413
|
const stageRows = windows.map((w) => {
|
|
196
414
|
const { span, measured } = measure(w.start, w.end);
|
|
197
415
|
return summaryRow(w.stage, w.status, span, measured);
|
|
@@ -209,33 +427,195 @@ function buildRunSummary(lines, inputs, since) {
|
|
|
209
427
|
};
|
|
210
428
|
if (!overlaps && overhead.workMs > 0)
|
|
211
429
|
stageRows.push(summaryRow("driver overhead", "", overhead, totalMeasure.measured));
|
|
212
|
-
return {
|
|
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
|
+
};
|
|
213
438
|
}
|
|
214
439
|
function renderSummaryMarkdown(summary) {
|
|
215
440
|
const line = (r, bold = false) => {
|
|
216
441
|
const stage = bold ? `**${r.stage}**` : r.stage;
|
|
217
442
|
return `| ${stage} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} |`;
|
|
218
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);
|
|
219
448
|
return [
|
|
220
449
|
"| Stage | Status | Time | Wait | Tool calls | Token (total / non-cached) |",
|
|
221
450
|
"| --- | --- | ---: | ---: | ---: | ---: |",
|
|
222
451
|
...summary.rows.map((r) => line(r)),
|
|
223
|
-
line(summary.total, true)
|
|
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}`
|
|
224
465
|
].join(`
|
|
225
466
|
`);
|
|
226
467
|
}
|
|
227
|
-
|
|
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]";
|
|
228
605
|
function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s), projectsRoot) {
|
|
229
606
|
let transcript;
|
|
230
607
|
let since;
|
|
231
608
|
let markdown = false;
|
|
232
609
|
const progressArgs = [];
|
|
610
|
+
const rollupArgs = [];
|
|
233
611
|
for (let i = 0;i < argv.length; i++) {
|
|
234
612
|
const arg = argv[i];
|
|
235
613
|
if (arg === "--transcript" && argv[i + 1])
|
|
236
614
|
transcript = argv[++i];
|
|
237
615
|
else if (arg === "--progress" && argv[i + 1])
|
|
238
616
|
progressArgs.push(argv[++i]);
|
|
617
|
+
else if (arg === "--rollup" && argv[i + 1])
|
|
618
|
+
rollupArgs.push(argv[++i]);
|
|
239
619
|
else if (arg === "--since" && argv[i + 1])
|
|
240
620
|
since = argv[++i];
|
|
241
621
|
else if (arg === "--markdown")
|
|
@@ -244,6 +624,28 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
244
624
|
i++;
|
|
245
625
|
else {
|
|
246
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}
|
|
247
649
|
`);
|
|
248
650
|
return 2;
|
|
249
651
|
}
|
|
@@ -253,7 +655,7 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
253
655
|
`);
|
|
254
656
|
return 2;
|
|
255
657
|
}
|
|
256
|
-
const resolved =
|
|
658
|
+
const resolved = resolveTranscript2(env, projectsRoot, transcript);
|
|
257
659
|
if (!resolved.ok) {
|
|
258
660
|
const out = markdown ? `Execution summary: n/a (${resolved.reason})` : JSON.stringify({ available: false, reason: resolved.reason });
|
|
259
661
|
write(`${out}
|
|
@@ -264,10 +666,11 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
264
666
|
const inputs = progressArgs.map((arg) => {
|
|
265
667
|
const eq = arg.indexOf("=");
|
|
266
668
|
const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
|
|
267
|
-
return { label, progress: JSON.parse(
|
|
669
|
+
return { label, progress: JSON.parse(readFileSync2(file, "utf8")) };
|
|
268
670
|
});
|
|
269
|
-
const summary = buildRunSummary(
|
|
671
|
+
const summary = buildRunSummary(readFileSync2(resolved.path, "utf8").split(`
|
|
270
672
|
`), inputs, since);
|
|
673
|
+
summary.gate = gateFromReceipt(process.cwd(), inputs[0]?.progress.runId ?? "");
|
|
271
674
|
write(`${markdown ? renderSummaryMarkdown(summary) : JSON.stringify({ ...summary, transcript: resolved.path })}
|
|
272
675
|
`);
|
|
273
676
|
return 0;
|
|
@@ -279,8 +682,12 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
279
682
|
}
|
|
280
683
|
process.exit(main(process.argv.slice(2)));
|
|
281
684
|
export {
|
|
685
|
+
writeCloseSummary,
|
|
282
686
|
renderSummaryMarkdown,
|
|
687
|
+
renderRollupMarkdown,
|
|
283
688
|
main,
|
|
689
|
+
gateFromReceipt,
|
|
284
690
|
buildRunSummary,
|
|
691
|
+
actorSplit,
|
|
285
692
|
RUN_SUMMARY_USAGE
|
|
286
693
|
};
|