@gobing-ai/spur 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/config.global.yaml +4 -2
- 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/templates/AGENTS.md +1 -1
- 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 +107 -14
- package/package.json +10 -10
- package/plugins/sp/README.md +12 -3
- package/plugins/sp/agents/super-planner.md +16 -6
- 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-plan.md +6 -0
- package/plugins/sp/commands/dev-review-session.md +4 -1
- package/plugins/sp/commands/dev-run.md +12 -2
- package/plugins/sp/commands/dev-runall.md +32 -15
- package/plugins/sp/hooks/context-owner.ts +56 -0
- package/plugins/sp/hooks/context-post-tool.ts +23 -0
- package/plugins/sp/hooks/context-session-start.ts +49 -27
- package/plugins/sp/hooks/context-session-stop.ts +131 -30
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
- package/plugins/sp/lib/idea-handoff.generated.mjs +43 -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 +351 -46
- package/plugins/sp/lib/plan-projection.generated.d.mts +47 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +368 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +35 -1
- package/plugins/sp/lib/quality-gate.generated.mjs +30296 -71
- 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 +150 -7
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +488 -0
- package/plugins/sp/scripts/batch-plan.ts +145 -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 +562 -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/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +30213 -61
- 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 +432 -14
- package/plugins/sp/scripts/run-summary.ts +41 -177
- package/plugins/sp/scripts/session-timeline.mjs +109 -11
- package/plugins/sp/scripts/session-timeline.ts +57 -19
- package/plugins/sp/scripts/task-diffstat.mjs +32 -29
- package/plugins/sp/scripts/task-diffstat.ts +34 -34
- 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/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +40 -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 +16 -8
- package/plugins/sp/skills/spur-cli/references/message.md +7 -7
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- 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 +20 -14
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +33 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +49 -14
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +37 -17
- package/plugins/sp/skills/spur-dev/references/execution-batch-continuation.md +68 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +199 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +156 -1024
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +133 -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 +44 -12
- package/plugins/sp/skills/spur-dev/references/glossary.md +2 -2
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +340 -165
- 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 +42402 -26889
- package/web/_astro/{BoardApp.VeoxxLBy.js → BoardApp.B3GQZvj8.js} +1 -1
- package/web/_astro/BoardApp.BT7L2kSb.js +324 -0
- package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
- package/web/_astro/{arc.f4e0OYLg.js → arc.vaT7tPOB.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.SSIvxi-Z.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DSXIHOFf.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DG9Ubu85.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
- package/web/_astro/channel.D4nG9YLf.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.C7QU88Qh.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.26Z94xBj.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DYRiBkqw.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.D5Vfv5pe.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.DXHV47bN.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.D1suAfOs.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.D6BmdHEY.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.rnPU-Ak-.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.CvEfXIo1.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.CvEfXIo1.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DkMisLSm.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.C77Q-pF6.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.YaV8ce27.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.CUyGEKge.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.CRgd8dgj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.Bmzys9hq.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.BvmJY4Lw.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.0hEhD30d.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.Bjt8lrnm.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.CJZTjRDV.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7._VqoVafc.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CEDb7tVo.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
- package/web/_astro/index.BhSwN8yY.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.CgwifPpK.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.B99wx2J1.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.2hRwJbEo.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BahJkKNC.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
- package/web/_astro/{linear.DguUUe6V.js → linear.BzGnBwNL.js} +1 -1
- package/web/_astro/{mermaid.core.CvgfmqVr.js → mermaid.core.CSbTpIVz.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.B3qEA3x9.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.FlWA-_ru.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.9bVn35X7.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.ChykIhct.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.Z2Y3hkYb.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.J7JsHWDt.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.DAPbiCSS.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.D2jvSi0q.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.psW2ci29.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DlILYVmg.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.xNWwLJnn.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.EU_BuH7V.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/commands/dev-parallel.md +0 -31
- package/web/_astro/BoardApp.Bzr4OXrW.js +0 -324
- package/web/_astro/TaskDetail.LAyR_Pvn.js +0 -1
- 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,20 @@ function formatTokenSplit(t) {
|
|
|
35
40
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
36
41
|
}
|
|
37
42
|
function promptText(row) {
|
|
43
|
+
if (row.type === "custom_message") {
|
|
44
|
+
if (row.display === false)
|
|
45
|
+
return;
|
|
46
|
+
const text = typeof row.content === "string" ? row.content.trim() : "";
|
|
47
|
+
return text === "" ? undefined : text;
|
|
48
|
+
}
|
|
49
|
+
if (row.type === "message") {
|
|
50
|
+
if (row.message?.role !== "user")
|
|
51
|
+
return;
|
|
52
|
+
const text = firstTextContent(row);
|
|
53
|
+
if (text === undefined)
|
|
54
|
+
return;
|
|
55
|
+
return skillInvocationPrompt(text) ?? text;
|
|
56
|
+
}
|
|
38
57
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
39
58
|
return;
|
|
40
59
|
const content = row.message?.content;
|
|
@@ -44,6 +63,22 @@ function promptText(row) {
|
|
|
44
63
|
return;
|
|
45
64
|
return content.find((b) => b.type === "text")?.text;
|
|
46
65
|
}
|
|
66
|
+
function firstTextContent(row) {
|
|
67
|
+
const content = row.message?.content;
|
|
68
|
+
if (typeof content === "string")
|
|
69
|
+
return content;
|
|
70
|
+
if (!Array.isArray(content))
|
|
71
|
+
return;
|
|
72
|
+
return content.find((b) => b.type === "text")?.text;
|
|
73
|
+
}
|
|
74
|
+
function skillInvocationPrompt(text) {
|
|
75
|
+
const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
|
|
76
|
+
if (name === undefined)
|
|
77
|
+
return;
|
|
78
|
+
const close = text.lastIndexOf("</skill>");
|
|
79
|
+
const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
|
|
80
|
+
return args === "" ? `/${name}` : `/${name} ${args}`;
|
|
81
|
+
}
|
|
47
82
|
function sumTokens(all) {
|
|
48
83
|
const total = zeroTokens();
|
|
49
84
|
for (const t of all)
|
|
@@ -64,10 +99,37 @@ var newAcc = (ts) => ({
|
|
|
64
99
|
askMs: 0,
|
|
65
100
|
asks: new Map,
|
|
66
101
|
toolIds: new Set,
|
|
67
|
-
messages: new Map
|
|
102
|
+
messages: new Map,
|
|
103
|
+
compactions: 0
|
|
68
104
|
});
|
|
69
105
|
function accumulate(acc, row, ts) {
|
|
70
106
|
acc.last = Math.max(acc.last, ts);
|
|
107
|
+
if (row.type === "message") {
|
|
108
|
+
const m = row.message;
|
|
109
|
+
if (m?.role === "assistant") {
|
|
110
|
+
const blocks2 = Array.isArray(m.content) ? m.content : [];
|
|
111
|
+
for (const b of blocks2) {
|
|
112
|
+
if (b.type === "toolCall" && b.id) {
|
|
113
|
+
acc.toolIds.add(b.id);
|
|
114
|
+
if (b.name && OPERATOR_TOOLS.has(b.name))
|
|
115
|
+
acc.asks.set(b.id, ts);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const u2 = m.usage;
|
|
119
|
+
if (u2 && row.id)
|
|
120
|
+
acc.messages.set(row.id, {
|
|
121
|
+
input: u2.input ?? 0,
|
|
122
|
+
cacheCreate: u2.cacheWrite ?? 0,
|
|
123
|
+
cacheRead: u2.cacheRead ?? 0,
|
|
124
|
+
output: u2.output ?? 0
|
|
125
|
+
});
|
|
126
|
+
} else if (m?.role === "toolResult" && m.toolCallId && acc.asks.has(m.toolCallId)) {
|
|
127
|
+
acc.askMs += ts - (acc.asks.get(m.toolCallId) ?? ts);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (row.type === "compaction")
|
|
132
|
+
acc.compactions++;
|
|
71
133
|
const blocks = Array.isArray(row.message?.content) ? row.message.content : [];
|
|
72
134
|
for (const b of blocks) {
|
|
73
135
|
if (b.type === "tool_use" && b.id) {
|
|
@@ -88,6 +150,18 @@ function accumulate(acc, row, ts) {
|
|
|
88
150
|
});
|
|
89
151
|
}
|
|
90
152
|
}
|
|
153
|
+
function isCompaction(row) {
|
|
154
|
+
return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
|
|
155
|
+
}
|
|
156
|
+
function sniffFormat(rows) {
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
|
|
159
|
+
return "pi";
|
|
160
|
+
if (row.type === "user" || row.type === "assistant")
|
|
161
|
+
return "claude";
|
|
162
|
+
}
|
|
163
|
+
return "unknown";
|
|
164
|
+
}
|
|
91
165
|
function parseRows(lines) {
|
|
92
166
|
const rows = [];
|
|
93
167
|
let skippedLines = 0;
|
|
@@ -105,14 +179,22 @@ function parseRows(lines) {
|
|
|
105
179
|
if (!Number.isNaN(ts))
|
|
106
180
|
rows.push([row, ts]);
|
|
107
181
|
}
|
|
108
|
-
return { rows, skippedLines };
|
|
182
|
+
return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
|
|
109
183
|
}
|
|
110
184
|
function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
|
|
111
185
|
if (override)
|
|
112
186
|
return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
113
187
|
const id = env.CLAUDE_CODE_SESSION_ID;
|
|
114
|
-
if (!id)
|
|
115
|
-
|
|
188
|
+
if (!id) {
|
|
189
|
+
const piFile = env.PI_SESSION_FILE;
|
|
190
|
+
if (piFile !== undefined && piFile !== "") {
|
|
191
|
+
return existsSync(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
|
|
192
|
+
}
|
|
193
|
+
return {
|
|
194
|
+
ok: false,
|
|
195
|
+
reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
|
|
196
|
+
};
|
|
197
|
+
}
|
|
116
198
|
if (!SESSION_ID.test(id))
|
|
117
199
|
return { ok: false, reason: "refusing a session id with path characters" };
|
|
118
200
|
if (!existsSync(projectsRoot))
|
|
@@ -125,7 +207,151 @@ function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "proje
|
|
|
125
207
|
return { ok: false, reason: `no transcript for session ${id}` };
|
|
126
208
|
}
|
|
127
209
|
|
|
128
|
-
// plugins/sp/
|
|
210
|
+
// plugins/sp/lib/env.ts
|
|
211
|
+
function getEnvVars2() {
|
|
212
|
+
return process.env;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// plugins/sp/lib/run-summary-core.ts
|
|
216
|
+
var IDLE_GAP_MS = 300000;
|
|
217
|
+
var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
|
|
218
|
+
var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
|
|
219
|
+
var OPERATOR_TOOLS2 = new Set(["askuserquestion", "ask_user_question"]);
|
|
220
|
+
var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
|
|
221
|
+
function actorOf(tool) {
|
|
222
|
+
const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
|
|
223
|
+
if (OPERATOR_TOOLS2.has(t))
|
|
224
|
+
return "operator";
|
|
225
|
+
if (SUBAGENT_TOOLS.has(t))
|
|
226
|
+
return "subagent";
|
|
227
|
+
if (SHELL_TOOLS.has(t))
|
|
228
|
+
return "shell";
|
|
229
|
+
return "other";
|
|
230
|
+
}
|
|
231
|
+
function unionMs(spans, start, end) {
|
|
232
|
+
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]);
|
|
233
|
+
let total = 0;
|
|
234
|
+
let cursor = Number.NaN;
|
|
235
|
+
let openUntil = Number.NaN;
|
|
236
|
+
for (const [a, b] of clipped) {
|
|
237
|
+
if (!Number.isFinite(cursor)) {
|
|
238
|
+
cursor = a;
|
|
239
|
+
openUntil = b;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (a > openUntil) {
|
|
243
|
+
total += openUntil - cursor;
|
|
244
|
+
cursor = a;
|
|
245
|
+
openUntil = b;
|
|
246
|
+
} else {
|
|
247
|
+
openUntil = Math.max(openUntil, b);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (Number.isFinite(cursor))
|
|
251
|
+
total += openUntil - cursor;
|
|
252
|
+
return total;
|
|
253
|
+
}
|
|
254
|
+
function subtract(spans, claimed) {
|
|
255
|
+
let free = spans;
|
|
256
|
+
for (const [ca, cb] of claimed) {
|
|
257
|
+
const next = [];
|
|
258
|
+
for (const [a, b] of free) {
|
|
259
|
+
if (cb <= a || ca >= b) {
|
|
260
|
+
next.push([a, b]);
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (ca > a)
|
|
264
|
+
next.push([a, ca]);
|
|
265
|
+
if (cb < b)
|
|
266
|
+
next.push([cb, b]);
|
|
267
|
+
}
|
|
268
|
+
free = next;
|
|
269
|
+
}
|
|
270
|
+
return free;
|
|
271
|
+
}
|
|
272
|
+
function actorSplit(rows, start, end) {
|
|
273
|
+
const empty = {
|
|
274
|
+
operatorMs: 0,
|
|
275
|
+
longestOperatorWaitMs: 0,
|
|
276
|
+
modelMs: 0,
|
|
277
|
+
shellMs: 0,
|
|
278
|
+
subagentMs: 0,
|
|
279
|
+
otherToolMs: 0,
|
|
280
|
+
idleMs: 0,
|
|
281
|
+
compactions: 0,
|
|
282
|
+
subagentErrors: 0
|
|
283
|
+
};
|
|
284
|
+
const window = end - start;
|
|
285
|
+
if (window <= 0)
|
|
286
|
+
return empty;
|
|
287
|
+
const calls = new Map;
|
|
288
|
+
const results = new Map;
|
|
289
|
+
const eventTs = [];
|
|
290
|
+
let compactions = 0;
|
|
291
|
+
for (const [row, ts] of rows) {
|
|
292
|
+
if (ts < start || ts > end)
|
|
293
|
+
continue;
|
|
294
|
+
eventTs.push(ts);
|
|
295
|
+
if (isCompaction(row))
|
|
296
|
+
compactions++;
|
|
297
|
+
const blocks = Array.isArray(row.message?.content) ? row.message?.content : [];
|
|
298
|
+
if (row.message?.role === "toolResult" && row.message.toolCallId !== undefined) {
|
|
299
|
+
results.set(row.message.toolCallId, { ts, isError: row.message.isError === true });
|
|
300
|
+
}
|
|
301
|
+
for (const b of blocks) {
|
|
302
|
+
if ((b.type === "tool_use" || b.type === "toolCall") && b.id !== undefined && b.name !== undefined) {
|
|
303
|
+
calls.set(b.id, { id: b.id, actor: actorOf(b.name), ts });
|
|
304
|
+
} else if (b.type === "tool_result" && b.tool_use_id !== undefined) {
|
|
305
|
+
results.set(b.tool_use_id, { ts, isError: b.is_error === true });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
let subagentErrors = 0;
|
|
310
|
+
const resolved = [];
|
|
311
|
+
for (const call of calls.values()) {
|
|
312
|
+
const result = results.get(call.id);
|
|
313
|
+
if (result?.isError === true && call.actor === "subagent")
|
|
314
|
+
subagentErrors++;
|
|
315
|
+
resolved.push({ ...call, end: Math.min(result?.ts ?? end, end) });
|
|
316
|
+
}
|
|
317
|
+
const claimed = [];
|
|
318
|
+
const byActor = new Map;
|
|
319
|
+
for (const actor of ACTOR_PRECEDENCE) {
|
|
320
|
+
const mine = resolved.filter((r) => r.actor === actor).map((r) => [r.ts, r.end]);
|
|
321
|
+
const free = subtract(mine, claimed);
|
|
322
|
+
for (const span of free)
|
|
323
|
+
claimed.push(span);
|
|
324
|
+
const ms = unionMs(free, start, end);
|
|
325
|
+
byActor.set(actor, ms);
|
|
326
|
+
if (actor === "operator") {
|
|
327
|
+
empty.longestOperatorWaitMs = Math.max(0, ...mine.map(([a, b]) => b - a));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const boundaries = [...new Set([start, end, ...eventTs, ...claimed.flat()])].sort((a, b) => a - b);
|
|
331
|
+
const claimedSorted = [...claimed].sort((a, b) => a[0] - b[0]);
|
|
332
|
+
let idleMs = 0;
|
|
333
|
+
for (let i = 0;i < boundaries.length - 1; i++) {
|
|
334
|
+
const a = boundaries[i];
|
|
335
|
+
const b = boundaries[i + 1];
|
|
336
|
+
if (b - a < IDLE_GAP_MS)
|
|
337
|
+
continue;
|
|
338
|
+
if (claimedSorted.some(([ca, cb]) => ca < b && cb > a))
|
|
339
|
+
continue;
|
|
340
|
+
idleMs += b - a;
|
|
341
|
+
}
|
|
342
|
+
const accounted = (byActor.get("operator") ?? 0) + (byActor.get("subagent") ?? 0) + (byActor.get("shell") ?? 0) + (byActor.get("other") ?? 0) + idleMs;
|
|
343
|
+
return {
|
|
344
|
+
...empty,
|
|
345
|
+
operatorMs: byActor.get("operator") ?? 0,
|
|
346
|
+
subagentMs: byActor.get("subagent") ?? 0,
|
|
347
|
+
shellMs: byActor.get("shell") ?? 0,
|
|
348
|
+
otherToolMs: byActor.get("other") ?? 0,
|
|
349
|
+
idleMs,
|
|
350
|
+
modelMs: Math.max(0, window - accounted),
|
|
351
|
+
compactions,
|
|
352
|
+
subagentErrors
|
|
353
|
+
};
|
|
354
|
+
}
|
|
129
355
|
function attemptWindow(attempts) {
|
|
130
356
|
const starts = attempts.map((a) => Date.parse(a.startedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
131
357
|
const ends = attempts.map((a) => Date.parse(a.completedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
@@ -144,7 +370,7 @@ function summaryRow(stage, status, span, measured) {
|
|
|
144
370
|
token: formatTokenSplit(measured ? span.tokens : null)
|
|
145
371
|
};
|
|
146
372
|
}
|
|
147
|
-
function buildRunSummary(lines, inputs, since) {
|
|
373
|
+
function buildRunSummary(lines, inputs, since, until) {
|
|
148
374
|
const windows = [];
|
|
149
375
|
for (const { label, progress } of inputs) {
|
|
150
376
|
const states = (progress.states ?? []).flatMap((s) => {
|
|
@@ -192,6 +418,9 @@ function buildRunSummary(lines, inputs, since) {
|
|
|
192
418
|
if (ts >= sinceMs)
|
|
193
419
|
totalEnd = Math.max(totalEnd, ts);
|
|
194
420
|
}
|
|
421
|
+
const untilMs = until ? Date.parse(until) : Number.NaN;
|
|
422
|
+
if (!Number.isNaN(untilMs) && untilMs > totalStart)
|
|
423
|
+
totalEnd = untilMs;
|
|
195
424
|
const stageRows = windows.map((w) => {
|
|
196
425
|
const { span, measured } = measure(w.start, w.end);
|
|
197
426
|
return summaryRow(w.stage, w.status, span, measured);
|
|
@@ -209,33 +438,195 @@ function buildRunSummary(lines, inputs, since) {
|
|
|
209
438
|
};
|
|
210
439
|
if (!overlaps && overhead.workMs > 0)
|
|
211
440
|
stageRows.push(summaryRow("driver overhead", "", overhead, totalMeasure.measured));
|
|
212
|
-
return {
|
|
441
|
+
return {
|
|
442
|
+
available: true,
|
|
443
|
+
rows: stageRows,
|
|
444
|
+
total,
|
|
445
|
+
actors: actorSplit(rows, totalStart, totalEnd),
|
|
446
|
+
gate: { ms: null, reason: "no matching check receipt" },
|
|
447
|
+
skippedLines
|
|
448
|
+
};
|
|
213
449
|
}
|
|
214
450
|
function renderSummaryMarkdown(summary) {
|
|
215
451
|
const line = (r, bold = false) => {
|
|
216
452
|
const stage = bold ? `**${r.stage}**` : r.stage;
|
|
217
453
|
return `| ${stage} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} |`;
|
|
218
454
|
};
|
|
455
|
+
const a = summary.actors;
|
|
456
|
+
const window = summary.total.workMs + summary.total.waitMs;
|
|
457
|
+
const share = (ms) => window > 0 ? `${Math.round(ms / window * 100)}%` : "n/a";
|
|
458
|
+
const gate = summary.gate.ms === null ? `n/a (${summary.gate.reason})` : formatDuration(summary.gate.ms);
|
|
219
459
|
return [
|
|
220
460
|
"| Stage | Status | Time | Wait | Tool calls | Token (total / non-cached) |",
|
|
221
461
|
"| --- | --- | ---: | ---: | ---: | ---: |",
|
|
222
462
|
...summary.rows.map((r) => line(r)),
|
|
223
|
-
line(summary.total, true)
|
|
463
|
+
line(summary.total, true),
|
|
464
|
+
"",
|
|
465
|
+
"| Actor | Time | Share |",
|
|
466
|
+
"| --- | ---: | ---: |",
|
|
467
|
+
`| operator wait (longest ${formatDuration(a.longestOperatorWaitMs)}) | ${formatDuration(a.operatorMs)} | ${share(a.operatorMs)} |`,
|
|
468
|
+
`| model | ${formatDuration(a.modelMs)} | ${share(a.modelMs)} |`,
|
|
469
|
+
`| shell | ${formatDuration(a.shellMs)} | ${share(a.shellMs)} |`,
|
|
470
|
+
`| of which gate (informational) | ${gate} | — |`,
|
|
471
|
+
`| subagent | ${formatDuration(a.subagentMs)} | ${share(a.subagentMs)} |`,
|
|
472
|
+
`| other tools | ${formatDuration(a.otherToolMs)} | ${share(a.otherToolMs)} |`,
|
|
473
|
+
`| idle / unattributed | ${formatDuration(a.idleMs)} | ${share(a.idleMs)} |`,
|
|
474
|
+
"",
|
|
475
|
+
`Compactions: ${a.compactions} · Subagent errors: ${a.subagentErrors}`
|
|
224
476
|
].join(`
|
|
225
477
|
`);
|
|
226
478
|
}
|
|
227
|
-
|
|
479
|
+
function renderRollupMarkdown(rows) {
|
|
480
|
+
const line = (label, r, actors, bold = false) => {
|
|
481
|
+
const name = bold ? `**${label}**` : label;
|
|
482
|
+
return `| ${name} | ${r.status} | ${r.time} | ${r.wait} | ${r.toolCalls ?? "n/a"} | ${r.token} | ${formatDuration(actors.operatorMs)} | ${formatDuration(actors.subagentMs)} |`;
|
|
483
|
+
};
|
|
484
|
+
const total = rows.reduce((acc, { summary }) => ({
|
|
485
|
+
workMs: acc.workMs + summary.total.workMs,
|
|
486
|
+
waitMs: acc.waitMs + summary.total.waitMs,
|
|
487
|
+
toolCalls: (acc.toolCalls ?? 0) + (summary.total.toolCalls ?? 0),
|
|
488
|
+
tokens: sumTokens([acc.tokens, summary.total.tokens ?? zeroTokens()])
|
|
489
|
+
}), { workMs: 0, waitMs: 0, toolCalls: 0, tokens: zeroTokens() });
|
|
490
|
+
const totalRow = {
|
|
491
|
+
stage: "Batch total",
|
|
492
|
+
status: "",
|
|
493
|
+
workMs: total.workMs,
|
|
494
|
+
waitMs: total.waitMs,
|
|
495
|
+
toolCalls: total.toolCalls,
|
|
496
|
+
tokens: total.tokens,
|
|
497
|
+
time: formatDuration(total.workMs),
|
|
498
|
+
wait: formatDuration(total.waitMs),
|
|
499
|
+
token: formatTokenSplit(total.tokens)
|
|
500
|
+
};
|
|
501
|
+
const totalActors = {
|
|
502
|
+
operatorMs: 0,
|
|
503
|
+
longestOperatorWaitMs: 0,
|
|
504
|
+
modelMs: 0,
|
|
505
|
+
shellMs: 0,
|
|
506
|
+
subagentMs: 0,
|
|
507
|
+
otherToolMs: 0,
|
|
508
|
+
idleMs: 0,
|
|
509
|
+
compactions: 0,
|
|
510
|
+
subagentErrors: 0
|
|
511
|
+
};
|
|
512
|
+
for (const { summary } of rows) {
|
|
513
|
+
totalActors.operatorMs += summary.actors.operatorMs;
|
|
514
|
+
totalActors.subagentMs += summary.actors.subagentMs;
|
|
515
|
+
totalActors.compactions += summary.actors.compactions;
|
|
516
|
+
totalActors.subagentErrors += summary.actors.subagentErrors;
|
|
517
|
+
}
|
|
518
|
+
const out = [
|
|
519
|
+
"| Run | Status | Time | Wait | Tool calls | Token (total / non-cached) | Operator wait | Subagent |",
|
|
520
|
+
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
521
|
+
...rows.map(({ label, summary }) => line(label, summary.total, summary.actors)),
|
|
522
|
+
line("Batch total", totalRow, totalActors, true)
|
|
523
|
+
];
|
|
524
|
+
const starts = rows.map((r) => Date.parse(r.summary.startedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
525
|
+
const ends = rows.map((r) => Date.parse(r.summary.completedAt ?? "")).filter((n) => !Number.isNaN(n));
|
|
526
|
+
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 ?? "")));
|
|
527
|
+
if (overlaps && starts.length > 0 && ends.length > 0) {
|
|
528
|
+
out.push("", `Wall span: ${new Date(Math.min(...starts)).toISOString()} → ${new Date(Math.max(...ends)).toISOString()}`);
|
|
529
|
+
}
|
|
530
|
+
return out.join(`
|
|
531
|
+
`);
|
|
532
|
+
}
|
|
533
|
+
function writeCloseSummary(run, opts = {}) {
|
|
534
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
535
|
+
const runDir = join2(cwd, ".spur", "run");
|
|
536
|
+
const rel = join2(".spur", "run", `${run.runId}-summary.md`);
|
|
537
|
+
const abs = join2(cwd, rel);
|
|
538
|
+
const na = (reason) => {
|
|
539
|
+
mkdirSync(runDir, { recursive: true });
|
|
540
|
+
writeFileSync(abs, `Execution summary: n/a (${reason})
|
|
541
|
+
`);
|
|
542
|
+
return rel;
|
|
543
|
+
};
|
|
544
|
+
try {
|
|
545
|
+
if (run.startedAt === undefined)
|
|
546
|
+
return na("run row has no started_at");
|
|
547
|
+
const resolved = resolveTranscript(opts.env ?? getEnvVars2(), opts.projectsRoot);
|
|
548
|
+
if (!resolved.ok)
|
|
549
|
+
return na(resolved.reason);
|
|
550
|
+
const lines = readFileSync(resolved.path, "utf8").split(`
|
|
551
|
+
`);
|
|
552
|
+
const summary = buildRunSummary(lines, [{ progress: run.progress }], run.startedAt, run.completedAt);
|
|
553
|
+
if (summary.rows.length === 0)
|
|
554
|
+
return na("no timed attempts");
|
|
555
|
+
mkdirSync(runDir, { recursive: true });
|
|
556
|
+
writeFileSync(abs, `${renderSummaryMarkdown(summary)}
|
|
557
|
+
`);
|
|
558
|
+
writeFileSync(join2(runDir, `${run.runId}-summary.json`), JSON.stringify({ ...summary, runId: run.runId, startedAt: run.startedAt, completedAt: run.completedAt }, null, 2));
|
|
559
|
+
return rel;
|
|
560
|
+
} catch (error) {
|
|
561
|
+
return na(error instanceof Error ? error.message : String(error));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function gateFromReceipt(cwd, runId) {
|
|
565
|
+
const path = join2(cwd, ".spur", "run", `${runId}-check-receipt.json`);
|
|
566
|
+
if (!existsSync2(path))
|
|
567
|
+
return { ms: null, reason: "no matching check receipt" };
|
|
568
|
+
try {
|
|
569
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
570
|
+
if (parsed.runId !== runId)
|
|
571
|
+
return { ms: null, reason: "check receipt belongs to another run" };
|
|
572
|
+
const ms = parsed.gateRuntimeMs;
|
|
573
|
+
if (typeof ms !== "number" || !Number.isFinite(ms))
|
|
574
|
+
return { ms: null, reason: "check receipt has no gate time" };
|
|
575
|
+
return { ms };
|
|
576
|
+
} catch {
|
|
577
|
+
return { ms: null, reason: "check receipt is unreadable" };
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// plugins/sp/lib/transcript.ts
|
|
582
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
583
|
+
import { homedir as homedir2 } from "node:os";
|
|
584
|
+
import { join as join3 } from "node:path";
|
|
585
|
+
var SESSION_ID2 = /^[A-Za-z0-9_-]+$/;
|
|
586
|
+
var OPERATOR_TOOLS3 = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
587
|
+
var PI_ROLES2 = new Set(["user", "assistant", "toolResult"]);
|
|
588
|
+
function resolveTranscript2(env, projectsRoot = join3(homedir2(), ".claude", "projects"), override) {
|
|
589
|
+
if (override)
|
|
590
|
+
return existsSync3(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
591
|
+
const id = env.CLAUDE_CODE_SESSION_ID;
|
|
592
|
+
if (!id) {
|
|
593
|
+
const piFile = env.PI_SESSION_FILE;
|
|
594
|
+
if (piFile !== undefined && piFile !== "") {
|
|
595
|
+
return existsSync3(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
ok: false,
|
|
599
|
+
reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
if (!SESSION_ID2.test(id))
|
|
603
|
+
return { ok: false, reason: "refusing a session id with path characters" };
|
|
604
|
+
if (!existsSync3(projectsRoot))
|
|
605
|
+
return { ok: false, reason: `no transcript root ${projectsRoot}` };
|
|
606
|
+
for (const dir of readdirSync2(projectsRoot)) {
|
|
607
|
+
const path = join3(projectsRoot, dir, `${id}.jsonl`);
|
|
608
|
+
if (existsSync3(path))
|
|
609
|
+
return { ok: true, path };
|
|
610
|
+
}
|
|
611
|
+
return { ok: false, reason: `no transcript for session ${id}` };
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// plugins/sp/scripts/run-summary.ts
|
|
615
|
+
var RUN_SUMMARY_USAGE = "usage: run-summary (--progress [<label>=]<file> [--progress ...] | --rollup [<label>=]<summary.json> [--rollup ...]) [--transcript <path>] [--since <iso>] [--markdown]";
|
|
228
616
|
function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s), projectsRoot) {
|
|
229
617
|
let transcript;
|
|
230
618
|
let since;
|
|
231
619
|
let markdown = false;
|
|
232
620
|
const progressArgs = [];
|
|
621
|
+
const rollupArgs = [];
|
|
233
622
|
for (let i = 0;i < argv.length; i++) {
|
|
234
623
|
const arg = argv[i];
|
|
235
624
|
if (arg === "--transcript" && argv[i + 1])
|
|
236
625
|
transcript = argv[++i];
|
|
237
626
|
else if (arg === "--progress" && argv[i + 1])
|
|
238
627
|
progressArgs.push(argv[++i]);
|
|
628
|
+
else if (arg === "--rollup" && argv[i + 1])
|
|
629
|
+
rollupArgs.push(argv[++i]);
|
|
239
630
|
else if (arg === "--since" && argv[i + 1])
|
|
240
631
|
since = argv[++i];
|
|
241
632
|
else if (arg === "--markdown")
|
|
@@ -244,6 +635,28 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
244
635
|
i++;
|
|
245
636
|
else {
|
|
246
637
|
process.stderr.write(`${RUN_SUMMARY_USAGE}
|
|
638
|
+
`);
|
|
639
|
+
return 2;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (rollupArgs.length > 0 && (progressArgs.length > 0 || since !== undefined || transcript !== undefined)) {
|
|
643
|
+
process.stderr.write(`${RUN_SUMMARY_USAGE}
|
|
644
|
+
`);
|
|
645
|
+
return 2;
|
|
646
|
+
}
|
|
647
|
+
if (rollupArgs.length > 0) {
|
|
648
|
+
try {
|
|
649
|
+
const inputs = rollupArgs.map((arg) => {
|
|
650
|
+
const eq = arg.indexOf("=");
|
|
651
|
+
const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
|
|
652
|
+
const summary = JSON.parse(readFileSync2(file, "utf8"));
|
|
653
|
+
return { label: label ?? summary.runId ?? file, summary };
|
|
654
|
+
});
|
|
655
|
+
write(`${renderRollupMarkdown(inputs)}
|
|
656
|
+
`);
|
|
657
|
+
return 0;
|
|
658
|
+
} catch (error) {
|
|
659
|
+
process.stderr.write(`run-summary: ${error.message}
|
|
247
660
|
`);
|
|
248
661
|
return 2;
|
|
249
662
|
}
|
|
@@ -253,7 +666,7 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
253
666
|
`);
|
|
254
667
|
return 2;
|
|
255
668
|
}
|
|
256
|
-
const resolved =
|
|
669
|
+
const resolved = resolveTranscript2(env, projectsRoot, transcript);
|
|
257
670
|
if (!resolved.ok) {
|
|
258
671
|
const out = markdown ? `Execution summary: n/a (${resolved.reason})` : JSON.stringify({ available: false, reason: resolved.reason });
|
|
259
672
|
write(`${out}
|
|
@@ -264,10 +677,11 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
264
677
|
const inputs = progressArgs.map((arg) => {
|
|
265
678
|
const eq = arg.indexOf("=");
|
|
266
679
|
const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
|
|
267
|
-
return { label, progress: JSON.parse(
|
|
680
|
+
return { label, progress: JSON.parse(readFileSync2(file, "utf8")) };
|
|
268
681
|
});
|
|
269
|
-
const summary = buildRunSummary(
|
|
682
|
+
const summary = buildRunSummary(readFileSync2(resolved.path, "utf8").split(`
|
|
270
683
|
`), inputs, since);
|
|
684
|
+
summary.gate = gateFromReceipt(process.cwd(), inputs[0]?.progress.runId ?? "");
|
|
271
685
|
write(`${markdown ? renderSummaryMarkdown(summary) : JSON.stringify({ ...summary, transcript: resolved.path })}
|
|
272
686
|
`);
|
|
273
687
|
return 0;
|
|
@@ -279,8 +693,12 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
279
693
|
}
|
|
280
694
|
process.exit(main(process.argv.slice(2)));
|
|
281
695
|
export {
|
|
696
|
+
writeCloseSummary,
|
|
282
697
|
renderSummaryMarkdown,
|
|
698
|
+
renderRollupMarkdown,
|
|
283
699
|
main,
|
|
700
|
+
gateFromReceipt,
|
|
284
701
|
buildRunSummary,
|
|
702
|
+
actorSplit,
|
|
285
703
|
RUN_SUMMARY_USAGE
|
|
286
704
|
};
|