bb-cc-lite 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,666 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { readdir, stat } from "node:fs/promises";
4
+ import { baselinePath } from "./paths.js";
5
+ import { asRecord, extractUsage, mergeUsage, stringField } from "./status-input.js";
6
+ import { isEditTool, safeToolName } from "./tool-metadata.js";
7
+ import { readTranscriptTail } from "./transcript-reader.js";
8
+ import { writeBaseline } from "./baseline.js";
9
+ const DEFAULT_MAX_FILES = 500;
10
+ const DEFAULT_MAX_BYTES_PER_TRANSCRIPT = 512 * 1024;
11
+ const SCAN_BUDGET_MS = 5_000;
12
+ const TRANSCRIPT_READ_CONCURRENCY = 8;
13
+ const RECENT_WINDOW_SIZE = 100;
14
+ const VALIDATION_CATEGORIES = ["tests", "lint", "typecheck", "build"];
15
+ const SAFE_TOOL_CATEGORIES = ["Bash:tests", "Bash:lint", "Bash:typecheck", "Bash:build", "Read", "Grep", "Glob", "LS", "Edit"];
16
+ export async function buildBaseline(options = {}) {
17
+ const homeDir = options.homeDir ?? homedir();
18
+ const claudeProjectsDir = options.claudeProjectsDir ?? join(homeDir, ".claude", "projects");
19
+ const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
20
+ const maxBytesPerTranscript = options.maxBytesPerTranscript ?? DEFAULT_MAX_BYTES_PER_TRANSCRIPT;
21
+ const startedAt = Date.now();
22
+ const deadlineMs = startedAt + SCAN_BUDGET_MS;
23
+ const files = await listTranscriptFiles(claudeProjectsDir, maxFiles, deadlineMs);
24
+ const counters = emptyCounters();
25
+ const sessions = await summarizeTranscriptFiles(files, maxBytesPerTranscript, deadlineMs);
26
+ for (const [index, session] of sessions.entries()) {
27
+ addSessionCounters(counters, session, index < RECENT_WINDOW_SIZE);
28
+ }
29
+ const baseline = baselineFromCounters(counters, { maxFiles, maxBytesPerTranscript }, options.now ?? new Date());
30
+ const written = baseline.source.transcriptFilesScanned > 0;
31
+ if (written) {
32
+ await writeBaseline(baseline, options.appHomePath ? join(options.appHomePath, "baseline.json") : baselinePath(homeDir));
33
+ }
34
+ return { baseline, written };
35
+ }
36
+ async function listTranscriptFiles(root, maxFiles, deadlineMs) {
37
+ if (maxFiles <= 0 || Date.now() > deadlineMs) {
38
+ return [];
39
+ }
40
+ const candidates = [];
41
+ const pending = [root];
42
+ while (pending.length > 0 && Date.now() <= deadlineMs) {
43
+ const current = pending.pop();
44
+ let entries;
45
+ try {
46
+ entries = await readdir(current, { withFileTypes: true });
47
+ }
48
+ catch {
49
+ continue;
50
+ }
51
+ for (const entry of entries) {
52
+ if (Date.now() > deadlineMs) {
53
+ break;
54
+ }
55
+ const child = join(current, entry.name);
56
+ if (entry.isDirectory()) {
57
+ pending.push(child);
58
+ }
59
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
60
+ const mtimeMs = await readableFileMtimeMs(child);
61
+ if (mtimeMs !== undefined) {
62
+ candidates.push({ path: child, mtimeMs });
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return candidates
68
+ .sort((left, right) => right.mtimeMs - left.mtimeMs || left.path.localeCompare(right.path))
69
+ .slice(0, maxFiles)
70
+ .map((candidate) => candidate.path);
71
+ }
72
+ async function readableFileMtimeMs(path) {
73
+ try {
74
+ const fileStat = await stat(path);
75
+ return fileStat.isFile() ? fileStat.mtimeMs : undefined;
76
+ }
77
+ catch {
78
+ return undefined;
79
+ }
80
+ }
81
+ async function summarizeTranscriptFiles(files, maxBytesPerTranscript, deadlineMs) {
82
+ const sessions = [];
83
+ for (let index = 0; index < files.length && Date.now() <= deadlineMs; index += TRANSCRIPT_READ_CONCURRENCY) {
84
+ const batch = files.slice(index, index + TRANSCRIPT_READ_CONCURRENCY);
85
+ const summaries = await Promise.all(batch.map(async (file) => {
86
+ if (Date.now() > deadlineMs) {
87
+ return undefined;
88
+ }
89
+ const tail = await readTranscriptTail(file, { maxBytes: maxBytesPerTranscript });
90
+ return tail.pathReadable ? summarizeSession(tail.lines) : undefined;
91
+ }));
92
+ sessions.push(...summaries.filter((session) => session !== undefined));
93
+ }
94
+ return sessions;
95
+ }
96
+ function addSessionCounters(counters, session, isRecent) {
97
+ counters.transcriptFilesScanned += 1;
98
+ counters.sessionsSeen += 1;
99
+ counters.malformedLines += session.malformedLines;
100
+ counters.toolCalls += session.toolCalls;
101
+ counters.toolResults += session.toolResults;
102
+ counters.failedToolResults += session.failedToolResults;
103
+ counters.validationResults += session.validationResults;
104
+ counters.validationFailures += session.validationFailures;
105
+ counters.successfulEditResults += session.successfulEditResults;
106
+ counters.readSearchToolCalls += session.readSearchToolCalls;
107
+ counters.cacheWritesHighSessions += session.cacheWritesHigh ? 1 : 0;
108
+ counters.repeatedFailureSessions += session.repeatedFailure ? 1 : 0;
109
+ counters.scenarios.read_heavy_debugging += session.readSearchToolCalls >= 3 && session.failedToolResults === 0 ? 1 : 0;
110
+ counters.scenarios.repeated_failure += session.repeatedFailure ? 1 : 0;
111
+ counters.scenarios.validation_command_loop += session.validationLoopUnrecovered ? 1 : 0;
112
+ counters.scenarios.edit_without_validation += session.editWithoutValidation ? 1 : 0;
113
+ counters.scenarios.validation_recovered += session.validationRecovered ? 1 : 0;
114
+ if (isRecent) {
115
+ counters.recentScenarios.read_heavy_debugging += session.readSearchToolCalls >= 3 && session.failedToolResults === 0 ? 1 : 0;
116
+ counters.recentScenarios.repeated_failure += session.repeatedFailure ? 1 : 0;
117
+ counters.recentScenarios.validation_command_loop += session.validationLoopUnrecovered ? 1 : 0;
118
+ counters.recentScenarios.edit_without_validation += session.editWithoutValidation ? 1 : 0;
119
+ counters.recentScenarios.validation_recovered += session.validationRecovered ? 1 : 0;
120
+ }
121
+ counters.outcomes.healthyLike.validationPassedAfterEdit += session.validationPassedAfterEdit ? 1 : 0;
122
+ counters.outcomes.healthyLike.validationRecovered += session.validationRecovered ? 1 : 0;
123
+ counters.outcomes.healthyLike.readHeavyNoFailure += session.readSearchToolCalls >= 3 && session.failedToolResults === 0 ? 1 : 0;
124
+ counters.outcomes.carefulLike.editWithoutValidation += session.editWithoutValidation ? 1 : 0;
125
+ counters.outcomes.carefulLike.toolFailureRecovered += session.toolFailureRecovered ? 1 : 0;
126
+ counters.outcomes.carefulLike.twoFailureStreakRecovered += session.twoFailureStreakRecovered ? 1 : 0;
127
+ counters.outcomes.stopLike.validationLoopUnrecovered += session.validationLoopUnrecovered ? 1 : 0;
128
+ counters.outcomes.stopLike.toolLoopUnrecovered += session.toolLoopUnrecovered ? 1 : 0;
129
+ counters.outcomes.stopLike.sessionEndedInFailureLoop += session.sessionEndedInFailureLoop ? 1 : 0;
130
+ mergeValidationCounters(counters.validation, session.validation);
131
+ mergeEditValidationCounters(counters.editValidation, session.editValidation);
132
+ mergeToolCategoryCounters(counters.toolCategories, session.toolCategories);
133
+ }
134
+ function summarizeSession(lines) {
135
+ const toolById = new Map();
136
+ const activeFailures = new Map();
137
+ let failedValidationSeen = false;
138
+ let editPendingAnyValidation = false;
139
+ let editPendingValidationSuccess = false;
140
+ let toolResultStep = 0;
141
+ const pendingEditSteps = [];
142
+ let usage = {};
143
+ const session = emptySessionCounters();
144
+ for (const line of lines) {
145
+ let parsed;
146
+ try {
147
+ parsed = JSON.parse(line);
148
+ }
149
+ catch {
150
+ session.malformedLines += 1;
151
+ continue;
152
+ }
153
+ const entry = asRecord(parsed);
154
+ if (!entry) {
155
+ session.malformedLines += 1;
156
+ continue;
157
+ }
158
+ usage = mergeUsage(usage, extractUsage(entry), extractUsage(asRecord(entry.message)));
159
+ for (const toolUse of extractToolUses(entry)) {
160
+ session.toolCalls += 1;
161
+ const name = safeToolName(toolUse.name, { basenameOnly: true });
162
+ const meta = {
163
+ name,
164
+ purpose: classifyPurpose(name, toolUse.input),
165
+ isEdit: isEditTool(name, { basenameOnly: true }),
166
+ isReadSearch: isReadSearchTool(name)
167
+ };
168
+ if (meta.isReadSearch) {
169
+ session.readSearchToolCalls += 1;
170
+ }
171
+ if (toolUse.id) {
172
+ toolById.set(toolUse.id, meta);
173
+ }
174
+ }
175
+ for (const toolResult of extractToolResults(entry)) {
176
+ const meta = resolveMeta(toolResult, toolById);
177
+ const key = `${meta.name}:${meta.purpose || "general"}`;
178
+ const validationCategory = validationCategoryForPurpose(meta.purpose);
179
+ const toolCategory = safeToolCategory(meta);
180
+ const isValidation = meta.name === "Bash" && validationCategory !== undefined;
181
+ toolResultStep += 1;
182
+ session.toolResults += 1;
183
+ if (toolCategory) {
184
+ ensureToolCategoryCounters(session.toolCategories, toolCategory).calls += 1;
185
+ }
186
+ if (isValidation) {
187
+ session.validationResults += 1;
188
+ session.validation[validationCategory].calls += 1;
189
+ if (pendingEditSteps.length > 0) {
190
+ for (const editStep of pendingEditSteps) {
191
+ session.editValidation.editsFollowedByValidation += 1;
192
+ session.editValidation.toolStepsFromEditToValidation.push(toolResultStep - editStep);
193
+ }
194
+ pendingEditSteps.length = 0;
195
+ }
196
+ if (editPendingValidationSuccess && !toolResult.isError) {
197
+ session.validationPassedAfterEdit = true;
198
+ editPendingValidationSuccess = false;
199
+ }
200
+ editPendingAnyValidation = false;
201
+ }
202
+ if (!toolResult.isError) {
203
+ const activeFailure = activeFailures.get(key);
204
+ if (activeFailure && activeFailure.count > 0) {
205
+ session.toolFailureRecovered = true;
206
+ session.twoFailureStreakRecovered ||= activeFailure.count >= 2;
207
+ session.validationRecovered ||= isValidation && failedValidationSeen;
208
+ if (activeFailure.validationCategory) {
209
+ const validation = session.validation[activeFailure.validationCategory];
210
+ validation.recovered += 1;
211
+ validation.failuresBeforeRecovery.push(activeFailure.count);
212
+ }
213
+ if (activeFailure.toolCategory) {
214
+ ensureToolCategoryCounters(session.toolCategories, activeFailure.toolCategory).recovered += 1;
215
+ }
216
+ activeFailures.delete(key);
217
+ }
218
+ if (meta.isEdit) {
219
+ session.successfulEditResults += 1;
220
+ editPendingAnyValidation = true;
221
+ editPendingValidationSuccess = true;
222
+ pendingEditSteps.push(toolResultStep);
223
+ }
224
+ continue;
225
+ }
226
+ session.failedToolResults += 1;
227
+ if (toolCategory) {
228
+ ensureToolCategoryCounters(session.toolCategories, toolCategory).failures += 1;
229
+ }
230
+ const previousFailure = activeFailures.get(key);
231
+ const nextFailureCount = (previousFailure?.count || 0) + 1;
232
+ activeFailures.set(key, {
233
+ count: nextFailureCount,
234
+ validationCategory,
235
+ toolCategory
236
+ });
237
+ session.repeatedFailure ||= nextFailureCount >= 2;
238
+ if (toolCategory && nextFailureCount === 2) {
239
+ ensureToolCategoryCounters(session.toolCategories, toolCategory).repeatedFailureSessions += 1;
240
+ }
241
+ if (isValidation) {
242
+ session.validationFailures += 1;
243
+ session.validation[validationCategory].failures += 1;
244
+ failedValidationSeen = true;
245
+ }
246
+ }
247
+ }
248
+ session.cacheWritesHigh = (usage.cacheCreationInputTokens || 0) > (usage.cacheReadInputTokens || 0);
249
+ session.editWithoutValidation = editPendingAnyValidation && session.successfulEditResults > 0;
250
+ session.editValidation.editsWithoutValidation += pendingEditSteps.length;
251
+ for (const [key, activeFailure] of activeFailures) {
252
+ session.repeatedFailure ||= activeFailure.count >= 2;
253
+ session.sessionEndedInFailureLoop ||= activeFailure.count >= 3;
254
+ session.validationLoopUnrecovered ||= key === "Bash:tests" && activeFailure.count >= 3;
255
+ session.toolLoopUnrecovered ||= activeFailure.count >= 3;
256
+ if (activeFailure.validationCategory) {
257
+ session.validation[activeFailure.validationCategory].unrecovered += 1;
258
+ }
259
+ if (activeFailure.toolCategory) {
260
+ const toolCounters = ensureToolCategoryCounters(session.toolCategories, activeFailure.toolCategory);
261
+ toolCounters.unrecovered += 1;
262
+ }
263
+ }
264
+ return session;
265
+ }
266
+ function extractToolUses(entry) {
267
+ const result = [];
268
+ for (const part of contentParts(entry)) {
269
+ if (part.type === "tool_use") {
270
+ const name = stringField(part.name);
271
+ if (name) {
272
+ result.push({ id: stringField(part.id), name, input: part.input });
273
+ }
274
+ }
275
+ }
276
+ const toolUse = asRecord(entry.tool_use) || asRecord(entry.toolUse);
277
+ const directName = stringField(toolUse?.name) || stringField(entry.tool_name) || stringField(entry.toolName);
278
+ if (directName && (entry.type === "tool_use" || toolUse)) {
279
+ result.push({ id: stringField(toolUse?.id) || stringField(entry.tool_use_id), name: directName, input: toolUse?.input });
280
+ }
281
+ return result;
282
+ }
283
+ function extractToolResults(entry) {
284
+ const result = [];
285
+ for (const part of contentParts(entry)) {
286
+ if (part.type === "tool_result") {
287
+ result.push({
288
+ toolUseId: stringField(part.tool_use_id) || stringField(part.toolUseId),
289
+ toolName: stringField(part.name) || stringField(part.tool_name),
290
+ isError: truthyError(part),
291
+ purpose: classifyResultPurpose(part)
292
+ });
293
+ }
294
+ }
295
+ if (entry.type === "tool_result" || entry.type === "tool_result_delta") {
296
+ result.push({
297
+ toolUseId: stringField(entry.tool_use_id) || stringField(entry.toolUseId),
298
+ toolName: stringField(entry.name) || stringField(entry.tool_name) || stringField(entry.toolName),
299
+ isError: truthyError(entry),
300
+ purpose: classifyResultPurpose(entry)
301
+ });
302
+ }
303
+ return result;
304
+ }
305
+ function contentParts(entry) {
306
+ const message = asRecord(entry.message);
307
+ const candidates = [entry.content, message?.content];
308
+ const parts = [];
309
+ for (const candidate of candidates) {
310
+ if (Array.isArray(candidate)) {
311
+ parts.push(...candidate.flatMap((part) => (asRecord(part) ? [asRecord(part)] : [])));
312
+ }
313
+ else if (asRecord(candidate)) {
314
+ parts.push(asRecord(candidate));
315
+ }
316
+ }
317
+ return parts;
318
+ }
319
+ function resolveMeta(toolResult, toolById) {
320
+ const byId = toolResult.toolUseId ? toolById.get(toolResult.toolUseId) : undefined;
321
+ if (byId) {
322
+ return { ...byId, purpose: toolResult.purpose || byId.purpose };
323
+ }
324
+ const name = safeToolName(toolResult.toolName, { basenameOnly: true });
325
+ return {
326
+ name,
327
+ purpose: toolResult.purpose,
328
+ isEdit: isEditTool(name, { basenameOnly: true }),
329
+ isReadSearch: isReadSearchTool(name)
330
+ };
331
+ }
332
+ function classifyPurpose(toolName, input) {
333
+ if (toolName !== "Bash") {
334
+ return undefined;
335
+ }
336
+ const command = stringField(asRecord(input)?.command);
337
+ if (!command) {
338
+ return undefined;
339
+ }
340
+ if (/\b(npm|pnpm|yarn|bun)\s+(run\s+)?test\b|\b(vitest|jest|mocha|pytest|cargo\s+test|go\s+test|rspec|playwright\s+test)\b/i.test(command)) {
341
+ return "tests";
342
+ }
343
+ if (/\b(npm|pnpm|yarn|bun)\s+(run\s+)?lint\b|\b(eslint|ruff|flake8|cargo\s+clippy)\b/i.test(command)) {
344
+ return "lint";
345
+ }
346
+ if (/\b(npm|pnpm|yarn|bun)\s+(run\s+)?typecheck\b|\btsc\s+--noEmit\b|\bmypy\b/i.test(command)) {
347
+ return "typecheck";
348
+ }
349
+ if (/\b(npm|pnpm|yarn|bun)\s+(run\s+)?(build|compile)\b|\b(tsc|vite\s+build|cargo\s+build|go\s+build)\b/i.test(command)) {
350
+ return "build";
351
+ }
352
+ if (/^\s*git\b/i.test(command)) {
353
+ return "git";
354
+ }
355
+ return undefined;
356
+ }
357
+ function classifyResultPurpose(part) {
358
+ const title = stringField(part.title) || stringField(part.summary);
359
+ if (title && /test/i.test(title)) {
360
+ return "tests";
361
+ }
362
+ return undefined;
363
+ }
364
+ function isValidationPurpose(purpose) {
365
+ return purpose === "tests" || purpose === "build" || purpose === "lint" || purpose === "typecheck";
366
+ }
367
+ function validationCategoryForPurpose(purpose) {
368
+ return isValidationPurpose(purpose) ? purpose : undefined;
369
+ }
370
+ function safeToolCategory(meta) {
371
+ if (meta.name === "Bash") {
372
+ const category = validationCategoryForPurpose(meta.purpose);
373
+ return category ? `Bash:${category}` : undefined;
374
+ }
375
+ if (meta.name === "Read" || meta.name === "Grep" || meta.name === "Glob" || meta.name === "LS") {
376
+ return meta.name;
377
+ }
378
+ if (meta.isEdit) {
379
+ return "Edit";
380
+ }
381
+ return undefined;
382
+ }
383
+ function isReadSearchTool(toolName) {
384
+ return toolName === "Read" || toolName === "Grep" || toolName === "Glob" || toolName === "LS";
385
+ }
386
+ function truthyError(value) {
387
+ if (value.is_error === true || value.isError === true || value.error === true) {
388
+ return true;
389
+ }
390
+ const status = stringField(value.status) || stringField(value.result);
391
+ if (status && /error|failed|failure/i.test(status)) {
392
+ return true;
393
+ }
394
+ const exitCode = value.exit_code ?? value.exitCode;
395
+ return typeof exitCode === "number" && exitCode !== 0;
396
+ }
397
+ function mergeValidationCounters(target, source) {
398
+ for (const category of VALIDATION_CATEGORIES) {
399
+ target[category].calls += source[category].calls;
400
+ target[category].failures += source[category].failures;
401
+ target[category].recovered += source[category].recovered;
402
+ target[category].unrecovered += source[category].unrecovered;
403
+ target[category].failuresBeforeRecovery.push(...source[category].failuresBeforeRecovery);
404
+ }
405
+ }
406
+ function mergeEditValidationCounters(target, source) {
407
+ target.editsFollowedByValidation += source.editsFollowedByValidation;
408
+ target.editsWithoutValidation += source.editsWithoutValidation;
409
+ target.toolStepsFromEditToValidation.push(...source.toolStepsFromEditToValidation);
410
+ }
411
+ function mergeToolCategoryCounters(target, source) {
412
+ for (const [category, sourceCounters] of Object.entries(source)) {
413
+ const targetCounters = ensureToolCategoryCounters(target, category);
414
+ targetCounters.calls += sourceCounters.calls;
415
+ targetCounters.failures += sourceCounters.failures;
416
+ targetCounters.repeatedFailureSessions += sourceCounters.repeatedFailureSessions;
417
+ targetCounters.recovered += sourceCounters.recovered;
418
+ targetCounters.unrecovered += sourceCounters.unrecovered;
419
+ }
420
+ }
421
+ function validationAggregatesFromCounters(counters) {
422
+ return {
423
+ tests: validationAggregateFromCounters(counters.tests),
424
+ lint: validationAggregateFromCounters(counters.lint),
425
+ typecheck: validationAggregateFromCounters(counters.typecheck),
426
+ build: validationAggregateFromCounters(counters.build)
427
+ };
428
+ }
429
+ function validationAggregateFromCounters(counters) {
430
+ const recoveryTotal = counters.recovered + counters.unrecovered;
431
+ return {
432
+ calls: counters.calls,
433
+ failures: counters.failures,
434
+ failureRate: rate(counters.failures, counters.calls),
435
+ recovered: counters.recovered,
436
+ unrecovered: counters.unrecovered,
437
+ recoveryRate: rate(counters.recovered, recoveryTotal),
438
+ averageFailuresBeforeRecovery: average(counters.failuresBeforeRecovery),
439
+ medianFailuresBeforeRecovery: percentile(counters.failuresBeforeRecovery, 0.5),
440
+ p75FailuresBeforeRecovery: percentile(counters.failuresBeforeRecovery, 0.75),
441
+ fivePlusFailuresBeforeRecovery: counters.failuresBeforeRecovery.filter((count) => count >= 5).length
442
+ };
443
+ }
444
+ function editValidationFromCounters(counters) {
445
+ const totalEdits = counters.editsFollowedByValidation + counters.editsWithoutValidation;
446
+ return {
447
+ editsFollowedByValidation: counters.editsFollowedByValidation,
448
+ editsWithoutValidation: counters.editsWithoutValidation,
449
+ editWithoutValidationRate: rate(counters.editsWithoutValidation, totalEdits),
450
+ medianToolStepsFromEditToValidation: percentile(counters.toolStepsFromEditToValidation, 0.5),
451
+ p75ToolStepsFromEditToValidation: percentile(counters.toolStepsFromEditToValidation, 0.75)
452
+ };
453
+ }
454
+ function toolCategoryAggregatesFromCounters(counters) {
455
+ const result = {};
456
+ for (const category of SAFE_TOOL_CATEGORIES) {
457
+ const source = counters[category];
458
+ if (!source || source.calls === 0) {
459
+ continue;
460
+ }
461
+ const recoveryTotal = source.recovered + source.unrecovered;
462
+ result[category] = {
463
+ calls: source.calls,
464
+ failures: source.failures,
465
+ repeatedFailureSessions: source.repeatedFailureSessions,
466
+ recovered: source.recovered,
467
+ unrecovered: source.unrecovered,
468
+ recoveryRate: rate(source.recovered, recoveryTotal)
469
+ };
470
+ }
471
+ return result;
472
+ }
473
+ function ensureToolCategoryCounters(target, category) {
474
+ return (target[category] ??= {
475
+ calls: 0,
476
+ failures: 0,
477
+ repeatedFailureSessions: 0,
478
+ recovered: 0,
479
+ unrecovered: 0
480
+ });
481
+ }
482
+ function baselineFromCounters(counters, sourceOptions, now) {
483
+ const timestamp = now.toISOString();
484
+ return {
485
+ schema: "bb-cc-lite.baseline.v1",
486
+ version: 1,
487
+ createdAt: timestamp,
488
+ updatedAt: timestamp,
489
+ source: {
490
+ kind: "local_transcript_scan",
491
+ transcriptFilesScanned: counters.transcriptFilesScanned,
492
+ sessionsSeen: counters.sessionsSeen,
493
+ malformedLines: counters.malformedLines,
494
+ maxBytesPerTranscript: sourceOptions.maxBytesPerTranscript,
495
+ maxFiles: sourceOptions.maxFiles,
496
+ scanStrategy: "mtime_desc_bounded_parallel",
497
+ parallelism: TRANSCRIPT_READ_CONCURRENCY
498
+ },
499
+ privacy: {
500
+ rawPromptsStored: false,
501
+ rawToolOutputStored: false,
502
+ rawPathsStored: false,
503
+ rawCommandsStored: false,
504
+ perSessionRowsStored: false
505
+ },
506
+ recent: {
507
+ windowKind: "newest_files",
508
+ windowSize: RECENT_WINDOW_SIZE,
509
+ transcriptFilesScanned: Math.min(counters.transcriptFilesScanned, RECENT_WINDOW_SIZE),
510
+ sessionsSeen: Math.min(counters.sessionsSeen, RECENT_WINDOW_SIZE)
511
+ },
512
+ totals: {
513
+ toolCalls: counters.toolCalls,
514
+ successfulToolResults: counters.toolResults - counters.failedToolResults,
515
+ failedToolResults: counters.failedToolResults,
516
+ validationCalls: counters.validationResults,
517
+ validationFailures: counters.validationFailures,
518
+ validationSuccesses: counters.validationResults - counters.validationFailures,
519
+ successfulEditResults: counters.successfulEditResults,
520
+ readSearchToolCalls: counters.readSearchToolCalls
521
+ },
522
+ scenarios: {
523
+ read_heavy_debugging: scenario(counters.scenarios.read_heavy_debugging, counters.recentScenarios.read_heavy_debugging),
524
+ repeated_failure: scenario(counters.scenarios.repeated_failure, counters.recentScenarios.repeated_failure),
525
+ validation_command_loop: scenario(counters.scenarios.validation_command_loop, counters.recentScenarios.validation_command_loop),
526
+ edit_without_validation: scenario(counters.scenarios.edit_without_validation, counters.recentScenarios.edit_without_validation),
527
+ validation_recovered: scenario(counters.scenarios.validation_recovered, counters.recentScenarios.validation_recovered)
528
+ },
529
+ outcomes: counters.outcomes,
530
+ rates: {
531
+ toolFailureRate: rate(counters.failedToolResults, counters.toolResults),
532
+ repeatedFailureRate: rate(counters.repeatedFailureSessions, counters.sessionsSeen),
533
+ validationFailureRate: rate(counters.validationFailures, counters.validationResults),
534
+ cacheWritesHighRate: rate(counters.cacheWritesHighSessions, counters.sessionsSeen)
535
+ },
536
+ validation: validationAggregatesFromCounters(counters.validation),
537
+ editValidation: editValidationFromCounters(counters.editValidation),
538
+ toolCategories: toolCategoryAggregatesFromCounters(counters.toolCategories)
539
+ };
540
+ }
541
+ function scenario(seen, recentSeen) {
542
+ return { seen, recentSeen, confidence: confidenceForSeen(Math.max(seen, recentSeen)) };
543
+ }
544
+ function confidenceForSeen(seen) {
545
+ if (seen >= 10) {
546
+ return "high";
547
+ }
548
+ if (seen >= 3) {
549
+ return "medium";
550
+ }
551
+ return "low";
552
+ }
553
+ function rate(count, total) {
554
+ return total > 0 ? Number((count / total).toFixed(4)) : 0;
555
+ }
556
+ function average(values) {
557
+ return values.length > 0 ? Number((values.reduce((total, value) => total + value, 0) / values.length).toFixed(4)) : 0;
558
+ }
559
+ function percentile(values, percentileValue) {
560
+ if (values.length === 0) {
561
+ return 0;
562
+ }
563
+ const sorted = [...values].sort((left, right) => left - right);
564
+ const index = Math.max(0, Math.ceil(sorted.length * percentileValue) - 1);
565
+ return sorted[index] ?? 0;
566
+ }
567
+ function emptyCounters() {
568
+ return {
569
+ transcriptFilesScanned: 0,
570
+ sessionsSeen: 0,
571
+ malformedLines: 0,
572
+ toolCalls: 0,
573
+ toolResults: 0,
574
+ failedToolResults: 0,
575
+ validationResults: 0,
576
+ validationFailures: 0,
577
+ successfulEditResults: 0,
578
+ readSearchToolCalls: 0,
579
+ cacheWritesHighSessions: 0,
580
+ repeatedFailureSessions: 0,
581
+ scenarios: {
582
+ read_heavy_debugging: 0,
583
+ repeated_failure: 0,
584
+ validation_command_loop: 0,
585
+ edit_without_validation: 0,
586
+ validation_recovered: 0
587
+ },
588
+ recentScenarios: {
589
+ read_heavy_debugging: 0,
590
+ repeated_failure: 0,
591
+ validation_command_loop: 0,
592
+ edit_without_validation: 0,
593
+ validation_recovered: 0
594
+ },
595
+ outcomes: {
596
+ healthyLike: {
597
+ validationPassedAfterEdit: 0,
598
+ validationRecovered: 0,
599
+ readHeavyNoFailure: 0
600
+ },
601
+ carefulLike: {
602
+ editWithoutValidation: 0,
603
+ toolFailureRecovered: 0,
604
+ twoFailureStreakRecovered: 0
605
+ },
606
+ stopLike: {
607
+ validationLoopUnrecovered: 0,
608
+ toolLoopUnrecovered: 0,
609
+ sessionEndedInFailureLoop: 0
610
+ }
611
+ },
612
+ validation: emptyValidationCounters(),
613
+ editValidation: emptyEditValidationCounters(),
614
+ toolCategories: {}
615
+ };
616
+ }
617
+ function emptySessionCounters() {
618
+ return {
619
+ malformedLines: 0,
620
+ toolCalls: 0,
621
+ toolResults: 0,
622
+ failedToolResults: 0,
623
+ validationResults: 0,
624
+ validationFailures: 0,
625
+ successfulEditResults: 0,
626
+ readSearchToolCalls: 0,
627
+ cacheWritesHigh: false,
628
+ validationPassedAfterEdit: false,
629
+ validationRecovered: false,
630
+ editWithoutValidation: false,
631
+ toolFailureRecovered: false,
632
+ twoFailureStreakRecovered: false,
633
+ validationLoopUnrecovered: false,
634
+ toolLoopUnrecovered: false,
635
+ sessionEndedInFailureLoop: false,
636
+ repeatedFailure: false,
637
+ validation: emptyValidationCounters(),
638
+ editValidation: emptyEditValidationCounters(),
639
+ toolCategories: {}
640
+ };
641
+ }
642
+ function emptyValidationCounters() {
643
+ return {
644
+ tests: emptyValidationCategoryCounters(),
645
+ lint: emptyValidationCategoryCounters(),
646
+ typecheck: emptyValidationCategoryCounters(),
647
+ build: emptyValidationCategoryCounters()
648
+ };
649
+ }
650
+ function emptyValidationCategoryCounters() {
651
+ return {
652
+ calls: 0,
653
+ failures: 0,
654
+ recovered: 0,
655
+ unrecovered: 0,
656
+ failuresBeforeRecovery: []
657
+ };
658
+ }
659
+ function emptyEditValidationCounters() {
660
+ return {
661
+ editsFollowedByValidation: 0,
662
+ editsWithoutValidation: 0,
663
+ toolStepsFromEditToValidation: []
664
+ };
665
+ }
666
+ //# sourceMappingURL=baseline-builder.js.map