@schoolai/shipyard 3.2.0 → 3.2.1-rc.20260421.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.
@@ -2,11 +2,11 @@
2
2
  import {
3
3
  ensureAuthenticated,
4
4
  getSignalingUrl
5
- } from "./chunk-CANDZLMB.js";
5
+ } from "./chunk-JSUYB74F.js";
6
6
  import {
7
7
  print
8
8
  } from "./chunk-YOMKRHVO.js";
9
- import "./chunk-5SJBSLGT.js";
9
+ import "./chunk-FMMRZTOF.js";
10
10
  import "./chunk-EHQITHQX.js";
11
11
  import "./chunk-IHSXN66C.js";
12
12
  import "./chunk-3UYLOJ6E.js";
@@ -27,10 +27,10 @@ async function startCommand() {
27
27
  env.SHIPYARD_USER_ID = authResult.userId;
28
28
  env.SHIPYARD_USER_DISPLAY_NAME = authResult.displayName;
29
29
  env.SHIPYARD_SIGNALING_URL = authResult.signalingUrl;
30
- const { serve } = await import("./serve-3EFFP3PN.js");
30
+ const { serve } = await import("./serve-P5WC5JIT.js");
31
31
  return serve({ isDev: env.SHIPYARD_DEV, autoOpenBrowser: !authResult.deviceFlowRan });
32
32
  }
33
33
  export {
34
34
  startCommand
35
35
  };
36
- //# sourceMappingURL=start-24JXLIQJ.js.map
36
+ //# sourceMappingURL=start-2K7HFXHV.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@schoolai/shipyard",
3
- "version": "3.2.0",
3
+ "version": "3.2.1-rc.20260421.0",
4
4
  "description": "Shipyard daemon - Claude Agent SDK + Loro CRDT sync",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/roi-aggregator/src/schemas/events.ts","../../../packages/roi-aggregator/src/emit/commit-attributed.ts","../../../packages/roi-aggregator/src/emit/pr-attributed.ts","../../../packages/roi-aggregator/src/emit/task-ended.ts","../../../packages/roi-aggregator/src/emit/task-started.ts","../../../packages/roi-aggregator/src/schemas/report.ts","../../../packages/roi-aggregator/src/schemas/trailers.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const ROI_EVENT_TYPE = 'shipyard.roi' as const;\n\nexport const ROI_EVENT_SCHEMA_VERSION = 2 as const;\n\nconst EventBase = z.object({\n version: z.literal(ROI_EVENT_SCHEMA_VERSION),\n taskId: z.string().min(1),\n userId: z.string().min(1),\n timestamp: z.number().int().positive(),\n});\n\nexport const TaskStartedEventSchema = EventBase.extend({\n kind: z.literal('task_started'),\n activeTaskCount: z.number().int().nonnegative(),\n taskType: z.string().max(50).optional(),\n mode: z.string().max(50),\n});\nexport type TaskStartedEvent = z.infer<typeof TaskStartedEventSchema>;\n\nexport const TaskEndedEventSchema = EventBase.extend({\n kind: z.literal('task_ended'),\n finalStatus: z.enum(['completed', 'canceled', 'input_required']),\n totalCostUsd: z.number().nonnegative(),\n totalOutputTokens: z.number().int().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n durationMs: z.number().int().nonnegative(),\n});\nexport type TaskEndedEvent = z.infer<typeof TaskEndedEventSchema>;\n\nexport const AttributionTypeSchema = z.enum(['originated', 'amended', 'extended']);\nexport type AttributionType = z.infer<typeof AttributionTypeSchema>;\n\nexport const CommitAttributedEventSchema = EventBase.extend({\n kind: z.literal('commit_attributed'),\n commitSha: z.string().regex(/^[0-9a-f]{7,40}$/, 'expected hex SHA'),\n repo: z.string().min(1),\n branch: z.string().min(1),\n model: z.string().min(1),\n tokens: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n attributionType: AttributionTypeSchema,\n});\nexport type CommitAttributedEvent = z.infer<typeof CommitAttributedEventSchema>;\n\nexport const PrAttributedEventSchema = EventBase.extend({\n kind: z.literal('pr_attributed'),\n prUrl: z.string().url(),\n prNumber: z.number().int().positive(),\n repo: z.string().min(1),\n});\nexport type PrAttributedEvent = z.infer<typeof PrAttributedEventSchema>;\n\nexport const RoiEventSchema = z.discriminatedUnion('kind', [\n TaskStartedEventSchema,\n TaskEndedEventSchema,\n CommitAttributedEventSchema,\n PrAttributedEventSchema,\n]);\nexport type RoiEvent = z.infer<typeof RoiEventSchema>;\n\nexport type RoiEventKind = RoiEvent['kind'];\n\nexport function isRoiEvent(value: unknown): value is RoiEvent {\n return RoiEventSchema.safeParse(value).success;\n}\n\nexport function assertNeverEvent(event: never): never {\n throw new Error(`unhandled ROI event kind: ${JSON.stringify(event)}`);\n}\n","import type { AttributionType } from '../schemas/events';\nimport {\n CommitAttributedEventSchema,\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface CommitAttributedInput {\n taskId: string;\n userId: string;\n commitSha: string;\n repo: string;\n branch: string;\n model: string;\n tokens: number;\n costUsd: number;\n turnCount: number;\n attributionType: AttributionType;\n timestamp?: number;\n}\n\nexport function emitCommitAttributed(\n collector: MetricsCapture,\n input: CommitAttributedInput\n): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'commit_attributed' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n commitSha: input.commitSha,\n repo: input.repo,\n branch: input.branch,\n model: input.model,\n tokens: input.tokens,\n costUsd: input.costUsd,\n turnCount: input.turnCount,\n attributionType: input.attributionType,\n };\n const parsed = CommitAttributedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import {\n PrAttributedEventSchema,\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface PrAttributedInput {\n taskId: string;\n userId: string;\n prUrl: string;\n prNumber: number;\n repo: string;\n timestamp?: number;\n}\n\nexport function emitPrAttributed(collector: MetricsCapture, input: PrAttributedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'pr_attributed' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n prUrl: input.prUrl,\n prNumber: input.prNumber,\n repo: input.repo,\n };\n const parsed = PrAttributedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import { ROI_EVENT_SCHEMA_VERSION, ROI_EVENT_TYPE, TaskEndedEventSchema } from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface TaskEndedInput {\n taskId: string;\n userId: string;\n finalStatus: 'completed' | 'canceled' | 'input_required';\n totalCostUsd: number;\n totalOutputTokens: number;\n turnCount: number;\n durationMs: number;\n timestamp?: number;\n}\n\nexport function emitTaskEnded(collector: MetricsCapture, input: TaskEndedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'task_ended' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n finalStatus: input.finalStatus,\n totalCostUsd: input.totalCostUsd,\n totalOutputTokens: input.totalOutputTokens,\n turnCount: input.turnCount,\n durationMs: input.durationMs,\n };\n const parsed = TaskEndedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import {\n ROI_EVENT_SCHEMA_VERSION,\n ROI_EVENT_TYPE,\n TaskStartedEventSchema,\n} from '../schemas/events';\nimport type { MetricsCapture } from './types';\n\nexport interface TaskStartedInput {\n taskId: string;\n userId: string;\n activeTaskCount: number;\n mode: string;\n taskType?: string;\n timestamp?: number;\n}\n\nexport function emitTaskStarted(collector: MetricsCapture, input: TaskStartedInput): void {\n const candidate = {\n version: ROI_EVENT_SCHEMA_VERSION,\n kind: 'task_started' as const,\n taskId: input.taskId,\n userId: input.userId,\n timestamp: input.timestamp ?? Date.now(),\n activeTaskCount: input.activeTaskCount,\n mode: input.mode,\n taskType: input.taskType,\n };\n const parsed = TaskStartedEventSchema.safeParse(candidate);\n if (!parsed.success) return;\n collector.capture(ROI_EVENT_TYPE, parsed.data);\n}\n","import { z } from 'zod';\n\nexport const REPORT_SCHEMA_VERSION = 1 as const;\n\nexport const TimeWindowSchema = z.object({\n since: z.string(),\n until: z.string(),\n});\nexport type TimeWindow = z.infer<typeof TimeWindowSchema>;\n\nexport const TimeSeriesPointSchema = z.object({\n bucket: z.string(),\n value: z.number(),\n denominator: z.number().int().nonnegative().optional(),\n});\nexport type TimeSeriesPoint = z.infer<typeof TimeSeriesPointSchema>;\n\nexport const TimeSeriesMetricSchema = z.object({\n metric: z.string(),\n unit: z.string(),\n bucketSize: z.enum(['day', 'week', 'month']),\n series: z.array(TimeSeriesPointSchema),\n});\nexport type TimeSeriesMetric = z.infer<typeof TimeSeriesMetricSchema>;\n\nexport const TaskOutcomeFunnelSchema = z.object({\n tasksStarted: z.number().int().nonnegative(),\n producedCommits: z.number().int().nonnegative(),\n openedPr: z.number().int().nonnegative(),\n merged: z.number().int().nonnegative(),\n abandoned: z.number().int().nonnegative(),\n revertedWithin30d: z.number().int().nonnegative(),\n});\nexport type TaskOutcomeFunnel = z.infer<typeof TaskOutcomeFunnelSchema>;\n\nexport const EngineerScorecardRowSchema = z.object({\n userId: z.string(),\n prsPerWeekDelta: z.number().optional(),\n cycleTimeDelta: z.number().optional(),\n peakParallel: z.number().nonnegative(),\n costPerPrUsd: z.number().nonnegative(),\n revertRate: z.number().min(0).max(1),\n sampleSize: z.number().int().nonnegative(),\n signal: z.enum(['strong', 'modest', 'low-adoption', 'outlier-high', 'insufficient-data']),\n});\nexport type EngineerScorecardRow = z.infer<typeof EngineerScorecardRowSchema>;\n\nexport const FacetBreakdownSchema = z.object({\n facet: z.string(),\n rows: z.array(\n z.object({\n key: z.string(),\n count: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n mergedPct: z.number().min(0).max(1).optional(),\n abandonedPct: z.number().min(0).max(1).optional(),\n revertedPct: z.number().min(0).max(1).optional(),\n })\n ),\n});\nexport type FacetBreakdown = z.infer<typeof FacetBreakdownSchema>;\n\nexport const RoiTotalsSchema = z.object({\n tasksStarted: z.number().int().nonnegative(),\n tasksAbandoned: z.number().int().nonnegative(),\n tasksShipped: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n tokens: z.number().int().nonnegative(),\n engineersActive: z.number().int().nonnegative(),\n});\nexport type RoiTotals = z.infer<typeof RoiTotalsSchema>;\n\nexport const RoiKpisSchema = z.object({\n costPerMergedPr: z.number().nonnegative(),\n peakLeverageAvg: z.number().nonnegative(),\n peakLeverageHumanBaseline: z.number().nonnegative().default(1.0),\n revertRateShipyard: z.number().min(0).max(1),\n revertRateManualBaseline: z.number().min(0).max(1).optional(),\n});\nexport type RoiKpis = z.infer<typeof RoiKpisSchema>;\n\nexport const RoiWarningSchema = z.object({\n code: z.string(),\n message: z.string(),\n context: z.record(z.unknown()).default({}),\n});\nexport type RoiWarning = z.infer<typeof RoiWarningSchema>;\n\nexport const RoiReportSchema = z.object({\n schemaVersion: z.literal(REPORT_SCHEMA_VERSION),\n generatedAt: z.string(),\n window: TimeWindowSchema,\n totals: RoiTotalsSchema,\n kpis: RoiKpisSchema,\n series: z.object({\n tokensPerPrWeekly: TimeSeriesMetricSchema,\n concurrentTasksWeekly: TimeSeriesMetricSchema,\n }),\n funnel: TaskOutcomeFunnelSchema,\n byEngineer: z.array(EngineerScorecardRowSchema),\n facets: z.array(FacetBreakdownSchema),\n warnings: z.array(RoiWarningSchema),\n});\nexport type RoiReport = z.infer<typeof RoiReportSchema>;\n","import { z } from 'zod';\nimport { type AttributionType, AttributionTypeSchema } from './events';\n\nexport const TRAILER_SCHEMA_VERSION = 2 as const;\n\nexport const SHIPYARD_COAUTHOR = 'Shipyard <bot@shipyard.dev>' as const;\n\nexport const GitTrailerV2Schema = z.object({\n version: z.literal(TRAILER_SCHEMA_VERSION),\n taskId: z.string().min(1),\n sessionId: z.string().min(1),\n model: z.string().min(1),\n tokens: z.number().int().nonnegative(),\n costUsd: z.number().nonnegative(),\n turnCount: z.number().int().nonnegative(),\n attributionType: AttributionTypeSchema,\n clientVersion: z.string().min(1),\n});\nexport type GitTrailerV2 = z.infer<typeof GitTrailerV2Schema>;\n\nconst HEADERS: Record<keyof GitTrailerV2, string> = {\n version: 'Shipyard-Version',\n taskId: 'Shipyard-Task-Id',\n sessionId: 'Shipyard-Session-Id',\n model: 'Shipyard-Model',\n tokens: 'Shipyard-Tokens',\n costUsd: 'Shipyard-Cost-Usd',\n turnCount: 'Shipyard-Turn-Count',\n attributionType: 'Shipyard-Attribution-Type',\n clientVersion: 'Shipyard-Client-Version',\n};\n\nexport function formatTrailer(trailer: GitTrailerV2): string {\n const lines = [\n `${HEADERS.version}: ${trailer.version}`,\n `${HEADERS.taskId}: ${trailer.taskId}`,\n `${HEADERS.sessionId}: ${trailer.sessionId}`,\n `${HEADERS.model}: ${trailer.model}`,\n `${HEADERS.tokens}: ${trailer.tokens}`,\n `${HEADERS.costUsd}: ${trailer.costUsd.toFixed(4)}`,\n `${HEADERS.turnCount}: ${trailer.turnCount}`,\n `${HEADERS.attributionType}: ${trailer.attributionType}`,\n `${HEADERS.clientVersion}: ${trailer.clientVersion}`,\n '',\n `Co-Authored-By: ${SHIPYARD_COAUTHOR}`,\n ];\n return lines.join('\\n');\n}\n\nexport type ParseTrailerResult =\n | { success: true; trailer: GitTrailerV2 }\n | { success: false; error: string };\n\nconst NUMERIC_FIELDS: Record<string, 'int' | 'float'> = {\n [HEADERS.version]: 'int',\n [HEADERS.tokens]: 'int',\n [HEADERS.costUsd]: 'float',\n [HEADERS.turnCount]: 'int',\n};\n\ntype ExtractFieldResult =\n | { ok: true; key: string; value: string | number }\n | { ok: false; error: string }\n | { ok: 'skip' };\n\nfunction extractField(rawLine: string): ExtractFieldResult {\n const line = rawLine.trimEnd();\n if (!line.startsWith('Shipyard-')) return { ok: 'skip' };\n const colonIdx = line.indexOf(':');\n if (colonIdx === -1) return { ok: 'skip' };\n\n const header = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n const numericKind = NUMERIC_FIELDS[header];\n\n if (numericKind === undefined) {\n return { ok: true, key: header, value };\n }\n const n = numericKind === 'int' ? Number.parseInt(value, 10) : Number.parseFloat(value);\n if (Number.isNaN(n)) {\n return { ok: false, error: `non-numeric ${header}: ${value}` };\n }\n return { ok: true, key: header, value: n };\n}\n\nexport function parseTrailer(commitMessage: string): ParseTrailerResult {\n const fields: Record<string, string | number> = {};\n for (const rawLine of commitMessage.split(/\\r?\\n/)) {\n const result = extractField(rawLine);\n if (result.ok === false) return { success: false, error: result.error };\n if (result.ok === 'skip') continue;\n fields[result.key] = result.value;\n }\n\n const candidate = {\n version: fields[HEADERS.version],\n taskId: fields[HEADERS.taskId],\n sessionId: fields[HEADERS.sessionId],\n model: fields[HEADERS.model],\n tokens: fields[HEADERS.tokens],\n costUsd: fields[HEADERS.costUsd],\n turnCount: fields[HEADERS.turnCount],\n attributionType: fields[HEADERS.attributionType],\n clientVersion: fields[HEADERS.clientVersion],\n };\n\n const parsed = GitTrailerV2Schema.safeParse(candidate);\n if (!parsed.success) {\n return { success: false, error: parsed.error.issues.map((i) => i.message).join('; ') };\n }\n return { success: true, trailer: parsed.data };\n}\n\nexport function hasShipyardTrailer(commitMessage: string): boolean {\n return commitMessage.includes(`${HEADERS.taskId}:`);\n}\n\nexport function appendTrailerToMessage(originalMessage: string, trailer: GitTrailerV2): string {\n const body = originalMessage.trimEnd();\n return `${body}\\n\\n${formatTrailer(trailer)}\\n`;\n}\n\nexport { AttributionTypeSchema };\nexport type { AttributionType };\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
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 { parseArgs } from 'node:util';\nimport {\n buildRoiReport,\n buildTaskContexts,\n defaultTasksJsonPath,\n fetchRoiEvents,\n readTasksJson,\n renderCsvReport,\n renderHtmlReport,\n renderJsonReport,\n} from '@shipyard/roi-aggregator';\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 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 ' -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 workerUrl = values['worker-url'] ?? process.env.SHIPYARD_METRICS_URL;\n const token = values.token ?? process.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 ctxs = buildTaskContexts(roiEvents, tasks.tasks);\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 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 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}\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 };\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 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 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 const hasMergedPr =\n ctx.record?.status === 'completed' && (ctx.pr != null || ctx.record?.prUrl != null);\n if (!hasMergedPr) continue;\n mergedPrs += 1;\n const cost = ctx.ended?.totalCostUsd ?? ctx.record?.totalCostUsd ?? sumCommitCosts(ctx);\n totalCost += cost;\n }\n return mergedPrs === 0 ? 0 : Number((totalCost / mergedPrs).toFixed(4));\n}\n\nfunction sumCommitCosts(ctx: TaskContext): number {\n return ctx.commits.reduce((sum, c) => sum + c.costUsd, 0);\n}\n","import 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 const merged =\n ctx.record?.status === 'completed' && (ctx.pr != null || ctx.record?.prUrl != null);\n if (!merged) 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 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 (ctx.record?.status === 'completed' && (ctx.pr != null || ctx.record?.prUrl != null))\n 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 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(\n (c) => c.record?.status === 'completed' && (c.pr || c.record?.prUrl)\n );\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 { REPORT_SCHEMA_VERSION, type RoiReport, type RoiWarning } from '../schemas/report';\n\nexport interface BuildReportOptions {\n since: string;\n until: string;\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((sum, c) => sum + (c.ended?.totalCostUsd ?? 0), 0);\n const totalTokens = ctxs.reduce((sum, c) => sum + (c.ended?.totalOutputTokens ?? 0), 0);\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: [],\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\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 { 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}\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\nexport async function readTasksJson(\n filePath: string = defaultTasksJsonPath()\n): Promise<TasksJsonShape> {\n let raw: string;\n try {\n raw = await fs.readFile(filePath, 'utf8');\n } catch {\n return { tasks: {} };\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: ${filePath} 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","fs"]}