@schoolai/shipyard 3.18.2 → 3.18.3-nightly.20260701.0
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/dist/capability-detector-worker.js +4 -4
- package/dist/{chunk-BK24XTBR.js → chunk-AFSAYFD2.js} +2 -3
- package/dist/{chunk-BK24XTBR.js.map → chunk-AFSAYFD2.js.map} +1 -1
- package/dist/{chunk-UQ2COP52.js → chunk-AX4RM7CP.js} +4 -1
- package/dist/chunk-AX4RM7CP.js.map +1 -0
- package/dist/{chunk-XXRTRTG4.js → chunk-CBLCALKE.js} +2 -2
- package/dist/{chunk-E74MXZWW.js → chunk-D4PUXGZA.js} +2 -2
- package/dist/{chunk-3GIE6EPP.js → chunk-FEJCW4CK.js} +2 -2
- package/dist/{chunk-WVPA2YD3.js → chunk-J6COEZIM.js} +3 -3
- package/dist/{chunk-LN2AW36U.js → chunk-RWG7XGPH.js} +43 -4
- package/dist/chunk-RWG7XGPH.js.map +1 -0
- package/dist/{chunk-PXZCSRS5.js → chunk-UUOHTXO7.js} +2 -2
- package/dist/{chunk-PXZCSRS5.js.map → chunk-UUOHTXO7.js.map} +1 -1
- package/dist/{chunk-5GY7WGIC.js → chunk-W6MC7ULT.js} +2 -2
- package/dist/cursor-runner.js +3 -3
- package/dist/electron-utility.js +2 -2
- package/dist/index.js +5 -5
- package/dist/{login-4AUKCCVX.js → login-VVEVIMRY.js} +3 -3
- package/dist/{plan-backfill-X4FGEJ4G.js → plan-backfill-6YRSHNXU.js} +4 -4
- package/dist/{roi-WN6ZLKUG.js → roi-O3LOQ37N.js} +25 -3
- package/dist/roi-O3LOQ37N.js.map +1 -0
- package/dist/{serve-7Z6TPUUN.js → serve-BHBP3NUA.js} +55 -18
- package/dist/{serve-7Z6TPUUN.js.map → serve-BHBP3NUA.js.map} +1 -1
- package/dist/{start-TJ5MEU6A.js → start-PEUWCTXE.js} +6 -6
- package/package.json +2 -2
- package/dist/chunk-LN2AW36U.js.map +0 -1
- package/dist/chunk-UQ2COP52.js.map +0 -1
- package/dist/roi-WN6ZLKUG.js.map +0 -1
- /package/dist/{chunk-XXRTRTG4.js.map → chunk-CBLCALKE.js.map} +0 -0
- /package/dist/{chunk-E74MXZWW.js.map → chunk-D4PUXGZA.js.map} +0 -0
- /package/dist/{chunk-3GIE6EPP.js.map → chunk-FEJCW4CK.js.map} +0 -0
- /package/dist/{chunk-WVPA2YD3.js.map → chunk-J6COEZIM.js.map} +0 -0
- /package/dist/{chunk-5GY7WGIC.js.map → chunk-W6MC7ULT.js.map} +0 -0
- /package/dist/{login-4AUKCCVX.js.map → login-VVEVIMRY.js.map} +0 -0
- /package/dist/{plan-backfill-X4FGEJ4G.js.map → plan-backfill-6YRSHNXU.js.map} +0 -0
- /package/dist/{start-TJ5MEU6A.js.map → start-PEUWCTXE.js.map} +0 -0
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
parseShipyardDeliverableUrl,
|
|
31
31
|
planEditorSerializer,
|
|
32
32
|
toTaskIndexEntry
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-AFSAYFD2.js";
|
|
34
34
|
import {
|
|
35
35
|
assertNever
|
|
36
36
|
} from "./chunk-X3MULCV5.js";
|
|
@@ -34719,4 +34719,4 @@ export {
|
|
|
34719
34719
|
runPlanBackfill,
|
|
34720
34720
|
buildTaskStateStore
|
|
34721
34721
|
};
|
|
34722
|
-
//# sourceMappingURL=chunk-
|
|
34722
|
+
//# sourceMappingURL=chunk-W6MC7ULT.js.map
|
package/dist/cursor-runner.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-CKQL7TWQ.js";
|
|
5
5
|
import {
|
|
6
6
|
buildCursorUserPrompt
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-D4PUXGZA.js";
|
|
8
8
|
import {
|
|
9
9
|
ContentBlockSchema,
|
|
10
10
|
HARNESS_SERVER_NAME
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import "./chunk-
|
|
11
|
+
} from "./chunk-AFSAYFD2.js";
|
|
12
|
+
import "./chunk-UUOHTXO7.js";
|
|
13
13
|
import "./chunk-EHQITHQX.js";
|
|
14
14
|
import "./chunk-X3MULCV5.js";
|
|
15
15
|
import "./chunk-X53VIV2J.js";
|
package/dist/electron-utility.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
getDaemonVersion
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-J6COEZIM.js";
|
|
5
5
|
import {
|
|
6
6
|
enableNativeCrashReports
|
|
7
7
|
} from "./chunk-TSJOSKN3.js";
|
|
@@ -71,7 +71,7 @@ async function main() {
|
|
|
71
71
|
await loadAuthFromConfig(env);
|
|
72
72
|
crumb("post-auth-load");
|
|
73
73
|
crumb("pre-serve-import");
|
|
74
|
-
const { serve } = await import("./serve-
|
|
74
|
+
const { serve } = await import("./serve-BHBP3NUA.js");
|
|
75
75
|
crumb("post-serve-import");
|
|
76
76
|
const portQueue = [];
|
|
77
77
|
let acceptor = null;
|
package/dist/index.js
CHANGED
|
@@ -118,7 +118,7 @@ async function loadAuthFromConfig(env) {
|
|
|
118
118
|
async function handleSubcommand() {
|
|
119
119
|
const subcommand = process.argv[2];
|
|
120
120
|
if (subcommand === "login") {
|
|
121
|
-
const { loginCommand } = await import("./login-
|
|
121
|
+
const { loginCommand } = await import("./login-VVEVIMRY.js");
|
|
122
122
|
const hasCheck = process.argv.includes("--check");
|
|
123
123
|
await loginCommand({ check: hasCheck });
|
|
124
124
|
return true;
|
|
@@ -129,17 +129,17 @@ async function handleSubcommand() {
|
|
|
129
129
|
return true;
|
|
130
130
|
}
|
|
131
131
|
if (subcommand === "start") {
|
|
132
|
-
const { startCommand } = await import("./start-
|
|
132
|
+
const { startCommand } = await import("./start-PEUWCTXE.js");
|
|
133
133
|
await startCommand();
|
|
134
134
|
return true;
|
|
135
135
|
}
|
|
136
136
|
if (subcommand === "roi") {
|
|
137
|
-
const { roiCommand } = await import("./roi-
|
|
137
|
+
const { roiCommand } = await import("./roi-O3LOQ37N.js");
|
|
138
138
|
await roiCommand();
|
|
139
139
|
return true;
|
|
140
140
|
}
|
|
141
141
|
if (subcommand === "plan-backfill") {
|
|
142
|
-
const { planBackfillCommand } = await import("./plan-backfill-
|
|
142
|
+
const { planBackfillCommand } = await import("./plan-backfill-6YRSHNXU.js");
|
|
143
143
|
await planBackfillCommand();
|
|
144
144
|
return true;
|
|
145
145
|
}
|
|
@@ -151,7 +151,7 @@ async function main() {
|
|
|
151
151
|
const args = parseCliArgs();
|
|
152
152
|
if (args.serve) {
|
|
153
153
|
await loadAuthFromConfig(env);
|
|
154
|
-
const { serve } = await import("./serve-
|
|
154
|
+
const { serve } = await import("./serve-BHBP3NUA.js");
|
|
155
155
|
return serve({ isDev: env.SHIPYARD_DEV });
|
|
156
156
|
}
|
|
157
157
|
logger.error("Use `shipyard start` to run the daemon. Use --help for usage.");
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
ensureAuthenticated,
|
|
4
4
|
getSignalingUrl,
|
|
5
5
|
loginCommand
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CBLCALKE.js";
|
|
7
7
|
import "./chunk-ZHLJRR5O.js";
|
|
8
8
|
import "./chunk-TFWDJCUJ.js";
|
|
9
|
-
import "./chunk-
|
|
9
|
+
import "./chunk-UUOHTXO7.js";
|
|
10
10
|
import "./chunk-EHQITHQX.js";
|
|
11
11
|
import "./chunk-X53VIV2J.js";
|
|
12
12
|
import "./chunk-UTL5PF43.js";
|
|
@@ -17,4 +17,4 @@ export {
|
|
|
17
17
|
getSignalingUrl,
|
|
18
18
|
loginCommand
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=login-
|
|
20
|
+
//# sourceMappingURL=login-VVEVIMRY.js.map
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildTaskStateStore,
|
|
4
4
|
runPlanBackfill
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-W6MC7ULT.js";
|
|
6
6
|
import "./chunk-RR6V6SNM.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-AFSAYFD2.js";
|
|
8
|
+
import "./chunk-UUOHTXO7.js";
|
|
9
9
|
import "./chunk-EHQITHQX.js";
|
|
10
10
|
import "./chunk-X3MULCV5.js";
|
|
11
11
|
import {
|
|
@@ -65,4 +65,4 @@ async function planBackfillCommand(argv = process.argv.slice(3)) {
|
|
|
65
65
|
export {
|
|
66
66
|
planBackfillCommand
|
|
67
67
|
};
|
|
68
|
-
//# sourceMappingURL=plan-backfill-
|
|
68
|
+
//# sourceMappingURL=plan-backfill-6YRSHNXU.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
ROI_EVENT_TYPE,
|
|
5
5
|
RoiEventSchema,
|
|
6
6
|
assertNeverEvent
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-AX4RM7CP.js";
|
|
8
8
|
import "./chunk-EHQITHQX.js";
|
|
9
9
|
import {
|
|
10
10
|
logger
|
|
@@ -214,6 +214,28 @@ function computeEngineerScorecard(ctxs) {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// ../../packages/roi-aggregator/src/reports/build-report.ts
|
|
217
|
+
function computeModelFacet(ctxs) {
|
|
218
|
+
const byModel = /* @__PURE__ */ new Map();
|
|
219
|
+
for (const ctx of ctxs) {
|
|
220
|
+
const model = ctx.ended?.model?.trim() || "unknown";
|
|
221
|
+
const row = byModel.get(model) ?? { count: 0, costUsd: 0, outputTokens: 0 };
|
|
222
|
+
row.count += 1;
|
|
223
|
+
row.costUsd += ctx.ended?.totalCostUsd ?? ctx.record?.totalCostUsd ?? 0;
|
|
224
|
+
row.outputTokens += ctx.ended?.totalOutputTokens ?? ctx.record?.totalOutputTokens ?? 0;
|
|
225
|
+
byModel.set(model, row);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
facet: "model",
|
|
229
|
+
rows: [...byModel.entries()].map(([key, row]) => ({
|
|
230
|
+
key,
|
|
231
|
+
count: row.count,
|
|
232
|
+
costUsd: Number(row.costUsd.toFixed(4)),
|
|
233
|
+
outputTokens: row.outputTokens
|
|
234
|
+
})).sort(
|
|
235
|
+
(a, b) => b.costUsd - a.costUsd || b.outputTokens - a.outputTokens || a.key.localeCompare(b.key)
|
|
236
|
+
)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
217
239
|
function buildRoiReport(ctxs, opts) {
|
|
218
240
|
const warnings = [];
|
|
219
241
|
if (ctxs.length === 0) {
|
|
@@ -271,7 +293,7 @@ function buildRoiReport(ctxs, opts) {
|
|
|
271
293
|
},
|
|
272
294
|
funnel,
|
|
273
295
|
byEngineer,
|
|
274
|
-
facets: [],
|
|
296
|
+
facets: [computeModelFacet(ctxs)],
|
|
275
297
|
warnings
|
|
276
298
|
};
|
|
277
299
|
}
|
|
@@ -680,4 +702,4 @@ function renderReport(report, format) {
|
|
|
680
702
|
export {
|
|
681
703
|
roiCommand
|
|
682
704
|
};
|
|
683
|
-
//# sourceMappingURL=roi-
|
|
705
|
+
//# sourceMappingURL=roi-O3LOQ37N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/shared/commands/roi.ts","../../../packages/roi-aggregator/src/joins/task-context.ts","../../../packages/roi-aggregator/src/metrics/cost-per-pr.ts","../../../packages/roi-aggregator/src/metrics/efficiency-curve.ts","../../../packages/roi-aggregator/src/metrics/funnel.ts","../../../packages/roi-aggregator/src/metrics/leverage.ts","../../../packages/roi-aggregator/src/metrics/per-engineer.ts","../../../packages/roi-aggregator/src/reports/build-report.ts","../../../packages/roi-aggregator/src/reports/csv.ts","../../../packages/roi-aggregator/src/reports/html.ts","../../../packages/roi-aggregator/src/reports/json.ts","../../../packages/roi-aggregator/src/sources/d1.ts","../../../packages/roi-aggregator/src/sources/tasks-json.ts"],"sourcesContent":["import { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { resolve } from 'node:path';\nimport { parseArgs } from 'node:util';\nimport {\n buildRoiReport,\n buildTaskContexts,\n defaultTasksJsonPath,\n fetchRoiEvents,\n type RawTaskRecord,\n type RoiEvent,\n readTasksJson,\n renderCsvReport,\n renderHtmlReport,\n renderJsonReport,\n} from '@shipyard/roi-aggregator';\nimport { validateEnv } from '../env.js';\nimport { logger } from '../logger.js';\n\nexport interface RoiCommandArgs {\n since?: string;\n until?: string;\n format: 'json' | 'html' | 'csv';\n output?: string;\n workerUrl: string;\n token: string;\n tasksJsonPath?: string;\n}\n\nfunction parseIsoToMs(raw: string | undefined): number | undefined {\n if (!raw) return undefined;\n const ms = Date.parse(raw);\n return Number.isFinite(ms) ? ms : undefined;\n}\n\nexport async function roiCommand(argv: string[] = process.argv.slice(3)): Promise<void> {\n const { values } = parseArgs({\n args: argv,\n options: {\n since: { type: 'string' },\n until: { type: 'string' },\n format: { type: 'string', default: 'json' },\n output: { type: 'string', short: 'o' },\n 'worker-url': { type: 'string' },\n token: { type: 'string' },\n 'tasks-json': { type: 'string' },\n 'cwd-prefix': { type: 'string', multiple: true },\n help: { type: 'boolean', short: 'h' },\n },\n strict: true,\n allowPositionals: false,\n });\n\n if (values.help) {\n process.stdout.write(\n [\n 'Usage: shipyard roi [options]',\n '',\n ' --since DATE Start of window (ISO date). Default: 30 days ago.',\n ' --until DATE End of window (ISO date). Default: now.',\n ' --format FORMAT Output format: json, html, csv. Default: json.',\n ' --output FILE, -o Write to file instead of stdout.',\n ' --worker-url URL Metrics worker base URL. Env: SHIPYARD_METRICS_URL.',\n ' --token TOKEN JWT bearer token. Env: SHIPYARD_USER_TOKEN.',\n ' --tasks-json PATH Override path to local tasks.json.',\n ' --cwd-prefix PATH Only include tasks whose cwd starts with PATH. Repeatable for worktrees.',\n ' -h, --help Show this help.',\n '',\n ].join('\\n')\n );\n return;\n }\n\n const format = normalizeFormat(values.format);\n if (!format) {\n logger.error('Invalid --format. Expected one of: json, html, csv.');\n process.exit(1);\n }\n\n const env = validateEnv();\n const workerUrl = values['worker-url'] ?? env.SHIPYARD_METRICS_URL;\n const token = values.token ?? env.SHIPYARD_USER_TOKEN;\n if (!workerUrl || !token) {\n logger.error(\n 'Missing required --worker-url / SHIPYARD_METRICS_URL and --token / SHIPYARD_USER_TOKEN.'\n );\n process.exit(1);\n }\n\n const until = parseIsoToMs(values.until) ?? Date.now();\n const since = parseIsoToMs(values.since) ?? until - 30 * 24 * 60 * 60 * 1000;\n const sinceIso = new Date(since).toISOString();\n const untilIso = new Date(until).toISOString();\n\n const [{ roiEvents }, tasks] = await Promise.all([\n fetchRoiEvents({ workerUrl, authToken: token, since, until }),\n readTasksJson(values['tasks-json'] ?? defaultTasksJsonPath()),\n ]);\n\n const cwdPrefixes = normalizeCwdPrefixes(values['cwd-prefix']);\n const { records: scopedRecords, events: scopedEvents } = applyCwdScope(\n tasks.tasks,\n roiEvents,\n cwdPrefixes\n );\n if (cwdPrefixes.length > 0) {\n logger.info(\n {\n cwdPrefixes,\n recordsKept: Object.keys(scopedRecords).length,\n recordsTotal: Object.keys(tasks.tasks).length,\n eventsKept: scopedEvents.length,\n eventsTotal: roiEvents.length,\n },\n 'roi report scoped by --cwd-prefix'\n );\n }\n\n const ctxs = buildTaskContexts(scopedEvents, scopedRecords);\n const report = buildRoiReport(ctxs, { since: sinceIso, until: untilIso });\n\n const rendered = renderReport(report, format);\n if (values.output) {\n await fs.writeFile(values.output, rendered, 'utf8');\n logger.info({ output: values.output, format }, 'roi report written');\n } else {\n process.stdout.write(rendered);\n if (!rendered.endsWith('\\n')) process.stdout.write('\\n');\n }\n}\n\nfunction normalizeCwdPrefixes(raw: string[] | undefined): string[] {\n if (!raw || raw.length === 0) return [];\n return raw.map((p) => {\n const expanded = p.startsWith('~/') || p === '~' ? p.replace(/^~/, homedir()) : p;\n return resolve(expanded);\n });\n}\n\nfunction applyCwdScope(\n records: Record<string, RawTaskRecord>,\n events: RoiEvent[],\n cwdPrefixes: string[]\n): { records: Record<string, RawTaskRecord>; events: RoiEvent[] } {\n if (cwdPrefixes.length === 0) return { records, events };\n const scoped: Record<string, RawTaskRecord> = {};\n for (const [taskId, record] of Object.entries(records)) {\n const recordCwd = record.cwd ? resolve(record.cwd) : '';\n const matches = cwdPrefixes.some(\n (prefix) => recordCwd === prefix || recordCwd.startsWith(`${prefix}/`)\n );\n if (matches) scoped[taskId] = record;\n }\n const allowed = new Set(Object.keys(scoped));\n const filteredEvents = events.filter((event) => allowed.has(event.taskId));\n return { records: scoped, events: filteredEvents };\n}\n\nfunction normalizeFormat(raw: string | undefined): 'json' | 'html' | 'csv' | null {\n if (raw === 'json' || raw === 'html' || raw === 'csv') return raw;\n return null;\n}\n\nfunction renderReport(\n report: Parameters<typeof renderJsonReport>[0],\n format: 'json' | 'html' | 'csv'\n): string {\n switch (format) {\n case 'json':\n return renderJsonReport(report);\n case 'html':\n return renderHtmlReport(report);\n case 'csv':\n return renderCsvReport(report);\n default: {\n const _exhaustive: never = format;\n throw new Error(`unhandled format: ${String(_exhaustive)}`);\n }\n }\n}\n","import {\n assertNeverEvent,\n type CommitAttributedEvent,\n type PrAttributedEvent,\n type PrMergedEvent,\n type RoiEvent,\n type TaskEndedEvent,\n type TaskStartedEvent,\n} from '../schemas/events';\nimport type { RawTaskRecord } from '../sources/tasks-json';\n\nexport interface TaskContext {\n taskId: string;\n userId: string;\n record: RawTaskRecord | null;\n started: TaskStartedEvent | null;\n ended: TaskEndedEvent | null;\n commits: CommitAttributedEvent[];\n pr: PrAttributedEvent | null;\n merged: PrMergedEvent | null;\n}\n\n/**\n * Canonical \"merged/shipped\" test. Uses the authoritative merge signal — the\n * pr_merged event or the persisted mergedAt stamp — NOT prUrl+status=completed,\n * which task-schemas.ts documents as an unreliable proxy (completed tasks drop\n * their PR link). Single source of truth for funnel + cost/efficiency/engineer\n * metrics so the shipped count and the per-PR KPIs share one denominator.\n */\nexport function hasMergedPr(ctx: TaskContext): boolean {\n return ctx.merged != null || ctx.record?.mergedAt != null;\n}\n\nfunction emptyContext(\n taskId: string,\n userId: string,\n records: Record<string, RawTaskRecord>\n): TaskContext {\n return {\n taskId,\n userId,\n record: records[taskId] ?? null,\n started: null,\n ended: null,\n commits: [],\n pr: null,\n merged: null,\n };\n}\n\nfunction applyEvent(ctx: TaskContext, event: RoiEvent): void {\n switch (event.kind) {\n case 'task_started':\n if (!ctx.started || event.timestamp < ctx.started.timestamp) ctx.started = event;\n break;\n case 'task_ended':\n if (!ctx.ended || event.timestamp > ctx.ended.timestamp) ctx.ended = event;\n break;\n case 'commit_attributed':\n ctx.commits.push(event);\n break;\n case 'pr_attributed':\n if (!ctx.pr) ctx.pr = event;\n break;\n case 'pr_merged':\n if (!ctx.merged || event.timestamp < ctx.merged.timestamp) ctx.merged = event;\n break;\n default:\n assertNeverEvent(event);\n }\n}\n\nexport function buildTaskContexts(\n events: RoiEvent[],\n records: Record<string, RawTaskRecord>\n): TaskContext[] {\n const byTask = new Map<string, TaskContext>();\n\n for (const event of events) {\n let ctx = byTask.get(event.taskId);\n if (!ctx) {\n ctx = emptyContext(event.taskId, event.userId, records);\n byTask.set(event.taskId, ctx);\n }\n applyEvent(ctx, event);\n }\n\n for (const record of Object.values(records)) {\n if (!byTask.has(record.taskId)) {\n byTask.set(record.taskId, emptyContext(record.taskId, '', records));\n }\n }\n\n return [...byTask.values()];\n}\n","import { hasMergedPr, type TaskContext } from '../joins/task-context';\n\nexport function computeCostPerMergedPr(ctxs: TaskContext[]): number {\n let totalCost = 0;\n let mergedPrs = 0;\n for (const ctx of ctxs) {\n if (!hasMergedPr(ctx)) continue;\n mergedPrs += 1;\n const cost =\n ctx.ended?.totalCostUsd ?? ctx.record?.totalCostUsd ?? latestCumulativeCommitCost(ctx);\n totalCost += cost;\n }\n return mergedPrs === 0 ? 0 : Number((totalCost / mergedPrs).toFixed(4));\n}\n\n/**\n * commit_attributed.costUsd is cumulative-to-date (running task total at scan\n * time), not a per-commit delta — so the latest/largest snapshot reconstructs\n * the task cost. MAX (not SUM) avoids overcount and is idempotent to a\n * duplicate-SHA re-emit. Last-resort fallback only (task_ended / record cost preferred).\n */\nfunction latestCumulativeCommitCost(ctx: TaskContext): number {\n return ctx.commits.reduce((max, c) => Math.max(max, c.costUsd), 0);\n}\n","import { hasMergedPr, type TaskContext } from '../joins/task-context';\nimport type { TimeSeriesMetric, TimeSeriesPoint } from '../schemas/report';\n\n/** ISO 8601 week number — week containing the first Thursday of the year is week 1. */\nfunction isoWeek(ms: number): string {\n const d = new Date(ms);\n const thursday = new Date(d);\n thursday.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));\n const yearStart = Date.UTC(thursday.getUTCFullYear(), 0, 1);\n const weekNo = Math.ceil(((thursday.getTime() - yearStart) / 86_400_000 + 1) / 7);\n return `${thursday.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`;\n}\n\nexport function computeTokensPerMergedPrWeekly(ctxs: TaskContext[]): TimeSeriesMetric {\n const buckets = new Map<string, { tokens: number; prs: number }>();\n for (const ctx of ctxs) {\n if (!hasMergedPr(ctx)) continue;\n const ts = ctx.ended?.timestamp ?? ctx.record?.updatedAt;\n if (ts == null) continue;\n const bucket = isoWeek(ts);\n const cur = buckets.get(bucket) ?? { tokens: 0, prs: 0 };\n cur.tokens += ctx.ended?.totalOutputTokens ?? ctx.record?.totalOutputTokens ?? 0;\n cur.prs += 1;\n buckets.set(bucket, cur);\n }\n const series: TimeSeriesPoint[] = [...buckets.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([week, { tokens, prs }]) => ({\n bucket: week,\n value: prs === 0 ? 0 : Math.round(tokens / prs),\n denominator: prs,\n }));\n return { metric: 'tokens_per_pr', unit: 'tokens', bucketSize: 'week', series };\n}\n\nexport function computeConcurrentTasksWeekly(ctxs: TaskContext[]): TimeSeriesMetric {\n const buckets = new Map<string, { peak: number }>();\n for (const ctx of ctxs) {\n const ts = ctx.started?.timestamp ?? ctx.record?.createdAt;\n if (ts == null) continue;\n const active = ctx.started?.activeTaskCount ?? 0;\n const bucket = isoWeek(ts);\n const cur = buckets.get(bucket) ?? { peak: 0 };\n cur.peak = Math.max(cur.peak, active);\n buckets.set(bucket, cur);\n }\n const series: TimeSeriesPoint[] = [...buckets.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([week, { peak }]) => ({ bucket: week, value: peak }));\n return { metric: 'concurrent_tasks', unit: 'count', bucketSize: 'week', series };\n}\n","import { hasMergedPr, type TaskContext } from '../joins/task-context';\nimport type { TaskOutcomeFunnel } from '../schemas/report';\n\nexport function computeFunnel(ctxs: TaskContext[]): TaskOutcomeFunnel {\n let tasksStarted = 0;\n let producedCommits = 0;\n let openedPr = 0;\n let merged = 0;\n let abandoned = 0;\n\n for (const ctx of ctxs) {\n if (ctx.started || ctx.record?.taskStartedAt != null) tasksStarted++;\n if (ctx.commits.length > 0) producedCommits++;\n if (ctx.pr || ctx.record?.prUrl) openedPr++;\n if (hasMergedPr(ctx)) merged++;\n if (ctx.record?.abandonedAt != null) abandoned++;\n }\n\n return {\n tasksStarted,\n producedCommits,\n openedPr,\n merged,\n abandoned,\n revertedWithin30d: 0,\n };\n}\n","import type { TaskContext } from '../joins/task-context';\n\nexport const HUMAN_BASELINE_LEVERAGE = 1.0;\n\nexport interface LeverageSummary {\n peakByUser: Record<string, number>;\n avgPeak: number;\n humanBaseline: number;\n}\n\nexport function computePeakLeverage(ctxs: TaskContext[]): LeverageSummary {\n const peakByUser: Record<string, number> = {};\n for (const ctx of ctxs) {\n const user = ctx.userId || 'unknown';\n const count = ctx.started?.activeTaskCount ?? 0;\n peakByUser[user] = Math.max(peakByUser[user] ?? 0, count);\n }\n const values = Object.values(peakByUser);\n const avgPeak =\n values.length === 0\n ? 0\n : Number((values.reduce((a, b) => a + b, 0) / values.length).toFixed(2));\n return { peakByUser, avgPeak, humanBaseline: HUMAN_BASELINE_LEVERAGE };\n}\n","import { hasMergedPr, type TaskContext } from '../joins/task-context';\nimport type { EngineerScorecardRow } from '../schemas/report';\n\nfunction classifySignal(sampleSize: number, peakParallel: number): EngineerScorecardRow['signal'] {\n if (sampleSize < 3) return 'insufficient-data';\n if (sampleSize < 10) return 'modest';\n if (sampleSize < 20) return peakParallel >= 3 ? 'strong' : 'modest';\n if (peakParallel >= 5) return 'outlier-high';\n if (peakParallel >= 3) return 'strong';\n return 'modest';\n}\n\nfunction engineerRow(userId: string, ctxs: TaskContext[]): EngineerScorecardRow {\n const mergedPrs = ctxs.filter((c) => hasMergedPr(c));\n const peakParallel = ctxs.reduce((max, c) => Math.max(max, c.started?.activeTaskCount ?? 0), 0);\n const sampleSize = ctxs.length;\n const costPerPr =\n mergedPrs.length === 0\n ? 0\n : Number(\n (\n mergedPrs.reduce((sum, c) => sum + (c.ended?.totalCostUsd ?? 0), 0) / mergedPrs.length\n ).toFixed(4)\n );\n return {\n userId,\n peakParallel,\n costPerPrUsd: costPerPr,\n revertRate: 0,\n sampleSize,\n signal: classifySignal(sampleSize, peakParallel),\n };\n}\n\nexport function computeEngineerScorecard(ctxs: TaskContext[]): EngineerScorecardRow[] {\n const byUser = new Map<string, TaskContext[]>();\n for (const ctx of ctxs) {\n const user = ctx.userId || 'unknown';\n const list = byUser.get(user) ?? [];\n list.push(ctx);\n byUser.set(user, list);\n }\n\n const rows: EngineerScorecardRow[] = [];\n for (const [userId, userCtxs] of byUser.entries()) {\n rows.push(engineerRow(userId, userCtxs));\n }\n return rows.sort((a, b) => b.sampleSize - a.sampleSize);\n}\n","import type { TaskContext } from '../joins/task-context';\nimport { computeCostPerMergedPr } from '../metrics/cost-per-pr';\nimport {\n computeConcurrentTasksWeekly,\n computeTokensPerMergedPrWeekly,\n} from '../metrics/efficiency-curve';\nimport { computeFunnel } from '../metrics/funnel';\nimport { computePeakLeverage } from '../metrics/leverage';\nimport { computeEngineerScorecard } from '../metrics/per-engineer';\nimport {\n type FacetBreakdown,\n REPORT_SCHEMA_VERSION,\n type RoiReport,\n type RoiWarning,\n} from '../schemas/report';\n\nexport interface BuildReportOptions {\n since: string;\n until: string;\n}\n\nfunction computeModelFacet(ctxs: TaskContext[]): FacetBreakdown {\n const byModel = new Map<\n string,\n {\n count: number;\n costUsd: number;\n outputTokens: number;\n }\n >();\n\n for (const ctx of ctxs) {\n const model = ctx.ended?.model?.trim() || 'unknown';\n const row = byModel.get(model) ?? { count: 0, costUsd: 0, outputTokens: 0 };\n row.count += 1;\n row.costUsd += ctx.ended?.totalCostUsd ?? ctx.record?.totalCostUsd ?? 0;\n row.outputTokens += ctx.ended?.totalOutputTokens ?? ctx.record?.totalOutputTokens ?? 0;\n byModel.set(model, row);\n }\n\n return {\n facet: 'model',\n rows: [...byModel.entries()]\n .map(([key, row]) => ({\n key,\n count: row.count,\n costUsd: Number(row.costUsd.toFixed(4)),\n outputTokens: row.outputTokens,\n }))\n .sort(\n (a, b) =>\n b.costUsd - a.costUsd || b.outputTokens - a.outputTokens || a.key.localeCompare(b.key)\n ),\n };\n}\n\nexport function buildRoiReport(ctxs: TaskContext[], opts: BuildReportOptions): RoiReport {\n const warnings: RoiWarning[] = [];\n if (ctxs.length === 0) {\n warnings.push({\n code: 'no_data',\n message:\n 'No ROI events found in the selected window. Ensure the daemon is running and events are being ingested.',\n context: {},\n });\n }\n\n const leverage = computePeakLeverage(ctxs);\n const funnel = computeFunnel(ctxs);\n const costPerMergedPr = computeCostPerMergedPr(ctxs);\n const byEngineer = computeEngineerScorecard(ctxs);\n const totalCost = ctxs.reduce(\n (sum, c) => sum + (c.ended?.totalCostUsd ?? c.record?.totalCostUsd ?? 0),\n 0\n );\n const totalTokens = ctxs.reduce(\n (sum, c) => sum + (c.ended?.totalOutputTokens ?? c.record?.totalOutputTokens ?? 0),\n 0\n );\n const engineersActive = new Set(\n ctxs.map((c) => c.userId || 'unknown').filter((u) => u !== 'unknown')\n ).size;\n\n for (const row of byEngineer) {\n if (row.sampleSize < 20) {\n warnings.push({\n code: 'small_sample',\n message: `Engineer ${row.userId} has only ${row.sampleSize} tasks in window. Deltas not statistically significant.`,\n context: { userId: row.userId, sampleSize: row.sampleSize },\n });\n }\n }\n\n return {\n schemaVersion: REPORT_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n window: { since: opts.since, until: opts.until },\n totals: {\n tasksStarted: funnel.tasksStarted,\n tasksAbandoned: funnel.abandoned,\n tasksShipped: funnel.merged,\n costUsd: Number(totalCost.toFixed(4)),\n tokens: totalTokens,\n engineersActive,\n },\n kpis: {\n costPerMergedPr,\n peakLeverageAvg: leverage.avgPeak,\n peakLeverageHumanBaseline: leverage.humanBaseline,\n revertRateShipyard: 0,\n },\n series: {\n tokensPerPrWeekly: computeTokensPerMergedPrWeekly(ctxs),\n concurrentTasksWeekly: computeConcurrentTasksWeekly(ctxs),\n },\n funnel,\n byEngineer,\n facets: [computeModelFacet(ctxs)],\n warnings,\n };\n}\n","import type { RoiReport } from '../schemas/report';\n\nfunction csvEscape(value: string | number): string {\n const s = String(value);\n if (s.includes(',') || s.includes('\"') || s.includes('\\n')) {\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n }\n return s;\n}\n\nexport function renderCsvReport(report: RoiReport): string {\n const lines: string[] = [];\n lines.push('section,key,value');\n lines.push(`totals,tasksStarted,${report.totals.tasksStarted}`);\n lines.push(`totals,tasksAbandoned,${report.totals.tasksAbandoned}`);\n lines.push(`totals,tasksShipped,${report.totals.tasksShipped}`);\n lines.push(`totals,costUsd,${report.totals.costUsd}`);\n lines.push(`totals,tokens,${report.totals.tokens}`);\n lines.push(`totals,engineersActive,${report.totals.engineersActive}`);\n lines.push(`kpi,costPerMergedPr,${report.kpis.costPerMergedPr}`);\n lines.push(`kpi,peakLeverageAvg,${report.kpis.peakLeverageAvg}`);\n lines.push(`funnel,tasksStarted,${report.funnel.tasksStarted}`);\n lines.push(`funnel,producedCommits,${report.funnel.producedCommits}`);\n lines.push(`funnel,openedPr,${report.funnel.openedPr}`);\n lines.push(`funnel,merged,${report.funnel.merged}`);\n lines.push(`funnel,abandoned,${report.funnel.abandoned}`);\n\n lines.push('');\n lines.push('engineer,sampleSize,peakParallel,costPerPrUsd,revertRate,signal');\n for (const row of report.byEngineer) {\n lines.push(\n [\n csvEscape(row.userId),\n row.sampleSize,\n row.peakParallel,\n row.costPerPrUsd,\n row.revertRate,\n row.signal,\n ].join(',')\n );\n }\n\n return `${lines.join('\\n')}\\n`;\n}\n","import type { RoiReport } from '../schemas/report';\n\nexport function renderHtmlReport(report: RoiReport): string {\n const data = JSON.stringify(report).replace(/</g, '\\\\u003c');\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Shipyard ROI — ${report.window.since} → ${report.window.until}</title>\n<style>\n :root { color-scheme: light dark; --text:#e7e5e4; --muted:#a8a29e; --bg:#0c0a09; --surface:#1c1917; --border:#292524; --ok:#1d9e75; --warn:#f0a030; --danger:#e24b4a; }\n @media (prefers-color-scheme: light) { :root { --text:#1c1917; --muted:#57534e; --bg:#fafaf9; --surface:#ffffff; --border:#e7e5e4; } }\n body { font-family: system-ui, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 32px; max-width: 1100px; margin-left: auto; margin-right: auto; line-height: 1.5; }\n h1 { font-size: 22px; font-weight: 500; margin: 0 0 4px 0; }\n .subtitle { font-size: 12px; color: var(--muted); margin-bottom: 24px; font-family: ui-monospace, monospace; }\n .kpi-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:12px; margin-bottom:20px; }\n .kpi { background: var(--surface); padding: 16px; border-radius: 12px; border: 1px solid var(--border); }\n .kpi .label { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); }\n .kpi .value { font-size: 28px; font-weight: 500; margin-top: 4px; }\n .kpi .baseline { font-size: 11px; color: var(--muted); margin-top: 4px; font-family: ui-monospace, monospace; }\n .panel { background: var(--surface); padding: 14px 16px; border-radius: 12px; border: 1px solid var(--border); margin-bottom: 16px; }\n .panel h2 { font-size: 14px; font-weight: 500; margin: 0 0 12px 0; }\n table { width: 100%; border-collapse: collapse; font-size: 13px; }\n th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--muted); border-bottom: 1px solid var(--border); padding: 8px 10px; font-weight: 500; }\n td { padding: 8px 10px; border-bottom: 1px solid var(--border); font-variant-numeric: tabular-nums; }\n .funnel-row { display: grid; grid-template-columns: 1fr 80px; gap: 8px; align-items: center; margin-bottom: 6px; }\n .funnel-bar { height: 10px; background: color-mix(in srgb, var(--ok) 25%, transparent); border-radius: 3px; overflow: hidden; }\n .funnel-fill { height: 100%; background: var(--ok); }\n .funnel-fill.revert { background: var(--danger); }\n .funnel-label { font-size: 12px; color: var(--muted); }\n .funnel-count { font-family: ui-monospace, monospace; font-size: 12px; text-align: right; }\n .warn { background: color-mix(in srgb, var(--warn) 12%, transparent); color: var(--warn); padding: 10px 12px; border-radius: 8px; font-size: 12px; margin-bottom: 8px; }\n .chart-wrap { position: relative; height: 240px; }\n</style>\n</head>\n<body>\n <h1>Shipyard ROI — ${escapeHtml(report.window.since)} → ${escapeHtml(report.window.until)}</h1>\n <div class=\"subtitle\">Generated ${escapeHtml(report.generatedAt)} · ${report.totals.engineersActive} engineer(s) · schema v${report.schemaVersion}</div>\n\n <div class=\"kpi-grid\">\n <div class=\"kpi\"><div class=\"label\">Cost / merged PR</div><div class=\"value\">$${report.kpis.costPerMergedPr.toFixed(2)}</div></div>\n <div class=\"kpi\"><div class=\"label\">Peak leverage</div><div class=\"value\">${report.kpis.peakLeverageAvg.toFixed(1)}×</div><div class=\"baseline\">vs ${report.kpis.peakLeverageHumanBaseline.toFixed(1)}× human baseline</div></div>\n <div class=\"kpi\"><div class=\"label\">Tasks shipped</div><div class=\"value\">${report.totals.tasksShipped}</div></div>\n <div class=\"kpi\"><div class=\"label\">Token spend</div><div class=\"value\">$${report.totals.costUsd.toFixed(2)}</div></div>\n </div>\n\n <div class=\"panel\">\n <h2>Task outcome funnel</h2>\n ${renderFunnel(report)}\n </div>\n\n <div class=\"panel\">\n <h2>Tokens per merged PR (weekly)</h2>\n <div class=\"chart-wrap\"><canvas id=\"chart-tokens\"></canvas></div>\n </div>\n\n <div class=\"panel\">\n <h2>Peak concurrent tasks (weekly)</h2>\n <div class=\"chart-wrap\"><canvas id=\"chart-concurrent\"></canvas></div>\n </div>\n\n <div class=\"panel\">\n <h2>Per-engineer scorecard</h2>\n <table>\n <thead><tr><th>Engineer</th><th>Tasks</th><th>Peak ‖</th><th>$ / PR</th><th>Signal</th></tr></thead>\n <tbody>\n ${report.byEngineer\n .map(\n (row) =>\n `<tr><td>${escapeHtml(row.userId)}</td><td>${row.sampleSize}</td><td>${row.peakParallel}</td><td>$${row.costPerPrUsd.toFixed(2)}</td><td>${escapeHtml(row.signal)}</td></tr>`\n )\n .join('')}\n </tbody>\n </table>\n </div>\n\n ${\n report.warnings.length > 0\n ? `<div class=\"panel\"><h2>Warnings</h2>${report.warnings.map((w) => `<div class=\"warn\">${escapeHtml(w.message)}</div>`).join('')}</div>`\n : ''\n }\n\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js\" crossorigin=\"anonymous\"></script>\n<script>\n const report = ${data};\n const textColor = getComputedStyle(document.documentElement).getPropertyValue('--text').trim();\n const gridColor = getComputedStyle(document.documentElement).getPropertyValue('--border').trim();\n Chart.defaults.color = textColor;\n\n const tokensEl = document.getElementById('chart-tokens');\n if (tokensEl) {\n new Chart(tokensEl, {\n type: 'line',\n data: {\n labels: report.series.tokensPerPrWeekly.series.map(p => p.bucket),\n datasets: [{ label: 'tokens / PR', data: report.series.tokensPerPrWeekly.series.map(p => p.value), borderColor: '#1d9e75', tension: 0.3 }]\n },\n options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { grid: { color: gridColor } }, y: { grid: { color: gridColor } } } }\n });\n }\n\n const concurrentEl = document.getElementById('chart-concurrent');\n if (concurrentEl) {\n const buckets = report.series.concurrentTasksWeekly.series.map(p => p.bucket);\n const baselineValue = report.kpis.peakLeverageHumanBaseline;\n new Chart(concurrentEl, {\n type: 'line',\n data: {\n labels: buckets,\n datasets: [\n { label: 'peak concurrent', data: report.series.concurrentTasksWeekly.series.map(p => p.value), borderColor: '#0f6e56', backgroundColor: 'rgba(15,110,86,0.18)', fill: true, tension: 0.3 },\n { label: 'human baseline', data: buckets.map(() => baselineValue), borderColor: '#888780', borderDash: [4, 4], borderWidth: 1, pointRadius: 0, fill: false }\n ]\n },\n options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: true, labels: { boxWidth: 12, font: { size: 11 } } } }, scales: { x: { grid: { color: gridColor } }, y: { grid: { color: gridColor }, beginAtZero: true } } }\n });\n }\n</script>\n</body>\n</html>\n`;\n}\n\nfunction renderFunnel(report: RoiReport): string {\n const stages = [\n { label: 'Tasks started', count: report.funnel.tasksStarted },\n { label: 'Produced commits', count: report.funnel.producedCommits },\n { label: 'Opened PR', count: report.funnel.openedPr },\n { label: 'Merged', count: report.funnel.merged },\n { label: 'Abandoned', count: report.funnel.abandoned, revert: true },\n ];\n const max = Math.max(1, ...stages.map((s) => s.count));\n return stages\n .map(\n (s) =>\n `<div class=\"funnel-row\"><div><div class=\"funnel-label\">${escapeHtml(s.label)}</div><div class=\"funnel-bar\"><div class=\"funnel-fill ${s.revert ? 'revert' : ''}\" style=\"width: ${Math.round((s.count / max) * 100)}%\"></div></div></div><div class=\"funnel-count\">${s.count}</div></div>`\n )\n .join('');\n}\n\nfunction escapeHtml(value: string): string {\n return String(value)\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n","import type { RoiReport } from '../schemas/report';\n\nexport function renderJsonReport(report: RoiReport): string {\n return JSON.stringify(report, null, 2);\n}\n","import { ROI_EVENT_TYPE, type RoiEvent, RoiEventSchema } from '../schemas/events';\n\nexport interface FetchEventsOptions {\n workerUrl: string;\n authToken: string;\n since?: number;\n until?: number;\n limit?: number;\n fetchImpl?: typeof fetch;\n}\n\nexport interface RawQueryEvent {\n eventType: string;\n taskId: string | null;\n userId: string;\n clientTimestamp: number;\n serverTimestamp: number;\n payload: unknown;\n}\n\nexport interface FetchedEvents {\n events: RawQueryEvent[];\n roiEvents: RoiEvent[];\n}\n\nexport async function fetchRoiEvents(opts: FetchEventsOptions): Promise<FetchedEvents> {\n const fetchImpl = opts.fetchImpl ?? fetch;\n const baseUrl = opts.workerUrl.replace(/\\/$/, '');\n const qs = new URLSearchParams({ eventType: ROI_EVENT_TYPE });\n if (opts.since != null) qs.set('since', String(opts.since));\n if (opts.until != null) qs.set('until', String(opts.until));\n if (opts.limit != null) qs.set('limit', String(opts.limit));\n\n const response = await fetchImpl(`${baseUrl}/query/events?${qs.toString()}`, {\n method: 'GET',\n headers: { Authorization: `Bearer ${opts.authToken}` },\n });\n if (!response.ok) {\n throw new Error(`query/events responded ${response.status}: ${await response.text()}`);\n }\n // eslint-disable-next-line no-restricted-syntax -- fetch response.json() is untyped at the boundary\n const body = (await response.json()) as { events: RawQueryEvent[] };\n const events = body.events ?? [];\n const roiEvents: RoiEvent[] = [];\n for (const row of events) {\n if (row.eventType !== ROI_EVENT_TYPE) continue;\n const parsed = RoiEventSchema.safeParse(row.payload);\n if (parsed.success) roiEvents.push(parsed.data);\n }\n return { events, roiEvents };\n}\n","import { promises as fs } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nexport interface RawTaskRecord {\n taskId: string;\n channelId: string;\n title: string;\n status: string;\n mode?: string;\n createdAt: number;\n updatedAt: number;\n taskStartedAt: number | null;\n cwd: string;\n totalCostUsd?: number;\n totalOutputTokens?: number;\n taskType?: string;\n prUrl?: string;\n abandonedAt?: number | null;\n /** ROI: canonical \"shipped\" signal — stamped by the 3-tier merge detector. */\n mergedAt?: number | null;\n}\n\nexport interface TasksJsonShape {\n schemaVersion?: number;\n tasks: Record<string, RawTaskRecord>;\n}\n\nexport function defaultTasksJsonPath(): string {\n return join(homedir(), '.shipyard', 'data', 'tasks.json');\n}\n\n/**\n * Read the per-task directory layout (`~/.shipyard/data/tasks/${id}.json`)\n * that `DirectoryRecordStore` writes after the v3.5 migration. Returns\n * `null` when the directory is missing — caller falls back to the legacy\n * single-file path.\n */\nasync function readTasksDirectory(dirPath: string): Promise<TasksJsonShape | null> {\n let entries: string[];\n try {\n entries = await fs.readdir(dirPath);\n } catch {\n return null;\n }\n const tasks: Record<string, RawTaskRecord> = {};\n for (const entry of entries) {\n if (!entry.endsWith('.json') || entry.startsWith('__')) continue;\n const taskId = entry.slice(0, -'.json'.length);\n let raw: string;\n try {\n raw = await fs.readFile(join(dirPath, entry), 'utf8');\n } catch {\n continue;\n }\n try {\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse result is validated by field access below\n const parsed = JSON.parse(raw) as RawTaskRecord;\n tasks[taskId] = parsed;\n } catch {\n process.stderr.write(\n `[roi-aggregator] warning: ${entry} contains malformed JSON — task skipped\\n`\n );\n }\n }\n return { tasks };\n}\n\n/**\n * Read the task store, preferring the per-task directory layout introduced in\n * v3.5 (`DirectoryRecordStore`) and falling back to the legacy single-file\n * `tasks.json` for unmigrated installs. Final fallback is `tasks.json.migrated`\n * (the rename produced by the directory store) so reports run during a\n * partial-rollout upgrade still see local task context.\n *\n * The `filePath` argument is interpreted as the legacy single-file path; the\n * sibling `tasks/` directory is checked first when no explicit override is\n * given. CLI users passing `--tasks-json PATH` keep single-file semantics.\n */\nexport async function readTasksJson(\n filePath: string = defaultTasksJsonPath()\n): Promise<TasksJsonShape> {\n const dirPath = join(dirname(filePath), 'tasks');\n const fromDir = await readTasksDirectory(dirPath);\n if (fromDir !== null) return fromDir;\n\n let raw: string;\n let sourcePath = filePath;\n try {\n raw = await fs.readFile(filePath, 'utf8');\n } catch {\n /**\n * Final fallback: the directory store renames `tasks.json` →\n * `tasks.json.migrated` after a successful migration. If the user is\n * mid-upgrade and the per-task directory is empty for some reason\n * (e.g. corrupt + quarantined), reading the renamed snapshot still\n * lets ROI report from the last known good state instead of returning\n * empty.\n */\n try {\n const migratedPath = `${filePath}.migrated`;\n raw = await fs.readFile(migratedPath, 'utf8');\n sourcePath = migratedPath;\n } catch {\n return { tasks: {} };\n }\n }\n try {\n // eslint-disable-next-line no-restricted-syntax -- JSON.parse result is validated by field access below\n const parsed = JSON.parse(raw) as TasksJsonShape;\n return { schemaVersion: parsed.schemaVersion, tasks: parsed.tasks ?? {} };\n } catch {\n process.stderr.write(\n `[roi-aggregator] warning: ${sourcePath} exists but contains malformed JSON — local task context will be empty\\n`\n );\n return { tasks: {} };\n }\n}\n"],"mappings":"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","names":["fs","homedir","fs","homedir"]}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
guardedSubscribe,
|
|
13
13
|
makeInitialAttemptState,
|
|
14
14
|
shutdownFileWatcherGuard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-FEJCW4CK.js";
|
|
16
16
|
import {
|
|
17
17
|
TRAILER_SCHEMA_VERSION,
|
|
18
18
|
appendTrailerToMessage,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
emitTaskEnded,
|
|
23
23
|
emitTaskStarted,
|
|
24
24
|
hasShipyardTrailer
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-AX4RM7CP.js";
|
|
26
26
|
import {
|
|
27
27
|
Adapter,
|
|
28
28
|
Bridge,
|
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
startPlanBackfillForDaemon,
|
|
40
40
|
sync,
|
|
41
41
|
validatePeerId
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-W6MC7ULT.js";
|
|
43
43
|
import {
|
|
44
44
|
loadAuthToken
|
|
45
45
|
} from "./chunk-TFWDJCUJ.js";
|
|
46
46
|
import {
|
|
47
47
|
getDaemonVersion
|
|
48
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-J6COEZIM.js";
|
|
49
49
|
import {
|
|
50
50
|
collectPriorCrashDetail,
|
|
51
51
|
getRecentWasmPanicMessages
|
|
@@ -174,7 +174,7 @@ import {
|
|
|
174
174
|
unresolveReviewThread,
|
|
175
175
|
withLabelAsync,
|
|
176
176
|
writeCursorApiKeyToVault
|
|
177
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-RWG7XGPH.js";
|
|
178
178
|
import {
|
|
179
179
|
buildNodeSpawnEnv,
|
|
180
180
|
nodeSpawnEnvOverlay,
|
|
@@ -235,7 +235,7 @@ import {
|
|
|
235
235
|
createAssetResolver,
|
|
236
236
|
toSdkContent,
|
|
237
237
|
toSdkPermissionMode
|
|
238
|
-
} from "./chunk-
|
|
238
|
+
} from "./chunk-D4PUXGZA.js";
|
|
239
239
|
import {
|
|
240
240
|
AGENT_IDS,
|
|
241
241
|
AGENT_SYSTEM_CLAUDE_CODE,
|
|
@@ -420,7 +420,7 @@ import {
|
|
|
420
420
|
unsafeAssertSourceId,
|
|
421
421
|
vizFileExtension,
|
|
422
422
|
withPreferredRuntimeAuthMethod
|
|
423
|
-
} from "./chunk-
|
|
423
|
+
} from "./chunk-AFSAYFD2.js";
|
|
424
424
|
import {
|
|
425
425
|
AuthGitHubCallbackRequestSchema,
|
|
426
426
|
AuthGitHubCallbackResponseSchema,
|
|
@@ -468,7 +468,7 @@ import {
|
|
|
468
468
|
VaultKeyPutResponseSchema,
|
|
469
469
|
classifyClaudeCodeCompatibility,
|
|
470
470
|
classifyCodexCompatibility
|
|
471
|
-
} from "./chunk-
|
|
471
|
+
} from "./chunk-UUOHTXO7.js";
|
|
472
472
|
import {
|
|
473
473
|
external_exports as external_exports2
|
|
474
474
|
} from "./chunk-EHQITHQX.js";
|
|
@@ -5788,7 +5788,7 @@ function nanoid(size = 21) {
|
|
|
5788
5788
|
}
|
|
5789
5789
|
|
|
5790
5790
|
// src/services/bootstrap/signaling.ts
|
|
5791
|
-
var DAEMON_NPM_VERSION = true ? "3.18.
|
|
5791
|
+
var DAEMON_NPM_VERSION = true ? "3.18.3" : "unknown";
|
|
5792
5792
|
function createDaemonSignaling(config) {
|
|
5793
5793
|
const agentId = config.agentId ?? nanoid();
|
|
5794
5794
|
function send(msg) {
|
|
@@ -20772,6 +20772,7 @@ var PREFIX_FALLBACK_CONTEXT_WINDOWS = [
|
|
|
20772
20772
|
{ prefix: "claude-opus-4-7", contextWindow: 1e6 },
|
|
20773
20773
|
{ prefix: "claude-opus-4-6", contextWindow: 1e6 },
|
|
20774
20774
|
{ prefix: "claude-fable-5", contextWindow: 1e6 },
|
|
20775
|
+
{ prefix: "claude-sonnet-5", contextWindow: 1e6 },
|
|
20775
20776
|
{ prefix: "claude-opus-4", contextWindow: 2e5 },
|
|
20776
20777
|
{ prefix: "claude-sonnet-4", contextWindow: 2e5 },
|
|
20777
20778
|
{ prefix: "claude-haiku-4", contextWindow: 2e5 },
|
|
@@ -20850,7 +20851,8 @@ var ONE_M_SPAWN_PREFIXES = [
|
|
|
20850
20851
|
"claude-opus-4-8",
|
|
20851
20852
|
"claude-opus-4-7",
|
|
20852
20853
|
"claude-opus-4-6",
|
|
20853
|
-
"claude-fable-5"
|
|
20854
|
+
"claude-fable-5",
|
|
20855
|
+
"claude-sonnet-5"
|
|
20854
20856
|
];
|
|
20855
20857
|
function resolveSpawnModel(model) {
|
|
20856
20858
|
if (!model) return model;
|
|
@@ -77462,6 +77464,15 @@ var AgentSessionManager = class {
|
|
|
77462
77464
|
get pendingInputCount() {
|
|
77463
77465
|
return this.#pendingInputs.size;
|
|
77464
77466
|
}
|
|
77467
|
+
/**
|
|
77468
|
+
* Read-only view of the one-shot intent flag: is the next `subprocess_died`
|
|
77469
|
+
* an anticipated (intentional) teardown rather than a surprise crash? Read by
|
|
77470
|
+
* the Thread's death handler to skip permission-denial on expected shutdowns.
|
|
77471
|
+
* See `#expectedShutdown` / `markExpectedShutdown()`.
|
|
77472
|
+
*/
|
|
77473
|
+
get expectedShutdown() {
|
|
77474
|
+
return this.#expectedShutdown;
|
|
77475
|
+
}
|
|
77465
77476
|
sendMessage(content) {
|
|
77466
77477
|
this.#queue.push({ content });
|
|
77467
77478
|
this.#config.log({
|
|
@@ -78940,7 +78951,7 @@ var Thread = class {
|
|
|
78940
78951
|
channelId: config.channelId,
|
|
78941
78952
|
log: (e) => this.#config.log(e),
|
|
78942
78953
|
getState: () => this.#manager.state,
|
|
78943
|
-
notifySdkError: (msg, opts) => this.#
|
|
78954
|
+
notifySdkError: (msg, opts) => this.#surfaceSdkError(msg, opts),
|
|
78944
78955
|
notifySessionExpired: () => this.#manager.notifySessionExpired(),
|
|
78945
78956
|
notifyAbortRetry: () => this.#manager.notifyAbortRetry(),
|
|
78946
78957
|
respawnForResume: () => this.#manager.respawnForResume(),
|
|
@@ -79000,7 +79011,7 @@ var Thread = class {
|
|
|
79000
79011
|
isDisposed: () => this.#disposed,
|
|
79001
79012
|
notifyAbortRetry: () => this.#manager.notifyAbortRetry(),
|
|
79002
79013
|
respawnForResume: () => this.#manager.respawnForResume(),
|
|
79003
|
-
notifyUsageLimitError: (msg) => this.#
|
|
79014
|
+
notifyUsageLimitError: (msg) => this.#surfaceSdkError(msg, { kind: "usage_limit" }),
|
|
79004
79015
|
persistError: (msg) => this.#persistErrorAsAssistantMessage(msg),
|
|
79005
79016
|
log: (entry) => this.#config.log(entry),
|
|
79006
79017
|
threadId: this.#config.threadId
|
|
@@ -79864,6 +79875,24 @@ var Thread = class {
|
|
|
79864
79875
|
});
|
|
79865
79876
|
this.#callbacks.onSessionIdChanged?.(event.sessionId);
|
|
79866
79877
|
}
|
|
79878
|
+
/**
|
|
79879
|
+
* Single terminal-sdk_error surface. Every give-up path routes here: the
|
|
79880
|
+
* recovery controller's caps/engine-fatal, auth-not-logged-in, billing, and
|
|
79881
|
+
* the rate-limit cap. The FSM force-kills the subprocess on `sdk_error`, so the
|
|
79882
|
+
* turn's process is dead and any pending permission's awaiting caller will
|
|
79883
|
+
* never answer — deny it, the same leak `#handleSubprocessDiedEvent` fixes for
|
|
79884
|
+
* the death path. This is the path the DEFAULT Codex runtime (shared engine)
|
|
79885
|
+
* takes on a terminal engine fatal: `onEngineFatal` surfaces `sdk_error`, NOT
|
|
79886
|
+
* `subprocess_died`. Retries never reach here (they respawn instead), so this
|
|
79887
|
+
* only fires when the turn is genuinely being reaped; denyAllPending no-ops
|
|
79888
|
+
* when nothing is pending.
|
|
79889
|
+
*/
|
|
79890
|
+
#surfaceSdkError(...args) {
|
|
79891
|
+
this.#permissionHandler.denyAllPending(
|
|
79892
|
+
"Agent session ended before the permission was answered"
|
|
79893
|
+
);
|
|
79894
|
+
this.#manager.notifySdkError(...args);
|
|
79895
|
+
}
|
|
79867
79896
|
#handleSubprocessDiedEvent(event) {
|
|
79868
79897
|
this.#config.log({
|
|
79869
79898
|
event: "thread_subprocess_died",
|
|
@@ -79884,6 +79913,11 @@ var Thread = class {
|
|
|
79884
79913
|
if (this.#cursorRecovery.consumeStallResumeOnDeath()) return;
|
|
79885
79914
|
if (this.#cursorRecovery.trySignalDeathRetry(event)) return;
|
|
79886
79915
|
if (this.#cursorRecovery.tryCrashDeathRetry(event)) return;
|
|
79916
|
+
if (!this.#manager.expectedShutdown) {
|
|
79917
|
+
this.#permissionHandler.denyAllPending(
|
|
79918
|
+
"Agent process exited before the permission was answered"
|
|
79919
|
+
);
|
|
79920
|
+
}
|
|
79887
79921
|
this.#manager.notifySubprocessDied({
|
|
79888
79922
|
exitCode: event.exitCode ?? null,
|
|
79889
79923
|
signal: event.signal ?? null
|
|
@@ -79920,7 +79954,7 @@ var Thread = class {
|
|
|
79920
79954
|
threadId: this.#config.threadId
|
|
79921
79955
|
});
|
|
79922
79956
|
this.#persistErrorAsAssistantMessage(AUTH_NOT_LOGGED_IN_MESSAGE);
|
|
79923
|
-
this.#
|
|
79957
|
+
this.#surfaceSdkError(AUTH_NOT_LOGGED_IN_MESSAGE, { kind: "auth_required" });
|
|
79924
79958
|
break;
|
|
79925
79959
|
case "rate_limit":
|
|
79926
79960
|
case "rate_limit_error":
|
|
@@ -79933,7 +79967,7 @@ var Thread = class {
|
|
|
79933
79967
|
threadId: this.#config.threadId
|
|
79934
79968
|
});
|
|
79935
79969
|
this.#persistErrorAsAssistantMessage(surfaceMsg);
|
|
79936
|
-
this.#
|
|
79970
|
+
this.#surfaceSdkError(surfaceMsg, { kind: "usage_limit" });
|
|
79937
79971
|
break;
|
|
79938
79972
|
}
|
|
79939
79973
|
case "user_message_echo": {
|
|
@@ -86710,6 +86744,7 @@ function emitTaskEndedAttribution(deps) {
|
|
|
86710
86744
|
taskId: deps.taskId,
|
|
86711
86745
|
userId: deps.userId,
|
|
86712
86746
|
finalStatus,
|
|
86747
|
+
model: deps.model,
|
|
86713
86748
|
totalCostUsd: deps.totalCostUsd,
|
|
86714
86749
|
totalOutputTokens: deps.totalOutputTokens,
|
|
86715
86750
|
turnCount: deps.turnCount,
|
|
@@ -86779,6 +86814,7 @@ var RoiTracker = class {
|
|
|
86779
86814
|
taskId: this.#deps.taskId,
|
|
86780
86815
|
userId: this.#deps.userId,
|
|
86781
86816
|
runtimeId: this.#deps.runtimeId,
|
|
86817
|
+
model: this.#deps.getCurrentModel(),
|
|
86782
86818
|
metricsCollector: this.#deps.metricsCollector,
|
|
86783
86819
|
roiLastStatus: this.#roiLastStatus,
|
|
86784
86820
|
roiEverRan: this.#roiEverRan,
|
|
@@ -90516,10 +90552,11 @@ var Task = class {
|
|
|
90516
90552
|
getCwd: () => this.#cwd,
|
|
90517
90553
|
/**
|
|
90518
90554
|
* "Current model" — 2 of 3 (cf. #buildCompactionDeps above and
|
|
90519
|
-
* UsageTracker.getLatestModel): ROI attribution
|
|
90520
|
-
*
|
|
90555
|
+
* UsageTracker.getLatestModel): ROI attribution uses the same current
|
|
90556
|
+
* model resolver across Claude/Codex/Cursor, then falls back to
|
|
90557
|
+
* 'unknown' only when no init/settings/task-record model exists.
|
|
90521
90558
|
*/
|
|
90522
|
-
getCurrentModel: () => this.#
|
|
90559
|
+
getCurrentModel: () => this.#usage.getLatestModel() ?? "unknown",
|
|
90523
90560
|
getCostBaseline: () => this.#usage.getCostBaseline(),
|
|
90524
90561
|
getSdkGitBranches: () => this.#usage.getLastTurnResult()?.gitBranches
|
|
90525
90562
|
};
|
|
@@ -100979,4 +101016,4 @@ export {
|
|
|
100979
101016
|
decideWorkspaceScope,
|
|
100980
101017
|
serve
|
|
100981
101018
|
};
|
|
100982
|
-
//# sourceMappingURL=serve-
|
|
101019
|
+
//# sourceMappingURL=serve-BHBP3NUA.js.map
|