@gobing-ai/spur 0.4.1 → 0.4.3
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/maker-liveness.yaml +39 -0
- package/config/plugin-scripts.json +0 -4
- package/config/rules/README.md +1 -0
- package/config/rules/docs/sourced-api-claims.yaml +56 -12
- package/config/rules/strict/runtime-boundaries.yaml +7 -0
- package/config/script-placement-baseline.json +1 -19
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/feature-lifecycle.yaml +46 -1
- package/config/workflows/feature-verification.yaml +11 -0
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +10 -10
- package/plugins/sp/README.md +23 -18
- package/plugins/sp/agents/super-planner.md +5 -6
- package/plugins/sp/agents/super-reviewer.md +43 -0
- package/plugins/sp/bunfig.toml +8 -5
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/commands/dev-runall.md +18 -9
- package/plugins/sp/hooks/careful-guard.ts +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +7 -404
- package/plugins/sp/hooks/context-session-start.ts +6 -192
- package/plugins/sp/hooks/context-session-stop.ts +6 -91
- package/plugins/sp/hooks/pi/guard-extension.ts +26 -12
- package/plugins/sp/hooks/task-write-guard.ts +1 -1
- package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
- package/plugins/sp/lib/context-owner.ts +56 -0
- package/plugins/sp/lib/context-post-tool.ts +432 -0
- package/plugins/sp/lib/context-session-start.ts +214 -0
- package/plugins/sp/lib/context-session-stop.ts +196 -0
- package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
- package/plugins/sp/lib/dogfood-report.ts +278 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +6 -4
- package/plugins/sp/lib/inline-run-app.ts +40 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
- package/plugins/sp/lib/inline-run.generated.mjs +1361 -87
- package/plugins/sp/lib/plan-projection.generated.d.mts +169 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +704 -1
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
- package/plugins/sp/lib/quality-gate.generated.mjs +62 -231
- package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
- package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
- package/plugins/sp/lib/run-summary-core.ts +34 -370
- package/plugins/sp/lib/transcript.ts +37 -200
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +740 -1
- package/plugins/sp/scripts/batch-plan.ts +48 -3
- package/plugins/sp/scripts/batch-preflight.mjs +23 -1
- package/plugins/sp/scripts/batch-preflight.ts +35 -295
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
- package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
- package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
- package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
- package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
- package/plugins/sp/scripts/inline-run-setup.mjs +158 -120
- package/plugins/sp/scripts/inline-run-setup.ts +1 -21
- 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 +38 -208
- package/plugins/sp/scripts/residual-scan.mjs +73 -6
- package/plugins/sp/scripts/residual-scan.ts +8 -5
- package/plugins/sp/scripts/run-summary.mjs +160 -95
- package/plugins/sp/scripts/run-summary.ts +7 -5
- package/plugins/sp/scripts/session-timeline.mjs +57 -29
- package/plugins/sp/scripts/session-timeline.ts +4 -18
- package/plugins/sp/scripts/task-diffstat.mjs +43 -30
- package/plugins/sp/scripts/task-diffstat.ts +40 -71
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/next-feature/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +2 -2
- 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 +5 -3
- package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
- package/plugins/sp/skills/spur-cli/references/decision.md +40 -8
- package/plugins/sp/skills/spur-cli/references/features.md +46 -5
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +6 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +56 -7
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +37 -25
- package/spur.js +89792 -76992
- package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.5IIl2O-z.js} +1 -1
- package/web/_astro/BoardApp.Drnx-KeM.js +324 -0
- package/web/_astro/TaskDetail.Ci4LNn9S.js +1 -0
- package/web/_astro/{arc._BUadrdH.js → arc.j2EVkYVd.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
- package/web/_astro/channel.DmdDxGsq.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
- package/web/_astro/index.Do2lLdgo.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
- package/web/_astro/{linear.MzmRrNER.js → linear.CI6djNi7.js} +1 -1
- package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CuQPxS8b.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
- package/web/index.html +2 -2
- package/config/proportional-route-table.ts +0 -155
- package/plugins/sp/commands/dev-parallel.md +0 -37
- package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
- package/web/_astro/BoardApp.D6sjAJr_.js +0 -324
- package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
- package/web/_astro/channel.DZVfto-r.js +0 -1
- package/web/_astro/index.COh_kBxN.css +0 -1
- /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
- /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
|
@@ -13,13 +13,16 @@ function getEnvVars() {
|
|
|
13
13
|
import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
14
14
|
import { join as join2 } from "node:path";
|
|
15
15
|
|
|
16
|
-
// plugins/sp/lib/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
var
|
|
16
|
+
// plugins/sp/lib/env.ts
|
|
17
|
+
function getEnvVars2() {
|
|
18
|
+
return process.env;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// plugins/sp/lib/plan-projection.generated.mjs
|
|
22
|
+
var NON_ABORTING_PREFLIGHT_CODES = new Set([
|
|
23
|
+
"L4.scenario-unverified",
|
|
24
|
+
"L4.verifying-incomplete-tasks"
|
|
25
|
+
]);
|
|
23
26
|
var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
|
|
24
27
|
function formatDuration(ms) {
|
|
25
28
|
const s = Math.round(ms / 1000);
|
|
@@ -39,18 +42,40 @@ function formatTokenSplit(t) {
|
|
|
39
42
|
const nonCached = t.input + t.cacheCreate + t.output;
|
|
40
43
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
41
44
|
}
|
|
45
|
+
var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
46
|
+
function isCompaction(row) {
|
|
47
|
+
return row.type === "compaction" || row.type === "system" && row.subtype === "compact_boundary";
|
|
48
|
+
}
|
|
49
|
+
function firstTextContent(row) {
|
|
50
|
+
const content = row.message?.content;
|
|
51
|
+
if (typeof content === "string")
|
|
52
|
+
return content;
|
|
53
|
+
if (!Array.isArray(content))
|
|
54
|
+
return;
|
|
55
|
+
return content.find((b) => b.type === "text")?.text;
|
|
56
|
+
}
|
|
57
|
+
function skillInvocationPrompt(text) {
|
|
58
|
+
const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
|
|
59
|
+
if (name === undefined)
|
|
60
|
+
return;
|
|
61
|
+
const close = text.lastIndexOf("</skill>");
|
|
62
|
+
const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
|
|
63
|
+
return args === "" ? `/${name}` : `/${name} ${args}`;
|
|
64
|
+
}
|
|
42
65
|
function promptText(row) {
|
|
66
|
+
if (row.type === "custom_message") {
|
|
67
|
+
if (row.display === false)
|
|
68
|
+
return;
|
|
69
|
+
const text = typeof row.content === "string" ? row.content.trim() : "";
|
|
70
|
+
return text === "" ? undefined : text;
|
|
71
|
+
}
|
|
43
72
|
if (row.type === "message") {
|
|
44
73
|
if (row.message?.role !== "user")
|
|
45
74
|
return;
|
|
46
|
-
|
|
75
|
+
const text = firstTextContent(row);
|
|
76
|
+
if (text === undefined)
|
|
47
77
|
return;
|
|
48
|
-
|
|
49
|
-
if (typeof content2 === "string")
|
|
50
|
-
return content2;
|
|
51
|
-
if (!Array.isArray(content2))
|
|
52
|
-
return;
|
|
53
|
-
return content2.find((b) => b.type === "text")?.text;
|
|
78
|
+
return skillInvocationPrompt(text) ?? text;
|
|
54
79
|
}
|
|
55
80
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
56
81
|
return;
|
|
@@ -61,13 +86,6 @@ function promptText(row) {
|
|
|
61
86
|
return;
|
|
62
87
|
return content.find((b) => b.type === "text")?.text;
|
|
63
88
|
}
|
|
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
89
|
function sumTokens(all) {
|
|
72
90
|
const total = zeroTokens();
|
|
73
91
|
for (const t of all)
|
|
@@ -139,77 +157,14 @@ function accumulate(acc, row, ts) {
|
|
|
139
157
|
});
|
|
140
158
|
}
|
|
141
159
|
}
|
|
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
160
|
var IDLE_GAP_MS = 300000;
|
|
206
161
|
var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
|
|
207
162
|
var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
|
|
208
|
-
var
|
|
163
|
+
var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
|
|
209
164
|
var ACTOR_PRECEDENCE = ["operator", "subagent", "shell", "other"];
|
|
210
165
|
function actorOf(tool) {
|
|
211
166
|
const t = tool.toLowerCase().replace(/[^a-z_]/g, "");
|
|
212
|
-
if (
|
|
167
|
+
if (LOWERCASE_OPERATOR_TOOLS.has(t))
|
|
213
168
|
return "operator";
|
|
214
169
|
if (SUBAGENT_TOOLS.has(t))
|
|
215
170
|
return "subagent";
|
|
@@ -359,7 +314,8 @@ function summaryRow(stage, status, span, measured) {
|
|
|
359
314
|
token: formatTokenSplit(measured ? span.tokens : null)
|
|
360
315
|
};
|
|
361
316
|
}
|
|
362
|
-
function buildRunSummary(
|
|
317
|
+
function buildRunSummary(rows, inputs, opts = {}) {
|
|
318
|
+
const { since, until } = opts;
|
|
363
319
|
const windows = [];
|
|
364
320
|
for (const { label, progress } of inputs) {
|
|
365
321
|
const states = (progress.states ?? []).flatMap((s) => {
|
|
@@ -385,7 +341,6 @@ function buildRunSummary(lines, inputs, since, until) {
|
|
|
385
341
|
}
|
|
386
342
|
}
|
|
387
343
|
windows.sort((a, b) => a.start - b.start);
|
|
388
|
-
const { rows, skippedLines } = parseRows(lines);
|
|
389
344
|
const measure = (start, end) => {
|
|
390
345
|
const acc = newAcc(start);
|
|
391
346
|
for (const [row, ts] of rows)
|
|
@@ -433,9 +388,87 @@ function buildRunSummary(lines, inputs, since, until) {
|
|
|
433
388
|
total,
|
|
434
389
|
actors: actorSplit(rows, totalStart, totalEnd),
|
|
435
390
|
gate: { ms: null, reason: "no matching check receipt" },
|
|
436
|
-
skippedLines
|
|
391
|
+
skippedLines: opts.skippedLines ?? 0
|
|
437
392
|
};
|
|
438
393
|
}
|
|
394
|
+
var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
|
|
395
|
+
var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
|
|
396
|
+
function globToRegex(glob) {
|
|
397
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
|
|
398
|
+
return new RegExp(`^${escaped}$`, "s");
|
|
399
|
+
}
|
|
400
|
+
var SENSITIVE_MATCHERS = [
|
|
401
|
+
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
402
|
+
label: prefix,
|
|
403
|
+
test: (path) => path.startsWith(prefix)
|
|
404
|
+
})),
|
|
405
|
+
...SENSITIVE_GLOBS.map((glob) => {
|
|
406
|
+
const regex = globToRegex(glob);
|
|
407
|
+
return { label: glob, test: (path) => regex.test(path) };
|
|
408
|
+
})
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
// plugins/sp/lib/transcript.ts
|
|
412
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
413
|
+
import { homedir } from "node:os";
|
|
414
|
+
import { join } from "node:path";
|
|
415
|
+
var SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
416
|
+
var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
|
|
417
|
+
function sniffFormat(rows) {
|
|
418
|
+
for (const row of rows) {
|
|
419
|
+
if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
|
|
420
|
+
return "pi";
|
|
421
|
+
if (row.type === "user" || row.type === "assistant")
|
|
422
|
+
return "claude";
|
|
423
|
+
}
|
|
424
|
+
return "unknown";
|
|
425
|
+
}
|
|
426
|
+
function parseRows(lines) {
|
|
427
|
+
const rows = [];
|
|
428
|
+
let skippedLines = 0;
|
|
429
|
+
for (const line of lines) {
|
|
430
|
+
if (!line.trim())
|
|
431
|
+
continue;
|
|
432
|
+
let row;
|
|
433
|
+
try {
|
|
434
|
+
row = JSON.parse(line);
|
|
435
|
+
} catch {
|
|
436
|
+
skippedLines++;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
|
|
440
|
+
if (!Number.isNaN(ts))
|
|
441
|
+
rows.push([row, ts]);
|
|
442
|
+
}
|
|
443
|
+
return { rows, skippedLines, format: sniffFormat(rows.map(([r]) => r)) };
|
|
444
|
+
}
|
|
445
|
+
function resolveTranscript(env, projectsRoot = join(homedir(), ".claude", "projects"), override) {
|
|
446
|
+
if (override)
|
|
447
|
+
return existsSync(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
448
|
+
const id = env.CLAUDE_CODE_SESSION_ID;
|
|
449
|
+
if (!id) {
|
|
450
|
+
const piFile = env.PI_SESSION_FILE;
|
|
451
|
+
if (piFile !== undefined && piFile !== "") {
|
|
452
|
+
return existsSync(piFile) ? { ok: true, path: piFile } : { ok: false, reason: `PI_SESSION_FILE ${piFile} does not exist` };
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
ok: false,
|
|
456
|
+
reason: "no host session id; pass --transcript <path> (pi: ~/.pi/agent/sessions/<cwd-slug>/<file>.jsonl, " + "or set PI_SESSION_FILE)"
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (!SESSION_ID.test(id))
|
|
460
|
+
return { ok: false, reason: "refusing a session id with path characters" };
|
|
461
|
+
if (!existsSync(projectsRoot))
|
|
462
|
+
return { ok: false, reason: `no transcript root ${projectsRoot}` };
|
|
463
|
+
for (const dir of readdirSync(projectsRoot)) {
|
|
464
|
+
const path = join(projectsRoot, dir, `${id}.jsonl`);
|
|
465
|
+
if (existsSync(path))
|
|
466
|
+
return { ok: true, path };
|
|
467
|
+
}
|
|
468
|
+
return { ok: false, reason: `no transcript for session ${id}` };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// plugins/sp/lib/run-summary-core.ts
|
|
439
472
|
function renderSummaryMarkdown(summary) {
|
|
440
473
|
const line = (r, bold = false) => {
|
|
441
474
|
const stage = bold ? `**${r.stage}**` : r.stage;
|
|
@@ -536,9 +569,13 @@ function writeCloseSummary(run, opts = {}) {
|
|
|
536
569
|
const resolved = resolveTranscript(opts.env ?? getEnvVars2(), opts.projectsRoot);
|
|
537
570
|
if (!resolved.ok)
|
|
538
571
|
return na(resolved.reason);
|
|
539
|
-
const
|
|
540
|
-
`);
|
|
541
|
-
const summary = buildRunSummary(
|
|
572
|
+
const { rows, skippedLines } = parseRows(readFileSync(resolved.path, "utf8").split(`
|
|
573
|
+
`));
|
|
574
|
+
const summary = buildRunSummary(rows, [{ progress: run.progress }], {
|
|
575
|
+
since: run.startedAt,
|
|
576
|
+
until: run.completedAt,
|
|
577
|
+
skippedLines
|
|
578
|
+
});
|
|
542
579
|
if (summary.rows.length === 0)
|
|
543
580
|
return na("no timed attempts");
|
|
544
581
|
mkdirSync(runDir, { recursive: true });
|
|
@@ -572,8 +609,35 @@ import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs"
|
|
|
572
609
|
import { homedir as homedir2 } from "node:os";
|
|
573
610
|
import { join as join3 } from "node:path";
|
|
574
611
|
var SESSION_ID2 = /^[A-Za-z0-9_-]+$/;
|
|
575
|
-
var OPERATOR_TOOLS3 = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
576
612
|
var PI_ROLES2 = new Set(["user", "assistant", "toolResult"]);
|
|
613
|
+
function sniffFormat2(rows) {
|
|
614
|
+
for (const row of rows) {
|
|
615
|
+
if (row.type === "message" && PI_ROLES2.has(row.message?.role ?? ""))
|
|
616
|
+
return "pi";
|
|
617
|
+
if (row.type === "user" || row.type === "assistant")
|
|
618
|
+
return "claude";
|
|
619
|
+
}
|
|
620
|
+
return "unknown";
|
|
621
|
+
}
|
|
622
|
+
function parseRows2(lines) {
|
|
623
|
+
const rows = [];
|
|
624
|
+
let skippedLines = 0;
|
|
625
|
+
for (const line of lines) {
|
|
626
|
+
if (!line.trim())
|
|
627
|
+
continue;
|
|
628
|
+
let row;
|
|
629
|
+
try {
|
|
630
|
+
row = JSON.parse(line);
|
|
631
|
+
} catch {
|
|
632
|
+
skippedLines++;
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
const ts = row.timestamp ? Date.parse(row.timestamp) : Number.NaN;
|
|
636
|
+
if (!Number.isNaN(ts))
|
|
637
|
+
rows.push([row, ts]);
|
|
638
|
+
}
|
|
639
|
+
return { rows, skippedLines, format: sniffFormat2(rows.map(([r]) => r)) };
|
|
640
|
+
}
|
|
577
641
|
function resolveTranscript2(env, projectsRoot = join3(homedir2(), ".claude", "projects"), override) {
|
|
578
642
|
if (override)
|
|
579
643
|
return existsSync3(override) ? { ok: true, path: override } : { ok: false, reason: "no transcript" };
|
|
@@ -668,8 +732,9 @@ function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s),
|
|
|
668
732
|
const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
|
|
669
733
|
return { label, progress: JSON.parse(readFileSync2(file, "utf8")) };
|
|
670
734
|
});
|
|
671
|
-
const
|
|
672
|
-
`)
|
|
735
|
+
const { rows, skippedLines } = parseRows2(readFileSync2(resolved.path, "utf8").split(`
|
|
736
|
+
`));
|
|
737
|
+
const summary = buildRunSummary(rows, inputs, { since, skippedLines });
|
|
673
738
|
summary.gate = gateFromReceipt(process.cwd(), inputs[0]?.progress.runId ?? "");
|
|
674
739
|
write(`${markdown ? renderSummaryMarkdown(summary) : JSON.stringify({ ...summary, transcript: resolved.path })}
|
|
675
740
|
`);
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* (`--no-summary` skips it). Inputs: one or more `spur workflow progress <run-id> --json` files
|
|
5
5
|
* (`--progress [<label>=]<file>`) and the host transcript (resolved like session-timeline, or
|
|
6
6
|
* `--transcript`), OR the close-written per-run summary files (`--rollup [<label>=]<file>`, 1146 R3).
|
|
7
|
-
* Measurement
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Measurement lives in `packages/app/src/workflow/run-summary.ts` (reached through the generated
|
|
8
|
+
* pure bundle entry); presentation and host IO live in `../lib/run-summary-core` (ADR-130 glue
|
|
9
|
+
* budget). This script is argv parsing and dispatch. Reporting only: no transcript or no timed
|
|
10
|
+
* attempts → an `n/a` result, exit 0. Node builtins only.
|
|
10
11
|
*/
|
|
11
12
|
import { readFileSync } from 'node:fs';
|
|
12
13
|
import { getEnvVars } from '../lib/env';
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
renderRollupMarkdown,
|
|
19
20
|
renderSummaryMarkdown,
|
|
20
21
|
} from '../lib/run-summary-core';
|
|
21
|
-
import { resolveTranscript } from '../lib/transcript';
|
|
22
|
+
import { parseRows, resolveTranscript } from '../lib/transcript';
|
|
22
23
|
|
|
23
24
|
// Re-exported so the suite and any consumer keep one import path for the whole surface.
|
|
24
25
|
export * from '../lib/run-summary-core';
|
|
@@ -90,7 +91,8 @@ export function main(
|
|
|
90
91
|
const [label, file] = eq > 0 ? [arg.slice(0, eq), arg.slice(eq + 1)] : [undefined, arg];
|
|
91
92
|
return { label, progress: JSON.parse(readFileSync(file, 'utf8')) as Progress };
|
|
92
93
|
});
|
|
93
|
-
const
|
|
94
|
+
const { rows, skippedLines } = parseRows(readFileSync(resolved.path, 'utf8').split('\n'));
|
|
95
|
+
const summary = buildRunSummary(rows, inputs, { since, skippedLines });
|
|
94
96
|
summary.gate = gateFromReceipt(process.cwd(), inputs[0]?.progress.runId ?? '');
|
|
95
97
|
write(
|
|
96
98
|
`${markdown ? renderSummaryMarkdown(summary) : JSON.stringify({ ...summary, transcript: resolved.path })}\n`,
|
|
@@ -13,9 +13,12 @@ function getEnvVars() {
|
|
|
13
13
|
import { existsSync, readdirSync } from "node:fs";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
var
|
|
16
|
+
|
|
17
|
+
// plugins/sp/lib/plan-projection.generated.mjs
|
|
18
|
+
var NON_ABORTING_PREFLIGHT_CODES = new Set([
|
|
19
|
+
"L4.scenario-unverified",
|
|
20
|
+
"L4.verifying-incomplete-tasks"
|
|
21
|
+
]);
|
|
19
22
|
var zeroTokens = () => ({ input: 0, cacheCreate: 0, cacheRead: 0, output: 0 });
|
|
20
23
|
function formatDuration(ms) {
|
|
21
24
|
const s = Math.round(ms / 1000);
|
|
@@ -35,18 +38,37 @@ function formatTokenSplit(t) {
|
|
|
35
38
|
const nonCached = t.input + t.cacheCreate + t.output;
|
|
36
39
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
37
40
|
}
|
|
41
|
+
var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
42
|
+
function firstTextContent(row) {
|
|
43
|
+
const content = row.message?.content;
|
|
44
|
+
if (typeof content === "string")
|
|
45
|
+
return content;
|
|
46
|
+
if (!Array.isArray(content))
|
|
47
|
+
return;
|
|
48
|
+
return content.find((b) => b.type === "text")?.text;
|
|
49
|
+
}
|
|
50
|
+
function skillInvocationPrompt(text) {
|
|
51
|
+
const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
|
|
52
|
+
if (name === undefined)
|
|
53
|
+
return;
|
|
54
|
+
const close = text.lastIndexOf("</skill>");
|
|
55
|
+
const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
|
|
56
|
+
return args === "" ? `/${name}` : `/${name} ${args}`;
|
|
57
|
+
}
|
|
38
58
|
function promptText(row) {
|
|
59
|
+
if (row.type === "custom_message") {
|
|
60
|
+
if (row.display === false)
|
|
61
|
+
return;
|
|
62
|
+
const text = typeof row.content === "string" ? row.content.trim() : "";
|
|
63
|
+
return text === "" ? undefined : text;
|
|
64
|
+
}
|
|
39
65
|
if (row.type === "message") {
|
|
40
66
|
if (row.message?.role !== "user")
|
|
41
67
|
return;
|
|
42
|
-
|
|
68
|
+
const text = firstTextContent(row);
|
|
69
|
+
if (text === undefined)
|
|
43
70
|
return;
|
|
44
|
-
|
|
45
|
-
if (typeof content2 === "string")
|
|
46
|
-
return content2;
|
|
47
|
-
if (!Array.isArray(content2))
|
|
48
|
-
return;
|
|
49
|
-
return content2.find((b) => b.type === "text")?.text;
|
|
71
|
+
return skillInvocationPrompt(text) ?? text;
|
|
50
72
|
}
|
|
51
73
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
52
74
|
return;
|
|
@@ -57,13 +79,6 @@ function promptText(row) {
|
|
|
57
79
|
return;
|
|
58
80
|
return content.find((b) => b.type === "text")?.text;
|
|
59
81
|
}
|
|
60
|
-
function isInjectedPrompt(row) {
|
|
61
|
-
if (row.type !== "message" || row.message?.role !== "user")
|
|
62
|
-
return false;
|
|
63
|
-
const content = row.message.content;
|
|
64
|
-
const text = typeof content === "string" ? content : Array.isArray(content) ? content.find((b) => b.type === "text")?.text : undefined;
|
|
65
|
-
return typeof text === "string" && text.replace(/^\s+/, "").startsWith('<skill name="');
|
|
66
|
-
}
|
|
67
82
|
function sumTokens(all) {
|
|
68
83
|
const total = zeroTokens();
|
|
69
84
|
for (const t of all)
|
|
@@ -135,6 +150,29 @@ function accumulate(acc, row, ts) {
|
|
|
135
150
|
});
|
|
136
151
|
}
|
|
137
152
|
}
|
|
153
|
+
var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
|
|
154
|
+
var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
|
|
155
|
+
var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
|
|
156
|
+
var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
|
|
157
|
+
var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
|
|
158
|
+
function globToRegex(glob) {
|
|
159
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
|
|
160
|
+
return new RegExp(`^${escaped}$`, "s");
|
|
161
|
+
}
|
|
162
|
+
var SENSITIVE_MATCHERS = [
|
|
163
|
+
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
164
|
+
label: prefix,
|
|
165
|
+
test: (path) => path.startsWith(prefix)
|
|
166
|
+
})),
|
|
167
|
+
...SENSITIVE_GLOBS.map((glob) => {
|
|
168
|
+
const regex = globToRegex(glob);
|
|
169
|
+
return { label: glob, test: (path) => regex.test(path) };
|
|
170
|
+
})
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
// plugins/sp/lib/transcript.ts
|
|
174
|
+
var SESSION_ID = /^[A-Za-z0-9_-]+$/;
|
|
175
|
+
var PI_ROLES = new Set(["user", "assistant", "toolResult"]);
|
|
138
176
|
function sniffFormat(rows) {
|
|
139
177
|
for (const row of rows) {
|
|
140
178
|
if (row.type === "message" && PI_ROLES.has(row.message?.role ?? ""))
|
|
@@ -212,15 +250,7 @@ function buildTimeline(lines, group) {
|
|
|
212
250
|
const open = [];
|
|
213
251
|
const { rows, skippedLines, format } = parseRows(lines);
|
|
214
252
|
const isPi = format === "pi";
|
|
215
|
-
let injectedPrompts = 0;
|
|
216
253
|
for (const [row, ts] of rows) {
|
|
217
|
-
if (isPi && isInjectedPrompt(row)) {
|
|
218
|
-
injectedPrompts++;
|
|
219
|
-
const injectedSeg = open.at(-1);
|
|
220
|
-
if (injectedSeg)
|
|
221
|
-
accumulate(injectedSeg, row, ts);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
254
|
const prompt = promptText(row);
|
|
225
255
|
if (prompt !== undefined) {
|
|
226
256
|
open.push({ start: ts, prompt, ...newAcc(ts) });
|
|
@@ -253,8 +283,7 @@ function buildTimeline(lines, group) {
|
|
|
253
283
|
available: true,
|
|
254
284
|
segments,
|
|
255
285
|
totals: render(isPi ? { ...totals, compactions: segments.reduce((n, s) => n + (s.compactions ?? 0), 0) } : totals),
|
|
256
|
-
skippedLines
|
|
257
|
-
...isPi ? { injectedPrompts } : {}
|
|
286
|
+
skippedLines
|
|
258
287
|
};
|
|
259
288
|
if (group) {
|
|
260
289
|
timeline.stages = parseGroups(group, segments.length).map(([a, b]) => render({ ...sumSpans(segments.slice(a - 1, b)), segments: a === b ? `${a}` : `${a}-${b}` }));
|
|
@@ -273,8 +302,7 @@ function zeroSegmentReason(rows) {
|
|
|
273
302
|
const census = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 5).map(([kind, n]) => `${kind} ${n}`).join(", ");
|
|
274
303
|
return `unrecognized transcript format (row types: ${census})`;
|
|
275
304
|
}
|
|
276
|
-
|
|
277
|
-
return `${format} transcript with no operator prompts (${rows.length} rows, ${injected} injected)`;
|
|
305
|
+
return `${format} transcript with no operator prompts (${rows.length} rows)`;
|
|
278
306
|
}
|
|
279
307
|
function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s), projectsRoot, writeErr = (s) => process.stderr.write(s)) {
|
|
280
308
|
let transcript;
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
accumulate,
|
|
17
17
|
formatDuration,
|
|
18
18
|
formatTokenSplit,
|
|
19
|
-
isInjectedPrompt,
|
|
20
19
|
newAcc,
|
|
21
20
|
parseRows,
|
|
22
21
|
promptText,
|
|
@@ -40,8 +39,6 @@ export interface Timeline {
|
|
|
40
39
|
stages?: Rendered<Span & { segments: string }>[];
|
|
41
40
|
totals: Rendered<Span & { elapsedMs: number; elapsed: string; compactions?: number }>;
|
|
42
41
|
skippedLines: number;
|
|
43
|
-
/** pi only (1138 R2): skill-injected user bodies counted as activity, never as segments. */
|
|
44
|
-
injectedPrompts?: number;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
const PROMPT_EXCERPT = 80;
|
|
@@ -75,17 +72,8 @@ export function buildTimeline(lines: string[], group?: string): Timeline {
|
|
|
75
72
|
const open: Open[] = [];
|
|
76
73
|
const { rows, skippedLines, format } = parseRows(lines);
|
|
77
74
|
const isPi = format === 'pi';
|
|
78
|
-
let injectedPrompts = 0;
|
|
79
75
|
|
|
80
76
|
for (const [row, ts] of rows) {
|
|
81
|
-
// 1138 R2: an injected skill body is activity, not a prompt — accumulate it into the open
|
|
82
|
-
// segment (its tools and usage are real work) and count it, never open a segment.
|
|
83
|
-
if (isPi && isInjectedPrompt(row)) {
|
|
84
|
-
injectedPrompts++;
|
|
85
|
-
const injectedSeg = open.at(-1);
|
|
86
|
-
if (injectedSeg) accumulate(injectedSeg, row, ts);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
77
|
const prompt = promptText(row);
|
|
90
78
|
if (prompt !== undefined) {
|
|
91
79
|
open.push({ start: ts, prompt, ...newAcc(ts) });
|
|
@@ -124,8 +112,6 @@ export function buildTimeline(lines: string[], group?: string): Timeline {
|
|
|
124
112
|
isPi ? { ...totals, compactions: segments.reduce((n, s) => n + (s.compactions ?? 0), 0) } : totals,
|
|
125
113
|
),
|
|
126
114
|
skippedLines,
|
|
127
|
-
// Emitted only for pi, as `compactions` is, so Claude output is unchanged (1138 R2).
|
|
128
|
-
...(isPi ? { injectedPrompts } : {}),
|
|
129
115
|
};
|
|
130
116
|
if (group) {
|
|
131
117
|
timeline.stages = parseGroups(group, segments.length).map(([a, b]) =>
|
|
@@ -139,8 +125,9 @@ export const SESSION_TIMELINE_USAGE = 'usage: session-timeline [--transcript <pa
|
|
|
139
125
|
|
|
140
126
|
/**
|
|
141
127
|
* 1138 R3: a zero-segment result must say WHAT was detected, so an unparseable-but-recognised
|
|
142
|
-
* transcript can never read as an authoritative all-zero timeline. A known format names the
|
|
143
|
-
*
|
|
128
|
+
* transcript can never read as an authoritative all-zero timeline. A known format names the row
|
|
129
|
+
* count; an unknown one lists the top-5 row-type census. (1151 R2 removed the pi-only injected
|
|
130
|
+
* count: pi injects no skill bodies, so a wrapper row is an operator prompt and the class is gone.)
|
|
144
131
|
*/
|
|
145
132
|
export function zeroSegmentReason(rows: [Row, number][]): string {
|
|
146
133
|
const format = sniffFormat(rows.map(([r]) => r));
|
|
@@ -157,8 +144,7 @@ export function zeroSegmentReason(rows: [Row, number][]): string {
|
|
|
157
144
|
.join(', ');
|
|
158
145
|
return `unrecognized transcript format (row types: ${census})`;
|
|
159
146
|
}
|
|
160
|
-
|
|
161
|
-
return `${format} transcript with no operator prompts (${rows.length} rows, ${injected} injected)`;
|
|
147
|
+
return `${format} transcript with no operator prompts (${rows.length} rows)`;
|
|
162
148
|
}
|
|
163
149
|
|
|
164
150
|
export function main(
|