inspecto 1.0.9 → 1.0.11

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/README.md CHANGED
@@ -6,6 +6,10 @@
6
6
 
7
7
  **Claude Code session quality analyzer — grade sessions, detect regressions, catch cache bugs.**
8
8
 
9
+ > CLI to grade Claude Code sessions — 750+ weekly downloads in first week.
10
+
11
+
12
+
9
13
  > AMD's AI director manually analyzed 7,000 Claude Code sessions to prove it got worse.
10
14
  > `inspecto` automates that analysis for every developer.
11
15
 
package/dist/index.js CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
+ import { unlink } from "fs/promises";
6
+
7
+ // src/commands/audit.ts
8
+ import chalk2 from "chalk";
5
9
 
6
10
  // src/parser/project-scanner.ts
7
11
  import { readdir, stat } from "fs/promises";
@@ -13,6 +17,9 @@ import { homedir } from "os";
13
17
  function getClaudeDir() {
14
18
  return join(homedir(), ".claude");
15
19
  }
20
+ function getCacheFilePath() {
21
+ return join(getClaudeDir(), "inspecto-cache.db");
22
+ }
16
23
 
17
24
  // src/parser/project-scanner.ts
18
25
  async function scanSessions(options) {
@@ -106,17 +113,32 @@ async function* readJsonl(filePath) {
106
113
 
107
114
  // src/parser/session-builder.ts
108
115
  import { basename as basename2 } from "path";
116
+
117
+ // src/parser/types.ts
118
+ var SKIP_TYPES = /* @__PURE__ */ new Set([
119
+ "queue-operation",
120
+ "attachment",
121
+ "system",
122
+ "last-prompt"
123
+ ]);
124
+
125
+ // src/parser/session-builder.ts
109
126
  async function buildSession(records, sessionId, projectSlug, subagentPaths) {
110
127
  const turns = [];
128
+ const unknownRecordTypes = /* @__PURE__ */ new Set();
111
129
  let cwd = "";
112
130
  let gitBranch = null;
113
131
  let model = "";
114
132
  let firstTimestamp = "";
115
133
  let lastTimestamp = "";
134
+ let formatVersion = "";
116
135
  async function processRecords(stream, agentId) {
117
136
  const assistantChunks = /* @__PURE__ */ new Map();
118
137
  for await (const record of stream) {
119
- if (isSkippable(record.type)) continue;
138
+ if (!formatVersion && "version" in record && typeof record.version === "string") {
139
+ formatVersion = record.version;
140
+ }
141
+ if (SKIP_TYPES.has(record.type)) continue;
120
142
  if (record.type === "user") {
121
143
  const userRecord = record;
122
144
  handleUserRecord(userRecord, agentId);
@@ -127,6 +149,8 @@ async function buildSession(records, sessionId, projectSlug, subagentPaths) {
127
149
  if (assistantRecord.error) continue;
128
150
  handleAssistantChunk(assistantRecord, assistantChunks);
129
151
  captureMetadata(assistantRecord);
152
+ } else {
153
+ unknownRecordTypes.add(record.type);
130
154
  }
131
155
  }
132
156
  for (const [, acc] of assistantChunks) {
@@ -163,7 +187,9 @@ async function buildSession(records, sessionId, projectSlug, subagentPaths) {
163
187
  gitBranch,
164
188
  durationMs: firstTimestamp && lastTimestamp ? new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime() : 0,
165
189
  subagentCount: subagentIds.size,
166
- subagentTurnCount
190
+ subagentTurnCount,
191
+ formatVersion,
192
+ unknownRecordTypes
167
193
  };
168
194
  function captureMetadata(record) {
169
195
  if (!firstTimestamp && record.timestamp) {
@@ -226,15 +252,6 @@ function normalizeContent(content) {
226
252
  }
227
253
  return content;
228
254
  }
229
- var SKIPPABLE = /* @__PURE__ */ new Set([
230
- "queue-operation",
231
- "attachment",
232
- "system",
233
- "last-prompt"
234
- ]);
235
- function isSkippable(type) {
236
- return SKIPPABLE.has(type);
237
- }
238
255
 
239
256
  // src/metrics/reads-per-edit.ts
240
257
  var EDIT_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "NotebookEdit"]);
@@ -660,6 +677,9 @@ var GRADE_THRESHOLDS = [
660
677
  [60, "D-"],
661
678
  [0, "F"]
662
679
  ];
680
+ function gradeLetterFromScore(score) {
681
+ return GRADE_THRESHOLDS.find(([threshold]) => score >= threshold)?.[1] ?? "F";
682
+ }
663
683
  function gradeSession(session) {
664
684
  const metrics = [];
665
685
  let weightedSum = 0;
@@ -673,7 +693,7 @@ function gradeSession(session) {
673
693
  }
674
694
  }
675
695
  const compositeScore = totalWeight > 0 ? weightedSum / totalWeight : 0;
676
- const letter = GRADE_THRESHOLDS.find(([threshold]) => compositeScore >= threshold)?.[1] ?? "F";
696
+ const letter = gradeLetterFromScore(compositeScore);
677
697
  return {
678
698
  letter,
679
699
  score: Math.round(compositeScore),
@@ -748,6 +768,11 @@ function getAllTips(metrics) {
748
768
  return metrics.map(getTip).filter((tip) => tip !== null);
749
769
  }
750
770
 
771
+ // src/version.ts
772
+ import { createRequire } from "module";
773
+ var _require = createRequire(import.meta.url);
774
+ var VERSION = _require("../package.json").version;
775
+
751
776
  // src/reporter/terminal.ts
752
777
  var STATUS_ICONS = {
753
778
  healthy: chalk.green("\u2713"),
@@ -772,7 +797,7 @@ var METRIC_DISPLAY_NAMES = {
772
797
  function renderAuditReport(session, grade) {
773
798
  const lines = [];
774
799
  lines.push("");
775
- lines.push(chalk.bold(" inspecto v1.0.0") + chalk.dim(" \u2014 Claude Code Session Quality Analyzer"));
800
+ lines.push(chalk.bold(` inspecto v${VERSION}`) + chalk.dim(" \u2014 Claude Code Session Quality Analyzer"));
776
801
  lines.push("");
777
802
  const agentInfo = session.subagentCount > 0 ? `${session.subagentCount} subagents | ${session.turns.length} turns` : `${session.turns.length} turns`;
778
803
  const sessionInfo = [
@@ -975,6 +1000,7 @@ function formatCacheCheckJson(results) {
975
1000
  }
976
1001
 
977
1002
  // src/commands/audit.ts
1003
+ var KNOWN_FORMAT_VERSION = "2.1.167";
978
1004
  async function runAudit(options) {
979
1005
  const sessionFile = await getMostRecentSession({
980
1006
  dataDir: options.dataDir,
@@ -990,9 +1016,21 @@ async function runAudit(options) {
990
1016
  const grade = gradeSession(session);
991
1017
  if (options.json) {
992
1018
  console.log(formatAuditJson(session, grade));
993
- } else {
994
- console.log(renderAuditReport(session, grade));
1019
+ return;
1020
+ }
1021
+ if (session.formatVersion && session.formatVersion !== KNOWN_FORMAT_VERSION) {
1022
+ console.log(
1023
+ chalk2.yellow(
1024
+ `\u26A0 JSONL format version ${session.formatVersion} detected (expected ${KNOWN_FORMAT_VERSION}). Metrics may be inaccurate.`
1025
+ )
1026
+ );
1027
+ }
1028
+ if (session.unknownRecordTypes.size > 0) {
1029
+ const types = [...session.unknownRecordTypes].sort().join(", ");
1030
+ process.stdout.write(chalk2.dim(`Note: skipped unknown record types: ${types}
1031
+ `));
995
1032
  }
1033
+ console.log(renderAuditReport(session, grade));
996
1034
  }
997
1035
 
998
1036
  // src/anomaly/baseline.ts
@@ -1071,7 +1109,69 @@ function parseDuration(duration, now = /* @__PURE__ */ new Date()) {
1071
1109
  return result;
1072
1110
  }
1073
1111
 
1112
+ // src/utils/concurrent.ts
1113
+ async function concurrentSettled(items, limit, fn) {
1114
+ const results = new Array(items.length);
1115
+ let next = 0;
1116
+ const worker = async () => {
1117
+ while (next < items.length) {
1118
+ const i = next++;
1119
+ try {
1120
+ results[i] = { status: "fulfilled", value: await fn(items[i]) };
1121
+ } catch (error) {
1122
+ results[i] = { status: "rejected", reason: error };
1123
+ }
1124
+ }
1125
+ };
1126
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
1127
+ return results;
1128
+ }
1129
+
1130
+ // src/cache/grade-cache.ts
1131
+ import { DatabaseSync } from "sqlite";
1132
+ import { createHash } from "crypto";
1133
+ var db = null;
1134
+ function getDb() {
1135
+ if (!db) {
1136
+ db = new DatabaseSync(getCacheFilePath());
1137
+ db.exec(`
1138
+ CREATE TABLE IF NOT EXISTS grade_cache (
1139
+ cache_key TEXT PRIMARY KEY,
1140
+ grade_result TEXT NOT NULL
1141
+ )
1142
+ `);
1143
+ }
1144
+ return db;
1145
+ }
1146
+ function makeCacheKey(sessionPath, mtime) {
1147
+ return createHash("sha256").update(`${sessionPath}:${mtime.getTime()}`).digest("hex");
1148
+ }
1149
+ function getCachedGrade(sessionPath, mtime) {
1150
+ try {
1151
+ const key = makeCacheKey(sessionPath, mtime);
1152
+ const stmt = getDb().prepare("SELECT grade_result FROM grade_cache WHERE cache_key = ?");
1153
+ const row = stmt.get(key);
1154
+ if (!row) return null;
1155
+ return JSON.parse(row.grade_result);
1156
+ } catch {
1157
+ process.stderr.write("[inspecto cache] read error, skipping cache\n");
1158
+ return null;
1159
+ }
1160
+ }
1161
+ function setCachedGrade(sessionPath, mtime, grade) {
1162
+ try {
1163
+ const key = makeCacheKey(sessionPath, mtime);
1164
+ const stmt = getDb().prepare(
1165
+ "INSERT OR REPLACE INTO grade_cache (cache_key, grade_result) VALUES (?, ?)"
1166
+ );
1167
+ stmt.run(key, JSON.stringify(grade));
1168
+ } catch {
1169
+ process.stderr.write("[inspecto cache] write error, skipping cache\n");
1170
+ }
1171
+ }
1172
+
1074
1173
  // src/commands/trend.ts
1174
+ var CONCURRENCY = 16;
1075
1175
  async function runTrend(options) {
1076
1176
  const duration = options.since ?? "7d";
1077
1177
  const sinceDate = parseDuration(duration);
@@ -1084,13 +1184,15 @@ async function runTrend(options) {
1084
1184
  console.log(`No sessions found in the last ${duration}.`);
1085
1185
  return;
1086
1186
  }
1087
- const settled = await Promise.allSettled(
1088
- sessionFiles.map(async (sf) => {
1089
- const records = readJsonl(sf.path);
1090
- const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);
1091
- return gradeSession(session);
1092
- })
1093
- );
1187
+ const settled = await concurrentSettled(sessionFiles, CONCURRENCY, async (sf) => {
1188
+ const cached = getCachedGrade(sf.path, sf.mtime);
1189
+ if (cached) return cached;
1190
+ const records = readJsonl(sf.path);
1191
+ const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);
1192
+ const grade = gradeSession(session);
1193
+ setCachedGrade(sf.path, sf.mtime, grade);
1194
+ return grade;
1195
+ });
1094
1196
  const grades = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
1095
1197
  if (grades.length === 0) {
1096
1198
  console.log("No valid sessions found to analyze.");
@@ -1158,8 +1260,9 @@ async function runCacheCheck(options) {
1158
1260
  }
1159
1261
 
1160
1262
  // src/commands/compare.ts
1161
- import chalk2 from "chalk";
1263
+ import chalk3 from "chalk";
1162
1264
  import Table2 from "cli-table3";
1265
+ var CONCURRENCY2 = 16;
1163
1266
  async function runCompare(options) {
1164
1267
  const projectNames = options.projects.split(",").map((p) => p.trim());
1165
1268
  const summaries = [];
@@ -1170,12 +1273,23 @@ async function runCompare(options) {
1170
1273
  project: projectFilter
1171
1274
  });
1172
1275
  if (sessionFiles.length === 0) return null;
1173
- const settled = await Promise.allSettled(
1174
- sessionFiles.map(async (sf) => {
1276
+ const settled = await concurrentSettled(
1277
+ sessionFiles,
1278
+ CONCURRENCY2,
1279
+ async (sf) => {
1280
+ const cached = getCachedGrade(sf.path, sf.mtime);
1281
+ if (cached) return cached;
1175
1282
  const records = readJsonl(sf.path);
1176
- const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);
1177
- return gradeSession(session);
1178
- })
1283
+ const session = await buildSession(
1284
+ records,
1285
+ sf.sessionId,
1286
+ sf.projectSlug,
1287
+ sf.subagentPaths
1288
+ );
1289
+ const grade = gradeSession(session);
1290
+ setCachedGrade(sf.path, sf.mtime, grade);
1291
+ return grade;
1292
+ }
1179
1293
  );
1180
1294
  const grades = settled.filter((r) => r.status === "fulfilled").map((r) => r.value);
1181
1295
  if (grades.length === 0) return null;
@@ -1191,7 +1305,7 @@ async function runCompare(options) {
1191
1305
  name: projectFilter,
1192
1306
  sessionCount: grades.length,
1193
1307
  avgGrade: Math.round(avgScore),
1194
- avgLetter: getLetterGrade(avgScore),
1308
+ avgLetter: gradeLetterFromScore(avgScore),
1195
1309
  metrics: metricAvgs
1196
1310
  };
1197
1311
  })
@@ -1216,10 +1330,10 @@ async function runCompare(options) {
1216
1330
  }
1217
1331
  const lines = [];
1218
1332
  lines.push("");
1219
- lines.push(chalk2.bold(" Project comparison"));
1333
+ lines.push(chalk3.bold(" Project comparison"));
1220
1334
  lines.push("");
1221
1335
  const head = ["Project", "Sessions", "Grade", ...summaries[0]?.metrics.keys() ?? []].map(
1222
- (h) => chalk2.dim(h)
1336
+ (h) => chalk3.dim(h)
1223
1337
  );
1224
1338
  const table = new Table2({
1225
1339
  head,
@@ -1257,25 +1371,10 @@ async function runCompare(options) {
1257
1371
  lines.push("");
1258
1372
  console.log(lines.join("\n"));
1259
1373
  }
1260
- function getLetterGrade(score) {
1261
- if (score >= 97) return "A+";
1262
- if (score >= 93) return "A";
1263
- if (score >= 90) return "A-";
1264
- if (score >= 87) return "B+";
1265
- if (score >= 83) return "B";
1266
- if (score >= 80) return "B-";
1267
- if (score >= 77) return "C+";
1268
- if (score >= 73) return "C";
1269
- if (score >= 70) return "C-";
1270
- if (score >= 67) return "D+";
1271
- if (score >= 63) return "D";
1272
- if (score >= 60) return "D-";
1273
- return "F";
1274
- }
1275
1374
 
1276
1375
  // src/index.ts
1277
1376
  var program = new Command();
1278
- program.name("inspecto").description("Claude Code session quality analyzer \u2014 grade sessions, detect regressions, catch cache bugs").version("1.0.0");
1377
+ program.name("inspecto").description("Claude Code session quality analyzer \u2014 grade sessions, detect regressions, catch cache bugs").version(VERSION);
1279
1378
  program.command("audit", { isDefault: true }).description("Grade the most recent Claude Code session").option("--json", "Output as JSON").option("--verbose", "Show per-message breakdown").option("--data-dir <path>", "Custom Claude data directory").option("--project <name>", "Filter to a specific project").action(async (options) => {
1280
1379
  try {
1281
1380
  await runAudit(options);
@@ -1304,6 +1403,24 @@ program.command("compare").description("Compare quality metrics across projects"
1304
1403
  handleError(error);
1305
1404
  }
1306
1405
  });
1406
+ var cache = program.command("cache").description("Manage the inspecto grade cache");
1407
+ cache.command("clear").description("Delete the grade cache file (~/.claude/inspecto-cache.db)").action(async () => {
1408
+ try {
1409
+ const cachePath = getCacheFilePath();
1410
+ try {
1411
+ await unlink(cachePath);
1412
+ console.log(`Cache cleared: ${cachePath}`);
1413
+ } catch (err) {
1414
+ if (err.code === "ENOENT") {
1415
+ console.log("No cache file found.");
1416
+ } else {
1417
+ throw err;
1418
+ }
1419
+ }
1420
+ } catch (error) {
1421
+ handleError(error);
1422
+ }
1423
+ });
1307
1424
  function handleError(error) {
1308
1425
  const message = error instanceof Error ? error.message : String(error);
1309
1426
  console.error(`
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/parser/project-scanner.ts","../src/utils/paths.ts","../src/parser/jsonl-reader.ts","../src/parser/session-builder.ts","../src/metrics/reads-per-edit.ts","../src/metrics/rewrite-ratio.ts","../src/metrics/cache-hit-rate.ts","../src/metrics/task-completion.ts","../src/utils/levenshtein.ts","../src/metrics/retry-density.ts","../src/metrics/tool-diversity.ts","../src/metrics/tokens-per-edit.ts","../src/metrics/subagent-overhead.ts","../src/metrics/grader.ts","../src/reporter/terminal.ts","../src/utils/format.ts","../src/reporter/tips.ts","../src/reporter/json-reporter.ts","../src/commands/audit.ts","../src/anomaly/baseline.ts","../src/anomaly/regression-detector.ts","../src/utils/duration.ts","../src/commands/trend.ts","../src/anomaly/cache-anomaly.ts","../src/commands/cache-check.ts","../src/commands/compare.ts"],"sourcesContent":["/**\n * inspecto — Claude Code Session Quality Analyzer\n *\n * Grade sessions, detect regressions, catch cache bugs.\n * All from the JSONL logs Claude Code already writes.\n */\n\nimport { Command } from \"commander\";\nimport { runAudit } from \"./commands/audit.js\";\nimport { runTrend } from \"./commands/trend.js\";\nimport { runCacheCheck } from \"./commands/cache-check.js\";\nimport { runCompare } from \"./commands/compare.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"inspecto\")\n .description(\"Claude Code session quality analyzer — grade sessions, detect regressions, catch cache bugs\")\n .version(\"1.0.0\");\n\nprogram\n .command(\"audit\", { isDefault: true })\n .description(\"Grade the most recent Claude Code session\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--verbose\", \"Show per-message breakdown\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--project <name>\", \"Filter to a specific project\")\n .action(async (options) => {\n try {\n await runAudit(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"trend\")\n .description(\"Analyze quality trends and detect regressions over time\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\", \"7d\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--project <name>\", \"Filter to a specific project\")\n .action(async (options) => {\n try {\n await runTrend(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"cache-check\")\n .description(\"Detect prompt cache bugs that inflate token costs\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\", \"7d\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .action(async (options) => {\n try {\n await runCacheCheck(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compare\")\n .description(\"Compare quality metrics across projects\")\n .requiredOption(\"--projects <names>\", \"Comma-separated project names\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\")\n .action(async (options) => {\n try {\n await runCompare(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nfunction handleError(error: unknown): void {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\nError: ${message}\\n`);\n process.exit(1);\n}\n\nprogram.parse();\n","/**\n * Discovers Claude Code session files under ~/.claude/projects/.\n *\n * Session files are at: ~/.claude/projects/{project-slug}/{sessionId}.jsonl\n * Subagent files are at: ~/.claude/projects/{project-slug}/{sessionId}/subagents/agent-*.jsonl\n */\n\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join, basename, extname } from \"node:path\";\nimport { getClaudeDir } from \"../utils/paths.js\";\nimport type { SessionFile } from \"./types.js\";\n\n/**\n * Scan ~/.claude/projects/ for all main session JSONL files.\n * Returns files sorted by modification time (most recent first).\n */\nexport async function scanSessions(options?: {\n dataDir?: string;\n project?: string;\n since?: Date;\n}): Promise<SessionFile[]> {\n const claudeDir = options?.dataDir ?? getClaudeDir();\n const projectsDir = join(claudeDir, \"projects\");\n\n let projectDirs: string[];\n try {\n projectDirs = await readdir(projectsDir);\n } catch {\n throw new Error(\n \"Claude Code data directory not found. \" +\n \"Make sure Claude Code is installed and has been used at least once.\\n\" +\n `Expected: ${projectsDir}`,\n );\n }\n\n // Filter to specific project if requested\n if (options?.project) {\n projectDirs = projectDirs.filter((dir) =>\n dir.toLowerCase().includes(options.project!.toLowerCase()),\n );\n }\n\n const projectResults = await Promise.all(\n projectDirs\n .filter((dir) => !dir.startsWith(\".\"))\n .map(async (projectDir) => {\n const fullProjectDir = join(projectsDir, projectDir);\n let entries: string[];\n try {\n entries = await readdir(fullProjectDir);\n } catch {\n return [] as SessionFile[];\n }\n\n const fileResults = await Promise.all(\n entries\n .filter((entry) => extname(entry) === \".jsonl\")\n .map(async (entry) => {\n const filePath = join(fullProjectDir, entry);\n const sessionId = basename(entry, \".jsonl\");\n try {\n const fileStat = await stat(filePath);\n if (options?.since && fileStat.mtime < options.since) return null;\n\n let subagentPaths: string[] | undefined;\n try {\n const subagentDir = join(fullProjectDir, sessionId, \"subagents\");\n const agentFiles = await readdir(subagentDir);\n const paths = agentFiles\n .filter((f) => f.startsWith(\"agent-\") && f.endsWith(\".jsonl\"))\n .map((f) => join(subagentDir, f));\n if (paths.length > 0) subagentPaths = paths;\n } catch {\n // No subagents directory — normal for older sessions\n }\n\n return {\n path: filePath,\n sessionId,\n projectSlug: projectDir,\n mtime: fileStat.mtime,\n subagentPaths,\n } as SessionFile;\n } catch {\n return null;\n }\n }),\n );\n return fileResults.filter((f): f is SessionFile => f !== null);\n }),\n );\n\n const sessions: SessionFile[] = projectResults.flat();\n\n // Sort most recent first\n sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n return sessions;\n}\n\n/**\n * Get the most recent session file, optionally filtered by project.\n */\nexport async function getMostRecentSession(options?: {\n dataDir?: string;\n project?: string;\n}): Promise<SessionFile> {\n const sessions = await scanSessions(options);\n if (sessions.length === 0) {\n throw new Error(\n \"No Claude Code sessions found. \" +\n \"Use Claude Code in a project first to generate session data.\",\n );\n }\n return sessions[0];\n}\n","/**\n * Cross-platform path resolution for Claude Code data directories.\n */\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/**\n * Returns the Claude Code data directory.\n * macOS/Linux: ~/.claude\n * Windows: %USERPROFILE%\\.claude\n */\nexport function getClaudeDir(): string {\n return join(homedir(), \".claude\");\n}\n","/**\n * Streaming JSONL reader using Node's readline + createReadStream.\n * Processes files line-by-line to handle 100MB+ session files without\n * loading them into memory.\n */\n\nimport { createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport type { RawRecord } from \"./types.js\";\n\n/**\n * Stream-reads a JSONL file, yielding one parsed record per line.\n * Malformed lines are silently skipped (common near session end during crashes).\n */\nexport async function* readJsonl(filePath: string): AsyncGenerator<RawRecord> {\n const stream = createReadStream(filePath, { encoding: \"utf-8\" });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (trimmed.length === 0) continue;\n\n try {\n const record = JSON.parse(trimmed) as RawRecord;\n if (record && typeof record === \"object\" && \"type\" in record) {\n yield record;\n }\n } catch {\n // Skip malformed lines — common at session boundaries\n }\n }\n}\n","/**\n * Builds a Session from raw JSONL records.\n *\n * Handles the core complexity of Claude Code's streaming format:\n * - Assistant turns are split across multiple JSONL records sharing the same\n * `message.id`. Content blocks from each chunk are merged into one turn.\n * - Only the final chunk (stop_reason != null) has real output_tokens.\n * - Synthetic records (model: \"<synthetic>\") and errored turns are excluded.\n */\n\nimport { basename } from \"node:path\";\nimport { readJsonl } from \"./jsonl-reader.js\";\nimport type {\n AssistantRecord,\n ContentBlock,\n MergedTurn,\n RawRecord,\n Session,\n UsageData,\n UserRecord,\n} from \"./types.js\";\n\ninterface AssistantAccumulator {\n content: ContentBlock[];\n usage: UsageData | null;\n complete: boolean;\n timestamp: string;\n model: string;\n}\n\n/**\n * Build a processed Session from an async stream of raw records.\n * @param records - AsyncIterable of raw JSONL records (from readJsonl)\n * @param sessionId - The session ID (from filename)\n * @param projectSlug - The project slug (from parent directory name)\n * @param subagentPaths - Optional paths to subagent JSONL files to merge in\n */\nexport async function buildSession(\n records: AsyncIterable<RawRecord>,\n sessionId: string,\n projectSlug: string,\n subagentPaths?: string[],\n): Promise<Session> {\n const turns: MergedTurn[] = [];\n\n let cwd = \"\";\n let gitBranch: string | null = null;\n let model = \"\";\n let firstTimestamp = \"\";\n let lastTimestamp = \"\";\n\n async function processRecords(\n stream: AsyncIterable<RawRecord>,\n agentId: string | undefined,\n ) {\n const assistantChunks = new Map<string, AssistantAccumulator>();\n\n for await (const record of stream) {\n if (isSkippable(record.type)) continue;\n\n if (record.type === \"user\") {\n const userRecord = record as UserRecord;\n handleUserRecord(userRecord, agentId);\n captureMetadata(userRecord);\n } else if (record.type === \"assistant\") {\n const assistantRecord = record as AssistantRecord;\n if (assistantRecord.message.model === \"<synthetic>\") continue;\n if (assistantRecord.error) continue;\n handleAssistantChunk(assistantRecord, assistantChunks);\n captureMetadata(assistantRecord);\n }\n }\n\n // Flush all accumulated assistant chunks into turns\n for (const [, acc] of assistantChunks) {\n turns.push({\n role: \"assistant\",\n content: acc.content,\n usage: acc.usage,\n complete: acc.complete,\n timestamp: acc.timestamp,\n isHumanTurn: false,\n model: acc.model,\n agentId,\n });\n }\n }\n\n await processRecords(records, undefined);\n\n for (const agentPath of subagentPaths ?? []) {\n const agentId = basename(agentPath, \".jsonl\");\n await processRecords(readJsonl(agentPath), agentId);\n }\n\n // Sort all turns (main + subagents) by timestamp\n turns.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n const subagentIds = new Set(\n turns.filter((t) => t.agentId !== undefined).map((t) => t.agentId!),\n );\n const subagentTurnCount = turns.filter((t) => t.agentId !== undefined).length;\n\n return {\n id: sessionId,\n projectSlug,\n model,\n turns,\n startTime: firstTimestamp,\n endTime: lastTimestamp,\n cwd,\n gitBranch,\n durationMs:\n firstTimestamp && lastTimestamp\n ? new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime()\n : 0,\n subagentCount: subagentIds.size,\n subagentTurnCount,\n };\n\n // -- Inner helpers --------------------------------------------------------\n\n function captureMetadata(record: UserRecord | AssistantRecord) {\n if (!firstTimestamp && record.timestamp) {\n firstTimestamp = record.timestamp;\n }\n if (record.timestamp) {\n lastTimestamp = record.timestamp;\n }\n if (!cwd && record.cwd) {\n cwd = record.cwd;\n }\n if (gitBranch === null && record.gitBranch) {\n gitBranch = record.gitBranch;\n }\n if (!model && record.type === \"assistant\") {\n const ar = record as AssistantRecord;\n if (ar.message.model && ar.message.model !== \"<synthetic>\") {\n model = ar.message.model;\n }\n }\n }\n\n function handleUserRecord(record: UserRecord, agentId: string | undefined) {\n const content = record.message.content;\n const isHumanTurn = typeof content === \"string\" && !record.isMeta;\n turns.push({\n role: \"user\",\n content: normalizeContent(content),\n usage: null,\n complete: true,\n timestamp: record.timestamp,\n isHumanTurn,\n agentId,\n });\n }\n\n function handleAssistantChunk(\n record: AssistantRecord,\n chunks: Map<string, AssistantAccumulator>,\n ) {\n const messageId = record.message.id;\n let acc = chunks.get(messageId);\n\n if (!acc) {\n acc = {\n content: [],\n usage: null,\n complete: false,\n timestamp: record.timestamp,\n model: record.message.model,\n };\n chunks.set(messageId, acc);\n }\n\n // Append content blocks from this streaming chunk\n for (const block of record.message.content) {\n acc.content.push(block);\n }\n\n // Final chunk has the real usage data\n if (record.message.stop_reason !== null) {\n acc.complete = true;\n acc.usage = record.message.usage;\n }\n }\n}\n\nfunction normalizeContent(content: string | ContentBlock[]): ContentBlock[] {\n if (typeof content === \"string\") {\n return [{ type: \"text\", text: content }];\n }\n return content;\n}\n\nconst SKIPPABLE = new Set([\n \"queue-operation\",\n \"attachment\",\n \"system\",\n \"last-prompt\",\n]);\n\nfunction isSkippable(type: string): boolean {\n return SKIPPABLE.has(type);\n}\n","/**\n * M1: Reads-before-edit ratio.\n *\n * Counts how many Read tool_use events occur before each Write or Edit event.\n * High values mean Claude is reading context before modifying files.\n * The AMD data showed this dropped from 6.6 to 2.0 after March 8.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nconst EDIT_TOOLS = new Set([\"Write\", \"Edit\", \"NotebookEdit\"]);\nconst READ_TOOL = \"Read\";\n\nexport function computeReadsPerEdit(session: Session): MetricResult {\n let readsSinceLastEdit = 0;\n const ratios: number[] = [];\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n\n if (toolBlock.name === READ_TOOL) {\n readsSinceLastEdit++;\n } else if (EDIT_TOOLS.has(toolBlock.name)) {\n ratios.push(readsSinceLastEdit);\n readsSinceLastEdit = 0;\n }\n }\n }\n\n if (ratios.length === 0) {\n return {\n name: \"reads-per-edit\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const average = ratios.reduce((a, b) => a + b, 0) / ratios.length;\n\n return {\n name: \"reads-per-edit\",\n value: round(average),\n status: average >= 4.0 ? \"healthy\" : average >= 2.0 ? \"warning\" : \"critical\",\n label: round(average).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M2: Full-file rewrite ratio.\n *\n * Ratio of Write calls (full file replacement) to total file modifications\n * (Write + Edit). Rising ratio means Claude is rewriting instead of\n * making surgical edits.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nexport function computeRewriteRatio(session: Session): MetricResult {\n let writes = 0;\n let edits = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n\n if (toolBlock.name === \"Write\") writes++;\n else if (toolBlock.name === \"Edit\" || toolBlock.name === \"NotebookEdit\") edits++;\n }\n }\n\n const total = writes + edits;\n if (total === 0) {\n return {\n name: \"rewrite-ratio\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const ratio = writes / total;\n\n return {\n name: \"rewrite-ratio\",\n value: round(ratio),\n status: ratio <= 0.25 ? \"healthy\" : ratio <= 0.5 ? \"warning\" : \"critical\",\n label: round(ratio).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M3: Cache hit rate.\n *\n * Ratio of cache_read_input_tokens to total input tokens\n * (cache_read + cache_creation). Detects the prompt cache bug\n * that caused 10-20x cost inflation.\n *\n * Note: raw `input_tokens` is always a streaming placeholder (1 or 3).\n * Real input cost = cache_read + cache_creation.\n */\n\nimport type { MetricResult, Session } from \"../parser/types.js\";\n\nexport function computeCacheHitRate(session: Session): MetricResult {\n let totalCacheRead = 0;\n let totalCacheCreation = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\" || !turn.usage || !turn.complete) continue;\n\n totalCacheRead += turn.usage.cache_read_input_tokens;\n totalCacheCreation += turn.usage.cache_creation_input_tokens;\n }\n\n const totalInput = totalCacheRead + totalCacheCreation;\n if (totalInput === 0) {\n return {\n name: \"cache-hit-rate\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No token usage data available\",\n };\n }\n\n const rate = totalCacheRead / totalInput;\n\n return {\n name: \"cache-hit-rate\",\n value: round(rate),\n status: rate >= 0.5 ? \"healthy\" : rate >= 0.2 ? \"warning\" : \"critical\",\n label: round(rate).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M4: Task completion rate.\n *\n * Detects sessions where Claude says it will do something but doesn't\n * follow through. Looks for intent phrases in assistant text that\n * aren't followed by a tool_use in the next assistant turn.\n */\n\nimport type { MetricResult, Session, MergedTurn, TextBlock, ToolUseBlock } from \"../parser/types.js\";\n\nconst INTENT_PATTERNS = [\n /\\bI'll now\\b/i,\n /\\bLet me\\b/i,\n /\\bI'll update\\b/i,\n /\\bNext,? I'll\\b/i,\n /\\bI'll (?:also |then )?(?:fix|add|create|implement|refactor|modify|change|write|edit|update)\\b/i,\n /\\bI'm going to\\b/i,\n];\n\nexport function computeTaskCompletion(session: Session): MetricResult {\n const assistantTurns = session.turns.filter(\n (t) => t.role === \"assistant\" && t.complete,\n );\n\n let totalIntents = 0;\n let unfulfilledIntents = 0;\n\n for (const turn of assistantTurns) {\n const hasIntent = hasIntentPhrase(turn);\n if (!hasIntent) continue;\n\n totalIntents++;\n\n // An intent is fulfilled if the same merged turn also contains a tool_use.\n // Since streaming chunks are merged, a real action within this turn means\n // Claude followed through. An intent without a tool_use in the same turn\n // is a dangling promise.\n const hasToolUse = turn.content.some((b) => b.type === \"tool_use\");\n if (!hasToolUse) {\n unfulfilledIntents++;\n }\n }\n\n if (totalIntents === 0) {\n return {\n name: \"task-completion\",\n value: 1,\n status: \"healthy\",\n label: \"1.00\",\n detail: \"No intent phrases detected\",\n };\n }\n\n const rate = 1 - unfulfilledIntents / totalIntents;\n\n return {\n name: \"task-completion\",\n value: round(rate),\n status: rate >= 0.9 ? \"healthy\" : rate >= 0.7 ? \"warning\" : \"critical\",\n label: round(rate).toString(),\n };\n}\n\nfunction hasIntentPhrase(turn: MergedTurn): boolean {\n for (const block of turn.content) {\n if (block.type === \"text\") {\n const textBlock = block as TextBlock;\n if (INTENT_PATTERNS.some((p) => p.test(textBlock.text))) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * Levenshtein distance and normalized similarity.\n * Pure implementation — no external dependencies.\n */\n\n/**\n * Compute the Levenshtein edit distance between two strings.\n * Uses a single-row DP approach for O(min(m,n)) space.\n */\nexport function levenshteinDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure a is the shorter string for space optimization\n if (a.length > b.length) [a, b] = [b, a];\n\n const aLen = a.length;\n const bLen = b.length;\n const row = new Array<number>(aLen + 1);\n\n for (let i = 0; i <= aLen; i++) row[i] = i;\n\n for (let j = 1; j <= bLen; j++) {\n let prev = row[0];\n row[0] = j;\n\n for (let i = 1; i <= aLen; i++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n const temp = row[i];\n row[i] = Math.min(\n row[i] + 1, // deletion\n row[i - 1] + 1, // insertion\n prev + cost, // substitution\n );\n prev = temp;\n }\n }\n\n return row[aLen];\n}\n\n/**\n * Compute normalized similarity between two strings (0 = different, 1 = identical).\n * Only compares the first `maxLen` characters for performance.\n */\nexport function normalizedSimilarity(\n a: string,\n b: string,\n maxLen = 200,\n): number {\n const aTrunc = a.slice(0, maxLen);\n const bTrunc = b.slice(0, maxLen);\n const maxLength = Math.max(aTrunc.length, bTrunc.length);\n\n if (maxLength === 0) return 1;\n\n const distance = levenshteinDistance(aTrunc, bTrunc);\n return 1 - distance / maxLength;\n}\n","/**\n * M5: Retry density.\n *\n * Measures how often the user sends messages very similar to their\n * previous message — a proxy for \"Claude got it wrong and I'm asking again.\"\n */\n\nimport type { MetricResult, Session, TextBlock } from \"../parser/types.js\";\nimport { normalizedSimilarity } from \"../utils/levenshtein.js\";\n\nexport function computeRetryDensity(session: Session): MetricResult {\n // Extract text from human-authored user turns only\n const humanTexts: string[] = [];\n for (const turn of session.turns) {\n if (!turn.isHumanTurn) continue;\n const text = turn.content\n .filter((b): b is TextBlock => b.type === \"text\")\n .map((b) => b.text)\n .join(\" \");\n if (text.length > 0) humanTexts.push(text);\n }\n\n if (humanTexts.length < 2) {\n return {\n name: \"retry-density\",\n value: 0,\n status: \"healthy\",\n label: \"0.00\",\n detail: \"Not enough user messages to detect retries\",\n };\n }\n\n let retries = 0;\n const pairs = humanTexts.length - 1;\n\n for (let i = 0; i < pairs; i++) {\n const similarity = normalizedSimilarity(humanTexts[i], humanTexts[i + 1]);\n if (similarity > 0.6) {\n retries++;\n }\n }\n\n const density = retries / pairs;\n\n return {\n name: \"retry-density\",\n value: round(density),\n status: density <= 0.1 ? \"healthy\" : density <= 0.25 ? \"warning\" : \"critical\",\n label: round(density).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M6: Tool diversity score.\n *\n * Shannon entropy over the distribution of tool_use events by tool name.\n * Normalized to 0-1. Low diversity means Claude is over-relying on\n * one tool (often Write).\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nexport function computeToolDiversity(session: Session): MetricResult {\n const toolCounts = new Map<string, number>();\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n toolCounts.set(toolBlock.name, (toolCounts.get(toolBlock.name) ?? 0) + 1);\n }\n }\n\n const uniqueTools = toolCounts.size;\n if (uniqueTools <= 1) {\n return {\n name: \"tool-diversity\",\n value: uniqueTools === 0 ? null : 0,\n status: uniqueTools === 0 ? \"healthy\" : \"critical\",\n label: uniqueTools === 0 ? \"N/A\" : \"0.00\",\n detail: uniqueTools === 0\n ? \"No tool usage in this session\"\n : `Only one tool used: ${[...toolCounts.keys()][0]}`,\n };\n }\n\n const totalCalls = [...toolCounts.values()].reduce((a, b) => a + b, 0);\n const maxEntropy = Math.log2(uniqueTools);\n\n let entropy = 0;\n for (const count of toolCounts.values()) {\n const p = count / totalCalls;\n entropy -= p * Math.log2(p);\n }\n\n const normalized = entropy / maxEntropy;\n\n // Build detail showing top tools\n const sorted = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]);\n const topTool = sorted[0];\n const topPercent = Math.round((topTool[1] / totalCalls) * 100);\n const detail = `Most used: ${topTool[0]} (${topPercent}%)`;\n\n return {\n name: \"tool-diversity\",\n value: round(normalized),\n status: normalized >= 0.6 ? \"healthy\" : normalized >= 0.4 ? \"warning\" : \"critical\",\n label: round(normalized).toString(),\n detail,\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M7: Tokens per useful edit.\n *\n * Total output tokens consumed divided by number of file modification\n * operations (Write + Edit). Rising ratio means Claude is burning more\n * tokens per productive action.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nconst EDIT_TOOLS = new Set([\"Write\", \"Edit\", \"NotebookEdit\"]);\n\nexport function computeTokensPerEdit(session: Session): MetricResult {\n let totalOutputTokens = 0;\n let editCount = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n // Count tokens from completed turns only\n if (turn.complete && turn.usage) {\n totalOutputTokens += turn.usage.output_tokens;\n }\n\n // Count edit operations\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n if (EDIT_TOOLS.has(toolBlock.name)) editCount++;\n }\n }\n\n if (editCount === 0) {\n return {\n name: \"tokens-per-edit\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const ratio = totalOutputTokens / editCount;\n\n return {\n name: \"tokens-per-edit\",\n value: Math.round(ratio),\n status: ratio <= 5000 ? \"healthy\" : ratio <= 15000 ? \"warning\" : \"critical\",\n label: Math.round(ratio).toLocaleString(\"en-US\"),\n };\n}\n","/**\n * M8: Subagent delegation ratio.\n *\n * Measures what fraction of total output tokens came from the main agent vs\n * subagents. A low main-agent ratio means the orchestrator delegated effectively.\n * Healthy = main agent produced < 60% of total output tokens.\n */\n\nimport type { MetricResult, Session } from \"../parser/types.js\";\n\nexport function computeSubagentOverhead(session: Session): MetricResult {\n if (session.subagentCount === 0) {\n return {\n name: \"subagent-overhead\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No subagents in this session\",\n };\n }\n\n let mainTokens = 0;\n let subagentTokens = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\" || !turn.usage) continue;\n const out = turn.usage.output_tokens ?? 0;\n if (turn.agentId === undefined) {\n mainTokens += out;\n } else {\n subagentTokens += out;\n }\n }\n\n const total = mainTokens + subagentTokens;\n if (total === 0) {\n return {\n name: \"subagent-overhead\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No output tokens recorded\",\n };\n }\n\n const ratio = mainTokens / total;\n const status = ratio < 0.6 ? \"healthy\" : ratio < 0.8 ? \"warning\" : \"critical\";\n\n return {\n name: \"subagent-overhead\",\n value: Math.round(ratio * 100) / 100,\n status,\n label: `${Math.round(ratio * 100)}% main`,\n };\n}\n","/**\n * Composite grading from all 8 quality metrics.\n *\n * Each metric is scored 0-100 based on its thresholds, then weighted\n * into a composite score mapped to a letter grade A+ through F.\n */\n\nimport type { GradeResult, MetricResult, Session } from \"../parser/types.js\";\nimport { computeReadsPerEdit } from \"./reads-per-edit.js\";\nimport { computeRewriteRatio } from \"./rewrite-ratio.js\";\nimport { computeCacheHitRate } from \"./cache-hit-rate.js\";\nimport { computeTaskCompletion } from \"./task-completion.js\";\nimport { computeRetryDensity } from \"./retry-density.js\";\nimport { computeToolDiversity } from \"./tool-diversity.js\";\nimport { computeTokensPerEdit } from \"./tokens-per-edit.js\";\nimport { computeSubagentOverhead } from \"./subagent-overhead.js\";\n\ninterface MetricWeight {\n compute: (session: Session) => MetricResult;\n weight: number;\n /** Convert metric value to 0-100 score. Higher is better. */\n score: (value: number) => number;\n}\n\nconst METRIC_WEIGHTS: MetricWeight[] = [\n {\n compute: computeReadsPerEdit,\n weight: 0.2,\n // 0 reads → 0, 2 reads → 50, 4+ reads → 100\n score: (v) => clamp(v / 4 * 100, 0, 100),\n },\n {\n compute: computeRewriteRatio,\n weight: 0.15,\n // 0 ratio → 100, 0.25 → 50, 0.5+ → 0 (inverted: lower is better)\n score: (v) => clamp((1 - v / 0.5) * 100, 0, 100),\n },\n {\n compute: computeCacheHitRate,\n weight: 0.15,\n // 0% → 0, 50% → 100\n score: (v) => clamp(v / 0.5 * 100, 0, 100),\n },\n {\n compute: computeTaskCompletion,\n weight: 0.15,\n // 0.7 → 0, 0.9 → 50, 1.0 → 100\n score: (v) => clamp((v - 0.7) / 0.3 * 100, 0, 100),\n },\n {\n compute: computeRetryDensity,\n weight: 0.1,\n // 0% → 100, 10% → 60, 25%+ → 0 (inverted)\n score: (v) => clamp((1 - v / 0.25) * 100, 0, 100),\n },\n {\n compute: computeToolDiversity,\n weight: 0.05,\n // 0 → 0, 0.4 → 50, 0.6+ → 100\n score: (v) => clamp(v / 0.6 * 100, 0, 100),\n },\n {\n compute: computeTokensPerEdit,\n weight: 0.15,\n // 5000 → 100, 10000 → 50, 15000+ → 0 (inverted)\n score: (v) => clamp((1 - (v - 5000) / 10000) * 100, 0, 100),\n },\n {\n compute: computeSubagentOverhead,\n weight: 0.05,\n // main ratio 0 → 100, 0.6 → 100 (threshold), 0.8 → 50, 1.0 → 0 (inverted)\n score: (v) => clamp((1 - v) / 0.4 * 100, 0, 100),\n },\n];\n\nconst GRADE_THRESHOLDS: Array<[number, string]> = [\n [97, \"A+\"],\n [93, \"A\"],\n [90, \"A-\"],\n [87, \"B+\"],\n [83, \"B\"],\n [80, \"B-\"],\n [77, \"C+\"],\n [73, \"C\"],\n [70, \"C-\"],\n [67, \"D+\"],\n [63, \"D\"],\n [60, \"D-\"],\n [0, \"F\"],\n];\n\nexport function gradeSession(session: Session): GradeResult {\n const metrics: MetricResult[] = [];\n let weightedSum = 0;\n let totalWeight = 0;\n\n for (const mw of METRIC_WEIGHTS) {\n const result = mw.compute(session);\n metrics.push(result);\n\n if (result.value !== null) {\n weightedSum += mw.score(result.value) * mw.weight;\n totalWeight += mw.weight;\n }\n }\n\n // Normalize if some metrics returned null (insufficient data)\n const compositeScore = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const letter =\n GRADE_THRESHOLDS.find(([threshold]) => compositeScore >= threshold)?.[1] ?? \"F\";\n\n return {\n letter,\n score: Math.round(compositeScore),\n metrics,\n };\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n","/**\n * Terminal output formatting using chalk and cli-table3.\n */\n\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\nimport type { GradeResult, MetricResult, Session } from \"../parser/types.js\";\nimport type { RegressionResult } from \"../anomaly/regression-detector.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\nimport { formatDuration, shortSessionId, projectNameFromSlug, formatNumber } from \"../utils/format.js\";\nimport { getAllTips } from \"./tips.js\";\n\nconst STATUS_ICONS: Record<string, string> = {\n healthy: chalk.green(\"✓\"),\n warning: chalk.yellow(\"⚠\"),\n critical: chalk.red(\"✗\"),\n};\n\nconst STATUS_LABELS: Record<string, string> = {\n healthy: chalk.green(\"healthy\"),\n warning: chalk.yellow(\"warning\"),\n critical: chalk.red(\"critical\"),\n};\n\nconst METRIC_DISPLAY_NAMES: Record<string, string> = {\n \"reads-per-edit\": \"Reads/edit\",\n \"rewrite-ratio\": \"Rewrite ratio\",\n \"cache-hit-rate\": \"Cache hit rate\",\n \"task-completion\": \"Task completion\",\n \"retry-density\": \"Retry density\",\n \"tool-diversity\": \"Tool diversity\",\n \"tokens-per-edit\": \"Tokens/useful-edit\",\n \"subagent-overhead\": \"Subagent delegation\",\n};\n\nexport function renderAuditReport(session: Session, grade: GradeResult): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(\" inspecto v1.0.0\") + chalk.dim(\" — Claude Code Session Quality Analyzer\"));\n lines.push(\"\");\n\n const agentInfo = session.subagentCount > 0\n ? `${session.subagentCount} subagents | ${session.turns.length} turns`\n : `${session.turns.length} turns`;\n\n const sessionInfo = [\n `Session: ${chalk.cyan(shortSessionId(session.id))}`,\n projectNameFromSlug(session.projectSlug),\n formatDuration(session.durationMs),\n session.model,\n agentInfo,\n ].join(chalk.dim(\" | \"));\n lines.push(` ${sessionInfo}`);\n lines.push(\"\");\n\n const gradeColor = getGradeColor(grade.letter);\n lines.push(` Overall grade: ${gradeColor(chalk.bold(grade.letter))}`);\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Metric\", \"Value\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const metric of grade.metrics) {\n const displayName = METRIC_DISPLAY_NAMES[metric.name] ?? metric.name;\n const icon = STATUS_ICONS[metric.status] ?? \"\";\n table.push([displayName, metric.label, `${icon} ${STATUS_LABELS[metric.status] ?? metric.status}`]);\n }\n\n lines.push(table.toString());\n\n const tips = getAllTips(grade.metrics);\n if (tips.length > 0) {\n lines.push(\"\");\n lines.push(chalk.yellow(\" Tips:\"));\n for (const tip of tips) {\n lines.push(` ${chalk.dim(\"→\")} ${tip}`);\n }\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nexport function renderTrendReport(\n results: RegressionResult[],\n sessionCount: number,\n period: string,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(` Trend report: last ${period}`) + chalk.dim(` (${sessionCount} sessions)`));\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Metric\", \"Recent avg\", \"Full avg\", \"Change\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const result of results) {\n const displayName = METRIC_DISPLAY_NAMES[result.name] ?? result.name;\n const recentStr = result.recentAvg !== null ? result.recentAvg.toFixed(2) : \"N/A\";\n const fullStr = result.fullAvg !== null ? result.fullAvg.toFixed(2) : \"N/A\";\n\n let changeStr = \"N/A\";\n if (result.changePercent !== null) {\n const arrow = result.changePercent > 0 ? \"▲\" : result.changePercent < 0 ? \"▼\" : \"\";\n changeStr = `${arrow} ${Math.abs(Math.round(result.changePercent))}%`;\n }\n\n const statusStr = formatRegressionStatus(result.status);\n table.push([displayName, recentStr, fullStr, changeStr, statusStr]);\n }\n\n lines.push(table.toString());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nexport function renderCacheCheckReport(results: CacheCheckResult[]): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(\" Cache health check\"));\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Session\", \"Project\", \"Cache Hit\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const result of results) {\n const hitStr = result.cacheHitRate !== null ? result.cacheHitRate.toFixed(2) : \"N/A\";\n const statusStr = result.isAnomaly\n ? chalk.red(\"✗ ANOMALY\")\n : chalk.green(\"✓ normal\");\n\n table.push([\n shortSessionId(result.sessionId),\n projectNameFromSlug(result.projectSlug),\n hitStr,\n statusStr,\n ]);\n }\n\n lines.push(table.toString());\n\n const anomalies = results.filter((r) => r.isAnomaly);\n if (anomalies.length > 0) {\n lines.push(\"\");\n lines.push(\n chalk.yellow(` ⚠ ${anomalies.length} session(s) with abnormally low cache hit rate.`),\n );\n for (const a of anomalies) {\n if (a.estimatedInflation) {\n lines.push(\n ` ${chalk.dim(\"→\")} Session ${shortSessionId(a.sessionId)} consumed ~${a.estimatedInflation}x more input tokens than expected.`,\n );\n }\n }\n lines.push(\n chalk.dim(\" Try: restart Claude Code or downgrade to a previous version.\"),\n );\n } else {\n lines.push(\"\");\n lines.push(chalk.green(\" ✓ No cache anomalies detected.\"));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nfunction getGradeColor(letter: string): (text: string) => string {\n if (letter.startsWith(\"A\")) return chalk.green;\n if (letter.startsWith(\"B\")) return chalk.cyan;\n if (letter.startsWith(\"C\")) return chalk.yellow;\n return chalk.red;\n}\n\nfunction formatRegressionStatus(status: string): string {\n switch (status) {\n case \"stable\":\n return chalk.green(\"✓ stable\");\n case \"declining\":\n return chalk.yellow(\"⚠ declining\");\n case \"regression\":\n return chalk.red(\"⚠ REGRESSION\");\n default:\n return status;\n }\n}\n","/**\n * Number and string formatting helpers for terminal output.\n */\n\n/** Format a number with comma separators: 3218 → \"3,218\" */\nexport function formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n/** Format a ratio as a fixed-2 decimal: 0.734 → \"0.73\" */\nexport function formatRatio(n: number): string {\n return n.toFixed(2);\n}\n\n/** Format a percentage: 0.734 → \"73%\" */\nexport function formatPercent(n: number): string {\n return `${Math.round(n * 100)}%`;\n}\n\n/** Format milliseconds into a human-readable duration: 2820000 → \"47 min\" */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes} min`;\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n if (remainingMinutes === 0) return `${hours}h`;\n return `${hours}h ${remainingMinutes}m`;\n}\n\n/** Truncate a session ID for display: \"31f3f224-abcd-...\" → \"31f3f224\" */\nexport function shortSessionId(id: string): string {\n return id.slice(0, 8);\n}\n\n/** Extract a human-readable project name from a slug like \"-Users-foo-my-app\" */\nexport function projectNameFromSlug(slug: string): string {\n const parts = slug.split(\"-\").filter(Boolean);\n return parts[parts.length - 1] || slug;\n}\n","/**\n * Contextual tips based on metric values.\n * Maps poor-scoring metrics to actionable suggestions.\n */\n\nimport type { MetricResult } from \"../parser/types.js\";\n\nconst TIPS: Record<string, Record<string, string>> = {\n \"reads-per-edit\": {\n warning: \"Claude is editing with less context. Add 'Always read files before editing' to your CLAUDE.md.\",\n critical: \"Very low reads before edits. Claude is making blind changes. Consider adding explicit read instructions.\",\n },\n \"rewrite-ratio\": {\n warning: \"High ratio of full-file rewrites. Add 'Prefer Edit over Write for existing files' to CLAUDE.md.\",\n critical: \"Claude is rewriting entire files instead of making surgical edits. This wastes tokens and risks data loss.\",\n },\n \"cache-hit-rate\": {\n warning: \"Cache hit rate is below normal. Sessions may be too short for caching to help.\",\n critical: \"Very low cache hit rate — possible cache bug. Try restarting Claude Code or downgrading to a previous version.\",\n },\n \"task-completion\": {\n warning: \"Claude is occasionally promising actions without following through.\",\n critical: \"Frequent unfulfilled promises. Claude says it will do things but doesn't. Try breaking tasks into smaller steps.\",\n },\n \"retry-density\": {\n warning: \"Some user messages look like retries. Claude may be misunderstanding requests.\",\n critical: \"High retry rate. Users are frequently re-asking. Consider providing more context in prompts.\",\n },\n \"tool-diversity\": {\n warning: \"Low tool diversity. Claude is over-relying on a narrow set of tools.\",\n critical: \"Very narrow tool usage. Claude may be stuck in a pattern. Try prompting for specific tool usage.\",\n },\n \"tokens-per-edit\": {\n warning: \"Tokens per edit is above average. Claude may be verbose without being productive.\",\n critical: \"Very high token cost per edit. Claude is burning tokens without proportional output.\",\n },\n};\n\nexport function getTip(metric: MetricResult): string | null {\n if (metric.status === \"healthy\") return null;\n\n const metricTips = TIPS[metric.name];\n if (!metricTips) return null;\n\n return metricTips[metric.status] ?? null;\n}\n\nexport function getAllTips(metrics: MetricResult[]): string[] {\n return metrics\n .map(getTip)\n .filter((tip): tip is string => tip !== null);\n}\n","/**\n * JSON output mode for scripting and CI.\n */\n\nimport type { GradeResult, Session } from \"../parser/types.js\";\nimport type { RegressionResult } from \"../anomaly/regression-detector.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\n\nexport interface AuditJsonOutput {\n session: {\n id: string;\n project: string;\n model: string;\n durationMs: number;\n startTime: string;\n };\n grade: string;\n score: number;\n metrics: Array<{\n name: string;\n value: number | null;\n status: string;\n label: string;\n }>;\n}\n\nexport function formatAuditJson(session: Session, grade: GradeResult): string {\n const output: AuditJsonOutput = {\n session: {\n id: session.id,\n project: session.projectSlug,\n model: session.model,\n durationMs: session.durationMs,\n startTime: session.startTime,\n },\n grade: grade.letter,\n score: grade.score,\n metrics: grade.metrics.map((m) => ({\n name: m.name,\n value: m.value,\n status: m.status,\n label: m.label,\n })),\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nexport function formatTrendJson(results: RegressionResult[]): string {\n return JSON.stringify({ trend: results }, null, 2);\n}\n\nexport function formatCacheCheckJson(results: CacheCheckResult[]): string {\n return JSON.stringify({ cacheCheck: results }, null, 2);\n}\n","/**\n * Default command — grade the most recent session.\n */\n\nimport { getMostRecentSession } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession } from \"../metrics/grader.js\";\nimport { renderAuditReport } from \"../reporter/terminal.js\";\nimport { formatAuditJson } from \"../reporter/json-reporter.js\";\n\nexport interface AuditOptions {\n json?: boolean;\n verbose?: boolean;\n dataDir?: string;\n project?: string;\n}\n\nexport async function runAudit(options: AuditOptions): Promise<void> {\n const sessionFile = await getMostRecentSession({\n dataDir: options.dataDir,\n project: options.project,\n });\n\n const records = readJsonl(sessionFile.path);\n const session = await buildSession(\n records,\n sessionFile.sessionId,\n sessionFile.projectSlug,\n sessionFile.subagentPaths,\n );\n\n const grade = gradeSession(session);\n\n if (options.json) {\n console.log(formatAuditJson(session, grade));\n } else {\n console.log(renderAuditReport(session, grade));\n }\n}\n","/**\n * Compute rolling averages from multiple sessions for trend analysis.\n */\n\nimport type { GradeResult } from \"../parser/types.js\";\n\nexport interface MetricAverage {\n name: string;\n recentAvg: number | null;\n fullAvg: number | null;\n changePercent: number | null;\n}\n\n/**\n * Compute per-metric averages for a recent window vs. full range.\n * @param grades - All graded sessions, sorted most recent first\n * @param recentCount - Number of sessions in the \"recent\" window\n */\nexport function computeBaselines(\n grades: GradeResult[],\n recentCount: number,\n): MetricAverage[] {\n if (grades.length === 0) return [];\n\n const recent = grades.slice(0, recentCount);\n const full = grades;\n\n const metricNames = grades[0].metrics.map((m) => m.name);\n\n return metricNames.map((name) => {\n const recentValues = extractValues(recent, name);\n const fullValues = extractValues(full, name);\n\n const recentAvg = average(recentValues);\n const fullAvg = average(fullValues);\n\n let changePercent: number | null = null;\n if (recentAvg !== null && fullAvg !== null && fullAvg !== 0) {\n changePercent = ((recentAvg - fullAvg) / Math.abs(fullAvg)) * 100;\n }\n\n return { name, recentAvg, fullAvg, changePercent };\n });\n}\n\nfunction extractValues(grades: GradeResult[], metricName: string): number[] {\n const values: number[] = [];\n for (const grade of grades) {\n const metric = grade.metrics.find((m) => m.name === metricName);\n if (metric?.value !== null && metric?.value !== undefined) {\n values.push(metric.value);\n }\n }\n return values;\n}\n\nfunction average(values: number[]): number | null {\n if (values.length === 0) return null;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n","/**\n * Z-score based regression detection.\n *\n * Compares recent metric values against historical baseline to flag\n * statistically significant regressions.\n */\n\nimport type { MetricAverage } from \"./baseline.js\";\n\nexport type RegressionStatus = \"stable\" | \"declining\" | \"regression\";\n\nexport interface RegressionResult {\n name: string;\n recentAvg: number | null;\n fullAvg: number | null;\n changePercent: number | null;\n status: RegressionStatus;\n}\n\n/** Metrics where HIGHER values are WORSE (inverted for regression detection). */\nconst INVERTED_METRICS = new Set([\n \"rewrite-ratio\",\n \"retry-density\",\n \"tokens-per-edit\",\n]);\n\n/**\n * Detect regressions from baseline averages.\n * A change > 30% in the \"bad\" direction is a regression.\n * A change > 10% is \"declining\".\n */\nexport function detectRegressions(\n baselines: MetricAverage[],\n): RegressionResult[] {\n return baselines.map((b) => {\n let status: RegressionStatus = \"stable\";\n\n if (b.changePercent !== null) {\n const isInverted = INVERTED_METRICS.has(b.name);\n // For normal metrics, negative change is bad. For inverted, positive change is bad.\n const badDirection = isInverted ? b.changePercent > 0 : b.changePercent < 0;\n const magnitude = Math.abs(b.changePercent);\n\n if (badDirection && magnitude > 30) {\n status = \"regression\";\n } else if (badDirection && magnitude > 10) {\n status = \"declining\";\n }\n }\n\n return {\n name: b.name,\n recentAvg: b.recentAvg,\n fullAvg: b.fullAvg,\n changePercent: b.changePercent,\n status,\n };\n });\n}\n","/**\n * Parse human-readable duration strings into Date offsets.\n */\n\n/**\n * Parse a duration string like \"7d\", \"14d\", \"30d\" into a Date\n * representing that many days before `now`.\n */\nexport function parseDuration(duration: string, now = new Date()): Date {\n const match = duration.match(/^(\\d+)d$/);\n if (!match) {\n throw new Error(\n `Invalid duration: \"${duration}\". Use format like \"7d\", \"14d\", \"30d\".`,\n );\n }\n\n const days = parseInt(match[1], 10);\n const result = new Date(now);\n result.setDate(result.getDate() - days);\n return result;\n}\n","/**\n * Trend analysis command — detect regressions over time.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession } from \"../metrics/grader.js\";\nimport { computeBaselines } from \"../anomaly/baseline.js\";\nimport { detectRegressions } from \"../anomaly/regression-detector.js\";\nimport { renderTrendReport } from \"../reporter/terminal.js\";\nimport { formatTrendJson } from \"../reporter/json-reporter.js\";\nimport { parseDuration } from \"../utils/duration.js\";\nimport type { GradeResult } from \"../parser/types.js\";\n\nexport interface TrendOptions {\n since?: string;\n json?: boolean;\n dataDir?: string;\n project?: string;\n}\n\nexport async function runTrend(options: TrendOptions): Promise<void> {\n const duration = options.since ?? \"7d\";\n const sinceDate = parseDuration(duration);\n\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: options.project,\n since: sinceDate,\n });\n\n if (sessionFiles.length === 0) {\n console.log(`No sessions found in the last ${duration}.`);\n return;\n }\n\n const settled = await Promise.allSettled(\n sessionFiles.map(async (sf) => {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);\n return gradeSession(session);\n }),\n );\n const grades: GradeResult[] = settled\n .filter((r): r is PromiseFulfilledResult<GradeResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (grades.length === 0) {\n console.log(\"No valid sessions found to analyze.\");\n return;\n }\n\n // Use half the sessions as the \"recent\" window, minimum 1\n const recentCount = Math.max(1, Math.floor(grades.length / 2));\n const baselines = computeBaselines(grades, recentCount);\n const regressions = detectRegressions(baselines);\n\n if (options.json) {\n console.log(formatTrendJson(regressions));\n } else {\n console.log(renderTrendReport(regressions, grades.length, duration));\n }\n}\n","/**\n * Cache hit rate anomaly detection.\n *\n * Specifically checks for the prompt cache bug that caused 10-20x\n * token cost inflation by detecting sessions with near-zero cache hit rates.\n */\n\nimport type { Session } from \"../parser/types.js\";\nimport { computeCacheHitRate } from \"../metrics/cache-hit-rate.js\";\n\nexport interface CacheCheckResult {\n sessionId: string;\n projectSlug: string;\n timestamp: string;\n cacheHitRate: number | null;\n isAnomaly: boolean;\n estimatedInflation: number | null;\n}\n\nconst ANOMALY_THRESHOLD = 0.05;\nconst NORMAL_CACHE_RATE = 0.65;\n\n/**\n * Check a single session for cache hit rate anomalies.\n */\nexport function checkCacheAnomaly(session: Session): CacheCheckResult {\n const metric = computeCacheHitRate(session);\n\n const isAnomaly = metric.value !== null && metric.value < ANOMALY_THRESHOLD;\n\n let estimatedInflation: number | null = null;\n if (isAnomaly && metric.value !== null) {\n // If normal rate is 65% cache reads, the effective input cost multiplier\n // when cache is broken is roughly 1 / (1 - normalRate)\n // Normal: 35% full-price tokens. Broken: 100% full-price tokens.\n estimatedInflation = Math.round(1 / (1 - NORMAL_CACHE_RATE));\n }\n\n return {\n sessionId: session.id,\n projectSlug: session.projectSlug,\n timestamp: session.startTime,\n cacheHitRate: metric.value,\n isAnomaly,\n estimatedInflation,\n };\n}\n","/**\n * Cache bug detection command.\n * Scans recent sessions for abnormally low cache hit rates.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { checkCacheAnomaly } from \"../anomaly/cache-anomaly.js\";\nimport { renderCacheCheckReport } from \"../reporter/terminal.js\";\nimport { formatCacheCheckJson } from \"../reporter/json-reporter.js\";\nimport { parseDuration } from \"../utils/duration.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\n\nexport interface CacheCheckOptions {\n since?: string;\n json?: boolean;\n dataDir?: string;\n}\n\nexport async function runCacheCheck(options: CacheCheckOptions): Promise<void> {\n const duration = options.since ?? \"7d\";\n const sinceDate = parseDuration(duration);\n\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n since: sinceDate,\n });\n\n if (sessionFiles.length === 0) {\n console.log(`No sessions found in the last ${duration}.`);\n return;\n }\n\n const settled = await Promise.allSettled(\n sessionFiles.map(async (sf) => {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);\n return checkCacheAnomaly(session);\n }),\n );\n const results: CacheCheckResult[] = settled\n .filter((r): r is PromiseFulfilledResult<CacheCheckResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (results.length === 0) {\n console.log(\"No valid sessions found to analyze.\");\n return;\n }\n\n if (options.json) {\n console.log(formatCacheCheckJson(results));\n } else {\n console.log(renderCacheCheckReport(results));\n }\n}\n","/**\n * Cross-project comparison command.\n * Compares average quality metrics across multiple projects.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession } from \"../metrics/grader.js\";\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\nimport type { GradeResult } from \"../parser/types.js\";\nimport { projectNameFromSlug } from \"../utils/format.js\";\n\nexport interface CompareOptions {\n projects: string;\n json?: boolean;\n dataDir?: string;\n since?: string;\n}\n\ninterface ProjectSummary {\n name: string;\n sessionCount: number;\n avgGrade: number;\n avgLetter: string;\n metrics: Map<string, number>;\n}\n\nexport async function runCompare(options: CompareOptions): Promise<void> {\n const projectNames = options.projects.split(\",\").map((p) => p.trim());\n const summaries: ProjectSummary[] = [];\n\n const projectSummaries = await Promise.all(\n projectNames.map(async (projectFilter) => {\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: projectFilter,\n });\n\n if (sessionFiles.length === 0) return null;\n\n const settled = await Promise.allSettled(\n sessionFiles.map(async (sf) => {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);\n return gradeSession(session);\n }),\n );\n const grades: GradeResult[] = settled\n .filter((r): r is PromiseFulfilledResult<GradeResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (grades.length === 0) return null;\n\n const avgScore = grades.reduce((s, g) => s + g.score, 0) / grades.length;\n const metricAvgs = new Map<string, number>();\n for (const metric of grades[0].metrics) {\n const values = grades\n .map((g) => g.metrics.find((m) => m.name === metric.name)?.value)\n .filter((v): v is number => v !== null);\n if (values.length > 0) {\n metricAvgs.set(metric.name, values.reduce((a, b) => a + b, 0) / values.length);\n }\n }\n\n return {\n name: projectFilter,\n sessionCount: grades.length,\n avgGrade: Math.round(avgScore),\n avgLetter: getLetterGrade(avgScore),\n metrics: metricAvgs,\n } as ProjectSummary;\n }),\n );\n\n for (const s of projectSummaries) {\n if (s !== null) summaries.push(s);\n }\n\n if (summaries.length === 0) {\n console.log(\"No matching projects found.\");\n return;\n }\n\n if (options.json) {\n const jsonOutput = summaries.map((s) => ({\n project: s.name,\n sessions: s.sessionCount,\n grade: s.avgLetter,\n score: s.avgGrade,\n metrics: Object.fromEntries(s.metrics),\n }));\n console.log(JSON.stringify({ compare: jsonOutput }, null, 2));\n return;\n }\n\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(chalk.bold(\" Project comparison\"));\n lines.push(\"\");\n\n const head = [\"Project\", \"Sessions\", \"Grade\", ...summaries[0]?.metrics.keys() ?? []].map(\n (h) => chalk.dim(h),\n );\n\n const table = new Table({\n head,\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const summary of summaries) {\n const row: string[] = [\n summary.name,\n summary.sessionCount.toString(),\n summary.avgLetter,\n ];\n for (const [, value] of summary.metrics) {\n row.push(value.toFixed(2));\n }\n table.push(row);\n }\n\n lines.push(table.toString());\n lines.push(\"\");\n console.log(lines.join(\"\\n\"));\n}\n\nfunction getLetterGrade(score: number): string {\n if (score >= 97) return \"A+\";\n if (score >= 93) return \"A\";\n if (score >= 90) return \"A-\";\n if (score >= 87) return \"B+\";\n if (score >= 83) return \"B\";\n if (score >= 80) return \"B-\";\n if (score >= 77) return \"C+\";\n if (score >= 73) return \"C\";\n if (score >= 70) return \"C-\";\n if (score >= 67) return \"D+\";\n if (score >= 63) return \"D\";\n if (score >= 60) return \"D-\";\n return \"F\";\n}\n"],"mappings":"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","names":["join","join","basename","basename","average","round","round","round","round","round","EDIT_TOOLS","chalk","Table"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/audit.ts","../src/parser/project-scanner.ts","../src/utils/paths.ts","../src/parser/jsonl-reader.ts","../src/parser/session-builder.ts","../src/parser/types.ts","../src/metrics/reads-per-edit.ts","../src/metrics/rewrite-ratio.ts","../src/metrics/cache-hit-rate.ts","../src/metrics/task-completion.ts","../src/utils/levenshtein.ts","../src/metrics/retry-density.ts","../src/metrics/tool-diversity.ts","../src/metrics/tokens-per-edit.ts","../src/metrics/subagent-overhead.ts","../src/metrics/grader.ts","../src/reporter/terminal.ts","../src/utils/format.ts","../src/reporter/tips.ts","../src/version.ts","../src/reporter/json-reporter.ts","../src/anomaly/baseline.ts","../src/anomaly/regression-detector.ts","../src/utils/duration.ts","../src/utils/concurrent.ts","../src/cache/grade-cache.ts","../src/commands/trend.ts","../src/anomaly/cache-anomaly.ts","../src/commands/cache-check.ts","../src/commands/compare.ts"],"sourcesContent":["/**\n * inspecto — Claude Code Session Quality Analyzer\n *\n * Grade sessions, detect regressions, catch cache bugs.\n * All from the JSONL logs Claude Code already writes.\n */\n\nimport { Command } from \"commander\";\nimport { unlink } from \"node:fs/promises\";\nimport { runAudit } from \"./commands/audit.js\";\nimport { runTrend } from \"./commands/trend.js\";\nimport { runCacheCheck } from \"./commands/cache-check.js\";\nimport { runCompare } from \"./commands/compare.js\";\nimport { getCacheFilePath } from \"./utils/paths.js\";\nimport { VERSION } from \"./version.js\";\n\nconst program = new Command();\n\nprogram\n .name(\"inspecto\")\n .description(\"Claude Code session quality analyzer — grade sessions, detect regressions, catch cache bugs\")\n .version(VERSION);\n\nprogram\n .command(\"audit\", { isDefault: true })\n .description(\"Grade the most recent Claude Code session\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--verbose\", \"Show per-message breakdown\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--project <name>\", \"Filter to a specific project\")\n .action(async (options) => {\n try {\n await runAudit(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"trend\")\n .description(\"Analyze quality trends and detect regressions over time\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\", \"7d\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--project <name>\", \"Filter to a specific project\")\n .action(async (options) => {\n try {\n await runTrend(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"cache-check\")\n .description(\"Detect prompt cache bugs that inflate token costs\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\", \"7d\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .action(async (options) => {\n try {\n await runCacheCheck(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nprogram\n .command(\"compare\")\n .description(\"Compare quality metrics across projects\")\n .requiredOption(\"--projects <names>\", \"Comma-separated project names\")\n .option(\"--json\", \"Output as JSON\")\n .option(\"--data-dir <path>\", \"Custom Claude data directory\")\n .option(\"--since <duration>\", \"Time range: 7d, 14d, 30d\")\n .action(async (options) => {\n try {\n await runCompare(options);\n } catch (error) {\n handleError(error);\n }\n });\n\nconst cache = program\n .command(\"cache\")\n .description(\"Manage the inspecto grade cache\");\n\ncache\n .command(\"clear\")\n .description(\"Delete the grade cache file (~/.claude/inspecto-cache.db)\")\n .action(async () => {\n try {\n const cachePath = getCacheFilePath();\n try {\n await unlink(cachePath);\n console.log(`Cache cleared: ${cachePath}`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.log(\"No cache file found.\");\n } else {\n throw err;\n }\n }\n } catch (error) {\n handleError(error);\n }\n });\n\nfunction handleError(error: unknown): void {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`\\nError: ${message}\\n`);\n process.exit(1);\n}\n\nprogram.parse();\n","/**\n * Default command — grade the most recent session.\n */\n\nimport chalk from \"chalk\";\nimport { getMostRecentSession } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession } from \"../metrics/grader.js\";\nimport { renderAuditReport } from \"../reporter/terminal.js\";\nimport { formatAuditJson } from \"../reporter/json-reporter.js\";\n\nconst KNOWN_FORMAT_VERSION = \"2.1.167\";\n\nexport interface AuditOptions {\n json?: boolean;\n verbose?: boolean;\n dataDir?: string;\n project?: string;\n}\n\nexport async function runAudit(options: AuditOptions): Promise<void> {\n const sessionFile = await getMostRecentSession({\n dataDir: options.dataDir,\n project: options.project,\n });\n\n const records = readJsonl(sessionFile.path);\n const session = await buildSession(\n records,\n sessionFile.sessionId,\n sessionFile.projectSlug,\n sessionFile.subagentPaths,\n );\n\n const grade = gradeSession(session);\n\n if (options.json) {\n console.log(formatAuditJson(session, grade));\n return;\n }\n\n if (session.formatVersion && session.formatVersion !== KNOWN_FORMAT_VERSION) {\n console.log(\n chalk.yellow(\n `⚠ JSONL format version ${session.formatVersion} detected (expected ${KNOWN_FORMAT_VERSION}). Metrics may be inaccurate.`,\n ),\n );\n }\n\n if (session.unknownRecordTypes.size > 0) {\n const types = [...session.unknownRecordTypes].sort().join(\", \");\n process.stdout.write(chalk.dim(`Note: skipped unknown record types: ${types}\\n`));\n }\n\n console.log(renderAuditReport(session, grade));\n}\n","/**\n * Discovers Claude Code session files under ~/.claude/projects/.\n *\n * Session files are at: ~/.claude/projects/{project-slug}/{sessionId}.jsonl\n * Subagent files are at: ~/.claude/projects/{project-slug}/{sessionId}/subagents/agent-*.jsonl\n */\n\nimport { readdir, stat } from \"node:fs/promises\";\nimport { join, basename, extname } from \"node:path\";\nimport { getClaudeDir } from \"../utils/paths.js\";\nimport type { SessionFile } from \"./types.js\";\n\n/**\n * Scan ~/.claude/projects/ for all main session JSONL files.\n * Returns files sorted by modification time (most recent first).\n */\nexport async function scanSessions(options?: {\n dataDir?: string;\n project?: string;\n since?: Date;\n}): Promise<SessionFile[]> {\n const claudeDir = options?.dataDir ?? getClaudeDir();\n const projectsDir = join(claudeDir, \"projects\");\n\n let projectDirs: string[];\n try {\n projectDirs = await readdir(projectsDir);\n } catch {\n throw new Error(\n \"Claude Code data directory not found. \" +\n \"Make sure Claude Code is installed and has been used at least once.\\n\" +\n `Expected: ${projectsDir}`,\n );\n }\n\n // Filter to specific project if requested\n if (options?.project) {\n projectDirs = projectDirs.filter((dir) =>\n dir.toLowerCase().includes(options.project!.toLowerCase()),\n );\n }\n\n const projectResults = await Promise.all(\n projectDirs\n .filter((dir) => !dir.startsWith(\".\"))\n .map(async (projectDir) => {\n const fullProjectDir = join(projectsDir, projectDir);\n let entries: string[];\n try {\n entries = await readdir(fullProjectDir);\n } catch {\n return [] as SessionFile[];\n }\n\n const fileResults = await Promise.all(\n entries\n .filter((entry) => extname(entry) === \".jsonl\")\n .map(async (entry) => {\n const filePath = join(fullProjectDir, entry);\n const sessionId = basename(entry, \".jsonl\");\n try {\n const fileStat = await stat(filePath);\n if (options?.since && fileStat.mtime < options.since) return null;\n\n let subagentPaths: string[] | undefined;\n try {\n const subagentDir = join(fullProjectDir, sessionId, \"subagents\");\n const agentFiles = await readdir(subagentDir);\n const paths = agentFiles\n .filter((f) => f.startsWith(\"agent-\") && f.endsWith(\".jsonl\"))\n .map((f) => join(subagentDir, f));\n if (paths.length > 0) subagentPaths = paths;\n } catch {\n // No subagents directory — normal for older sessions\n }\n\n return {\n path: filePath,\n sessionId,\n projectSlug: projectDir,\n mtime: fileStat.mtime,\n subagentPaths,\n } as SessionFile;\n } catch {\n return null;\n }\n }),\n );\n return fileResults.filter((f): f is SessionFile => f !== null);\n }),\n );\n\n const sessions: SessionFile[] = projectResults.flat();\n\n // Sort most recent first\n sessions.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n return sessions;\n}\n\n/**\n * Get the most recent session file, optionally filtered by project.\n */\nexport async function getMostRecentSession(options?: {\n dataDir?: string;\n project?: string;\n}): Promise<SessionFile> {\n const sessions = await scanSessions(options);\n if (sessions.length === 0) {\n throw new Error(\n \"No Claude Code sessions found. \" +\n \"Use Claude Code in a project first to generate session data.\",\n );\n }\n return sessions[0];\n}\n","/**\n * Cross-platform path resolution for Claude Code data directories.\n */\n\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\n/**\n * Returns the Claude Code data directory.\n * macOS/Linux: ~/.claude\n * Windows: %USERPROFILE%\\.claude\n */\nexport function getClaudeDir(): string {\n return join(homedir(), \".claude\");\n}\n\nexport function getCacheFilePath(): string {\n return join(getClaudeDir(), \"inspecto-cache.db\");\n}\n","/**\n * Streaming JSONL reader using Node's readline + createReadStream.\n * Processes files line-by-line to handle 100MB+ session files without\n * loading them into memory.\n */\n\nimport { createReadStream } from \"node:fs\";\nimport { createInterface } from \"node:readline\";\nimport type { RawRecord } from \"./types.js\";\n\n/**\n * Stream-reads a JSONL file, yielding one parsed record per line.\n * Malformed lines are silently skipped (common near session end during crashes).\n */\nexport async function* readJsonl(filePath: string): AsyncGenerator<RawRecord> {\n const stream = createReadStream(filePath, { encoding: \"utf-8\" });\n const rl = createInterface({ input: stream, crlfDelay: Infinity });\n\n for await (const line of rl) {\n const trimmed = line.trim();\n if (trimmed.length === 0) continue;\n\n try {\n const record = JSON.parse(trimmed) as RawRecord;\n if (record && typeof record === \"object\" && \"type\" in record) {\n yield record;\n }\n } catch {\n // Skip malformed lines — common at session boundaries\n }\n }\n}\n","/**\n * Builds a Session from raw JSONL records.\n *\n * Handles the core complexity of Claude Code's streaming format:\n * - Assistant turns are split across multiple JSONL records sharing the same\n * `message.id`. Content blocks from each chunk are merged into one turn.\n * - Only the final chunk (stop_reason != null) has real output_tokens.\n * - Synthetic records (model: \"<synthetic>\") and errored turns are excluded.\n */\n\nimport { basename } from \"node:path\";\nimport { readJsonl } from \"./jsonl-reader.js\";\nimport { SKIP_TYPES } from \"./types.js\";\nimport type {\n AssistantRecord,\n ContentBlock,\n MergedTurn,\n RawRecord,\n Session,\n UsageData,\n UserRecord,\n} from \"./types.js\";\n\ninterface AssistantAccumulator {\n content: ContentBlock[];\n usage: UsageData | null;\n complete: boolean;\n timestamp: string;\n model: string;\n}\n\n/**\n * Build a processed Session from an async stream of raw records.\n * @param records - AsyncIterable of raw JSONL records (from readJsonl)\n * @param sessionId - The session ID (from filename)\n * @param projectSlug - The project slug (from parent directory name)\n * @param subagentPaths - Optional paths to subagent JSONL files to merge in\n */\nexport async function buildSession(\n records: AsyncIterable<RawRecord>,\n sessionId: string,\n projectSlug: string,\n subagentPaths?: string[],\n): Promise<Session> {\n const turns: MergedTurn[] = [];\n const unknownRecordTypes = new Set<string>();\n\n let cwd = \"\";\n let gitBranch: string | null = null;\n let model = \"\";\n let firstTimestamp = \"\";\n let lastTimestamp = \"\";\n let formatVersion = \"\";\n\n async function processRecords(\n stream: AsyncIterable<RawRecord>,\n agentId: string | undefined,\n ) {\n const assistantChunks = new Map<string, AssistantAccumulator>();\n\n for await (const record of stream) {\n if (!formatVersion && \"version\" in record && typeof record.version === \"string\") {\n formatVersion = record.version;\n }\n\n if (SKIP_TYPES.has(record.type)) continue;\n\n if (record.type === \"user\") {\n const userRecord = record as UserRecord;\n handleUserRecord(userRecord, agentId);\n captureMetadata(userRecord);\n } else if (record.type === \"assistant\") {\n const assistantRecord = record as AssistantRecord;\n if (assistantRecord.message.model === \"<synthetic>\") continue;\n if (assistantRecord.error) continue;\n handleAssistantChunk(assistantRecord, assistantChunks);\n captureMetadata(assistantRecord);\n } else {\n unknownRecordTypes.add(record.type);\n }\n }\n\n // Flush all accumulated assistant chunks into turns\n for (const [, acc] of assistantChunks) {\n turns.push({\n role: \"assistant\",\n content: acc.content,\n usage: acc.usage,\n complete: acc.complete,\n timestamp: acc.timestamp,\n isHumanTurn: false,\n model: acc.model,\n agentId,\n });\n }\n }\n\n await processRecords(records, undefined);\n\n for (const agentPath of subagentPaths ?? []) {\n const agentId = basename(agentPath, \".jsonl\");\n await processRecords(readJsonl(agentPath), agentId);\n }\n\n // Sort all turns (main + subagents) by timestamp\n turns.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n const subagentIds = new Set(\n turns.filter((t) => t.agentId !== undefined).map((t) => t.agentId!),\n );\n const subagentTurnCount = turns.filter((t) => t.agentId !== undefined).length;\n\n return {\n id: sessionId,\n projectSlug,\n model,\n turns,\n startTime: firstTimestamp,\n endTime: lastTimestamp,\n cwd,\n gitBranch,\n durationMs:\n firstTimestamp && lastTimestamp\n ? new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime()\n : 0,\n subagentCount: subagentIds.size,\n subagentTurnCount,\n formatVersion,\n unknownRecordTypes,\n };\n\n // -- Inner helpers --------------------------------------------------------\n\n function captureMetadata(record: UserRecord | AssistantRecord) {\n if (!firstTimestamp && record.timestamp) {\n firstTimestamp = record.timestamp;\n }\n if (record.timestamp) {\n lastTimestamp = record.timestamp;\n }\n if (!cwd && record.cwd) {\n cwd = record.cwd;\n }\n if (gitBranch === null && record.gitBranch) {\n gitBranch = record.gitBranch;\n }\n if (!model && record.type === \"assistant\") {\n const ar = record as AssistantRecord;\n if (ar.message.model && ar.message.model !== \"<synthetic>\") {\n model = ar.message.model;\n }\n }\n }\n\n function handleUserRecord(record: UserRecord, agentId: string | undefined) {\n const content = record.message.content;\n const isHumanTurn = typeof content === \"string\" && !record.isMeta;\n turns.push({\n role: \"user\",\n content: normalizeContent(content),\n usage: null,\n complete: true,\n timestamp: record.timestamp,\n isHumanTurn,\n agentId,\n });\n }\n\n function handleAssistantChunk(\n record: AssistantRecord,\n chunks: Map<string, AssistantAccumulator>,\n ) {\n const messageId = record.message.id;\n let acc = chunks.get(messageId);\n\n if (!acc) {\n acc = {\n content: [],\n usage: null,\n complete: false,\n timestamp: record.timestamp,\n model: record.message.model,\n };\n chunks.set(messageId, acc);\n }\n\n // Append content blocks from this streaming chunk\n for (const block of record.message.content) {\n acc.content.push(block);\n }\n\n // Final chunk has the real usage data\n if (record.message.stop_reason !== null) {\n acc.complete = true;\n acc.usage = record.message.usage;\n }\n }\n}\n\nfunction normalizeContent(content: string | ContentBlock[]): ContentBlock[] {\n if (typeof content === \"string\") {\n return [{ type: \"text\", text: content }];\n }\n return content;\n}\n","/**\n * Type definitions for Claude Code JSONL session data.\n *\n * Claude Code writes one JSONL file per session. Each line is a JSON record\n * with a discriminated `type` field. Assistant responses are streamed as\n * multiple chunks sharing the same `message.id` — only the final chunk\n * (with `stop_reason != null`) carries real token usage data.\n */\n\n// ---------------------------------------------------------------------------\n// Content blocks\n// ---------------------------------------------------------------------------\n\nexport interface TextBlock {\n type: \"text\";\n text: string;\n}\n\nexport interface ThinkingBlock {\n type: \"thinking\";\n thinking: string;\n signature?: string;\n}\n\nexport interface ToolUseBlock {\n type: \"tool_use\";\n id: string;\n name: string;\n input: Record<string, unknown>;\n}\n\nexport interface ToolResultBlock {\n type: \"tool_result\";\n tool_use_id: string;\n content: string | ContentBlock[];\n is_error?: boolean;\n}\n\nexport type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;\n\n// ---------------------------------------------------------------------------\n// Usage data (on assistant messages)\n// ---------------------------------------------------------------------------\n\nexport interface UsageData {\n input_tokens: number;\n output_tokens: number;\n cache_creation_input_tokens: number;\n cache_read_input_tokens: number;\n cache_creation?: {\n ephemeral_1h_input_tokens: number;\n ephemeral_5m_input_tokens: number;\n };\n server_tool_use?: {\n web_search_requests: number;\n web_fetch_requests: number;\n };\n service_tier?: string;\n speed?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Raw JSONL record types (discriminated union on `type`)\n// ---------------------------------------------------------------------------\n\nexport interface BaseRecord {\n uuid: string;\n parentUuid: string | null;\n sessionId: string;\n timestamp: string;\n version: string;\n cwd: string;\n type: string;\n isSidechain?: boolean;\n entrypoint?: string;\n gitBranch?: string | null;\n slug?: string;\n userType?: string;\n agentId?: string;\n}\n\nexport interface UserRecord extends BaseRecord {\n type: \"user\";\n message: {\n role: \"user\";\n content: string | ContentBlock[];\n };\n isMeta?: boolean;\n permissionMode?: string;\n}\n\nexport interface AssistantRecord extends BaseRecord {\n type: \"assistant\";\n requestId?: string;\n message: {\n id: string;\n type: \"message\";\n role: \"assistant\";\n model: string;\n content: ContentBlock[];\n stop_reason: \"tool_use\" | \"end_turn\" | \"stop_sequence\" | null;\n usage: UsageData;\n };\n error?: string;\n isApiErrorMessage?: boolean;\n}\n\nexport interface QueueOperationRecord {\n type: \"queue-operation\";\n operation: string;\n timestamp: string;\n sessionId: string;\n content?: string;\n}\n\nexport interface AttachmentRecord extends BaseRecord {\n type: \"attachment\";\n attachment: Record<string, unknown>;\n}\n\nexport interface SystemRecord extends BaseRecord {\n type: \"system\";\n subtype?: string;\n [key: string]: unknown;\n}\n\nexport interface LastPromptRecord {\n type: \"last-prompt\";\n lastPrompt: string;\n sessionId: string;\n}\n\nexport type RawRecord =\n | UserRecord\n | AssistantRecord\n | QueueOperationRecord\n | AttachmentRecord\n | SystemRecord\n | LastPromptRecord;\n\n/** Record types to skip during session building. */\nexport const SKIP_TYPES = new Set([\n \"queue-operation\",\n \"attachment\",\n \"system\",\n \"last-prompt\",\n]);\n\n// ---------------------------------------------------------------------------\n// Processed session types (output of session builder)\n// ---------------------------------------------------------------------------\n\nexport interface MergedTurn {\n role: \"user\" | \"assistant\";\n content: ContentBlock[];\n /** Real usage from the final streaming chunk. Null for user turns. */\n usage: UsageData | null;\n /** Whether the assistant turn completed (stop_reason was non-null). */\n complete: boolean;\n timestamp: string;\n /** True for human-authored user messages (not tool results or hook injections). */\n isHumanTurn: boolean;\n /** The model that generated this turn (assistant only). */\n model?: string;\n /** Subagent ID (e.g. \"agent-abc123\"). Undefined = main agent. */\n agentId?: string;\n}\n\nexport interface Session {\n id: string;\n projectSlug: string;\n model: string;\n turns: MergedTurn[];\n startTime: string;\n endTime: string;\n cwd: string;\n gitBranch: string | null;\n durationMs: number;\n subagentCount: number;\n subagentTurnCount: number;\n formatVersion: string;\n unknownRecordTypes: Set<string>;\n}\n\n// ---------------------------------------------------------------------------\n// Metric result types\n// ---------------------------------------------------------------------------\n\nexport type MetricStatus = \"healthy\" | \"warning\" | \"critical\";\n\nexport interface MetricResult {\n name: string;\n value: number | null;\n status: MetricStatus;\n label: string;\n detail?: string;\n}\n\nexport interface GradeResult {\n letter: string;\n score: number;\n metrics: MetricResult[];\n}\n\n// ---------------------------------------------------------------------------\n// Session discovery\n// ---------------------------------------------------------------------------\n\nexport interface SessionFile {\n path: string;\n sessionId: string;\n projectSlug: string;\n mtime: Date;\n subagentPaths?: string[];\n}\n","/**\n * M1: Reads-before-edit ratio.\n *\n * Counts how many Read tool_use events occur before each Write or Edit event.\n * High values mean Claude is reading context before modifying files.\n * The AMD data showed this dropped from 6.6 to 2.0 after March 8.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nconst EDIT_TOOLS = new Set([\"Write\", \"Edit\", \"NotebookEdit\"]);\nconst READ_TOOL = \"Read\";\n\nexport function computeReadsPerEdit(session: Session): MetricResult {\n let readsSinceLastEdit = 0;\n const ratios: number[] = [];\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n\n if (toolBlock.name === READ_TOOL) {\n readsSinceLastEdit++;\n } else if (EDIT_TOOLS.has(toolBlock.name)) {\n ratios.push(readsSinceLastEdit);\n readsSinceLastEdit = 0;\n }\n }\n }\n\n if (ratios.length === 0) {\n return {\n name: \"reads-per-edit\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const average = ratios.reduce((a, b) => a + b, 0) / ratios.length;\n\n return {\n name: \"reads-per-edit\",\n value: round(average),\n status: average >= 4.0 ? \"healthy\" : average >= 2.0 ? \"warning\" : \"critical\",\n label: round(average).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M2: Full-file rewrite ratio.\n *\n * Ratio of Write calls (full file replacement) to total file modifications\n * (Write + Edit). Rising ratio means Claude is rewriting instead of\n * making surgical edits.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nexport function computeRewriteRatio(session: Session): MetricResult {\n let writes = 0;\n let edits = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n\n if (toolBlock.name === \"Write\") writes++;\n else if (toolBlock.name === \"Edit\" || toolBlock.name === \"NotebookEdit\") edits++;\n }\n }\n\n const total = writes + edits;\n if (total === 0) {\n return {\n name: \"rewrite-ratio\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const ratio = writes / total;\n\n return {\n name: \"rewrite-ratio\",\n value: round(ratio),\n status: ratio <= 0.25 ? \"healthy\" : ratio <= 0.5 ? \"warning\" : \"critical\",\n label: round(ratio).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M3: Cache hit rate.\n *\n * Ratio of cache_read_input_tokens to total input tokens\n * (cache_read + cache_creation). Detects the prompt cache bug\n * that caused 10-20x cost inflation.\n *\n * Note: raw `input_tokens` is always a streaming placeholder (1 or 3).\n * Real input cost = cache_read + cache_creation.\n */\n\nimport type { MetricResult, Session } from \"../parser/types.js\";\n\nexport function computeCacheHitRate(session: Session): MetricResult {\n let totalCacheRead = 0;\n let totalCacheCreation = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\" || !turn.usage || !turn.complete) continue;\n\n totalCacheRead += turn.usage.cache_read_input_tokens;\n totalCacheCreation += turn.usage.cache_creation_input_tokens;\n }\n\n const totalInput = totalCacheRead + totalCacheCreation;\n if (totalInput === 0) {\n return {\n name: \"cache-hit-rate\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No token usage data available\",\n };\n }\n\n const rate = totalCacheRead / totalInput;\n\n return {\n name: \"cache-hit-rate\",\n value: round(rate),\n status: rate >= 0.5 ? \"healthy\" : rate >= 0.2 ? \"warning\" : \"critical\",\n label: round(rate).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M4: Task completion rate.\n *\n * Detects sessions where Claude says it will do something but doesn't\n * follow through. Looks for intent phrases in assistant text that\n * aren't followed by a tool_use in the next assistant turn.\n */\n\nimport type { MetricResult, Session, MergedTurn, TextBlock, ToolUseBlock } from \"../parser/types.js\";\n\nconst INTENT_PATTERNS = [\n /\\bI'll now\\b/i,\n /\\bLet me\\b/i,\n /\\bI'll update\\b/i,\n /\\bNext,? I'll\\b/i,\n /\\bI'll (?:also |then )?(?:fix|add|create|implement|refactor|modify|change|write|edit|update)\\b/i,\n /\\bI'm going to\\b/i,\n];\n\nexport function computeTaskCompletion(session: Session): MetricResult {\n const assistantTurns = session.turns.filter(\n (t) => t.role === \"assistant\" && t.complete,\n );\n\n let totalIntents = 0;\n let unfulfilledIntents = 0;\n\n for (const turn of assistantTurns) {\n const hasIntent = hasIntentPhrase(turn);\n if (!hasIntent) continue;\n\n totalIntents++;\n\n // An intent is fulfilled if the same merged turn also contains a tool_use.\n // Since streaming chunks are merged, a real action within this turn means\n // Claude followed through. An intent without a tool_use in the same turn\n // is a dangling promise.\n const hasToolUse = turn.content.some((b) => b.type === \"tool_use\");\n if (!hasToolUse) {\n unfulfilledIntents++;\n }\n }\n\n if (totalIntents === 0) {\n return {\n name: \"task-completion\",\n value: 1,\n status: \"healthy\",\n label: \"1.00\",\n detail: \"No intent phrases detected\",\n };\n }\n\n const rate = 1 - unfulfilledIntents / totalIntents;\n\n return {\n name: \"task-completion\",\n value: round(rate),\n status: rate >= 0.9 ? \"healthy\" : rate >= 0.7 ? \"warning\" : \"critical\",\n label: round(rate).toString(),\n };\n}\n\nfunction hasIntentPhrase(turn: MergedTurn): boolean {\n for (const block of turn.content) {\n if (block.type === \"text\") {\n const textBlock = block as TextBlock;\n if (INTENT_PATTERNS.some((p) => p.test(textBlock.text))) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * Levenshtein distance and normalized similarity.\n * Pure implementation — no external dependencies.\n */\n\n/**\n * Compute the Levenshtein edit distance between two strings.\n * Uses a single-row DP approach for O(min(m,n)) space.\n */\nexport function levenshteinDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure a is the shorter string for space optimization\n if (a.length > b.length) [a, b] = [b, a];\n\n const aLen = a.length;\n const bLen = b.length;\n const row = new Array<number>(aLen + 1);\n\n for (let i = 0; i <= aLen; i++) row[i] = i;\n\n for (let j = 1; j <= bLen; j++) {\n let prev = row[0];\n row[0] = j;\n\n for (let i = 1; i <= aLen; i++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n const temp = row[i];\n row[i] = Math.min(\n row[i] + 1, // deletion\n row[i - 1] + 1, // insertion\n prev + cost, // substitution\n );\n prev = temp;\n }\n }\n\n return row[aLen];\n}\n\n/**\n * Compute normalized similarity between two strings (0 = different, 1 = identical).\n * Only compares the first `maxLen` characters for performance.\n */\nexport function normalizedSimilarity(\n a: string,\n b: string,\n maxLen = 200,\n): number {\n const aTrunc = a.slice(0, maxLen);\n const bTrunc = b.slice(0, maxLen);\n const maxLength = Math.max(aTrunc.length, bTrunc.length);\n\n if (maxLength === 0) return 1;\n\n const distance = levenshteinDistance(aTrunc, bTrunc);\n return 1 - distance / maxLength;\n}\n","/**\n * M5: Retry density.\n *\n * Measures how often the user sends messages very similar to their\n * previous message — a proxy for \"Claude got it wrong and I'm asking again.\"\n */\n\nimport type { MetricResult, Session, TextBlock } from \"../parser/types.js\";\nimport { normalizedSimilarity } from \"../utils/levenshtein.js\";\n\nexport function computeRetryDensity(session: Session): MetricResult {\n // Extract text from human-authored user turns only\n const humanTexts: string[] = [];\n for (const turn of session.turns) {\n if (!turn.isHumanTurn) continue;\n const text = turn.content\n .filter((b): b is TextBlock => b.type === \"text\")\n .map((b) => b.text)\n .join(\" \");\n if (text.length > 0) humanTexts.push(text);\n }\n\n if (humanTexts.length < 2) {\n return {\n name: \"retry-density\",\n value: 0,\n status: \"healthy\",\n label: \"0.00\",\n detail: \"Not enough user messages to detect retries\",\n };\n }\n\n let retries = 0;\n const pairs = humanTexts.length - 1;\n\n for (let i = 0; i < pairs; i++) {\n const similarity = normalizedSimilarity(humanTexts[i], humanTexts[i + 1]);\n if (similarity > 0.6) {\n retries++;\n }\n }\n\n const density = retries / pairs;\n\n return {\n name: \"retry-density\",\n value: round(density),\n status: density <= 0.1 ? \"healthy\" : density <= 0.25 ? \"warning\" : \"critical\",\n label: round(density).toString(),\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M6: Tool diversity score.\n *\n * Shannon entropy over the distribution of tool_use events by tool name.\n * Normalized to 0-1. Low diversity means Claude is over-relying on\n * one tool (often Write).\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nexport function computeToolDiversity(session: Session): MetricResult {\n const toolCounts = new Map<string, number>();\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n toolCounts.set(toolBlock.name, (toolCounts.get(toolBlock.name) ?? 0) + 1);\n }\n }\n\n const uniqueTools = toolCounts.size;\n if (uniqueTools <= 1) {\n return {\n name: \"tool-diversity\",\n value: uniqueTools === 0 ? null : 0,\n status: uniqueTools === 0 ? \"healthy\" : \"critical\",\n label: uniqueTools === 0 ? \"N/A\" : \"0.00\",\n detail: uniqueTools === 0\n ? \"No tool usage in this session\"\n : `Only one tool used: ${[...toolCounts.keys()][0]}`,\n };\n }\n\n const totalCalls = [...toolCounts.values()].reduce((a, b) => a + b, 0);\n const maxEntropy = Math.log2(uniqueTools);\n\n let entropy = 0;\n for (const count of toolCounts.values()) {\n const p = count / totalCalls;\n entropy -= p * Math.log2(p);\n }\n\n const normalized = entropy / maxEntropy;\n\n // Build detail showing top tools\n const sorted = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]);\n const topTool = sorted[0];\n const topPercent = Math.round((topTool[1] / totalCalls) * 100);\n const detail = `Most used: ${topTool[0]} (${topPercent}%)`;\n\n return {\n name: \"tool-diversity\",\n value: round(normalized),\n status: normalized >= 0.6 ? \"healthy\" : normalized >= 0.4 ? \"warning\" : \"critical\",\n label: round(normalized).toString(),\n detail,\n };\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n","/**\n * M7: Tokens per useful edit.\n *\n * Total output tokens consumed divided by number of file modification\n * operations (Write + Edit). Rising ratio means Claude is burning more\n * tokens per productive action.\n */\n\nimport type { MetricResult, Session, ToolUseBlock } from \"../parser/types.js\";\n\nconst EDIT_TOOLS = new Set([\"Write\", \"Edit\", \"NotebookEdit\"]);\n\nexport function computeTokensPerEdit(session: Session): MetricResult {\n let totalOutputTokens = 0;\n let editCount = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\") continue;\n\n // Count tokens from completed turns only\n if (turn.complete && turn.usage) {\n totalOutputTokens += turn.usage.output_tokens;\n }\n\n // Count edit operations\n for (const block of turn.content) {\n if (block.type !== \"tool_use\") continue;\n const toolBlock = block as ToolUseBlock;\n if (EDIT_TOOLS.has(toolBlock.name)) editCount++;\n }\n }\n\n if (editCount === 0) {\n return {\n name: \"tokens-per-edit\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No file modifications in this session\",\n };\n }\n\n const ratio = totalOutputTokens / editCount;\n\n return {\n name: \"tokens-per-edit\",\n value: Math.round(ratio),\n status: ratio <= 5000 ? \"healthy\" : ratio <= 15000 ? \"warning\" : \"critical\",\n label: Math.round(ratio).toLocaleString(\"en-US\"),\n };\n}\n","/**\n * M8: Subagent delegation ratio.\n *\n * Measures what fraction of total output tokens came from the main agent vs\n * subagents. A low main-agent ratio means the orchestrator delegated effectively.\n * Healthy = main agent produced < 60% of total output tokens.\n */\n\nimport type { MetricResult, Session } from \"../parser/types.js\";\n\nexport function computeSubagentOverhead(session: Session): MetricResult {\n if (session.subagentCount === 0) {\n return {\n name: \"subagent-overhead\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No subagents in this session\",\n };\n }\n\n let mainTokens = 0;\n let subagentTokens = 0;\n\n for (const turn of session.turns) {\n if (turn.role !== \"assistant\" || !turn.usage) continue;\n const out = turn.usage.output_tokens ?? 0;\n if (turn.agentId === undefined) {\n mainTokens += out;\n } else {\n subagentTokens += out;\n }\n }\n\n const total = mainTokens + subagentTokens;\n if (total === 0) {\n return {\n name: \"subagent-overhead\",\n value: null,\n status: \"healthy\",\n label: \"N/A\",\n detail: \"No output tokens recorded\",\n };\n }\n\n const ratio = mainTokens / total;\n const status = ratio < 0.6 ? \"healthy\" : ratio < 0.8 ? \"warning\" : \"critical\";\n\n return {\n name: \"subagent-overhead\",\n value: Math.round(ratio * 100) / 100,\n status,\n label: `${Math.round(ratio * 100)}% main`,\n };\n}\n","/**\n * Composite grading from all 8 quality metrics.\n *\n * Each metric is scored 0-100 based on its thresholds, then weighted\n * into a composite score mapped to a letter grade A+ through F.\n */\n\nimport type { GradeResult, MetricResult, Session } from \"../parser/types.js\";\nimport { computeReadsPerEdit } from \"./reads-per-edit.js\";\nimport { computeRewriteRatio } from \"./rewrite-ratio.js\";\nimport { computeCacheHitRate } from \"./cache-hit-rate.js\";\nimport { computeTaskCompletion } from \"./task-completion.js\";\nimport { computeRetryDensity } from \"./retry-density.js\";\nimport { computeToolDiversity } from \"./tool-diversity.js\";\nimport { computeTokensPerEdit } from \"./tokens-per-edit.js\";\nimport { computeSubagentOverhead } from \"./subagent-overhead.js\";\n\ninterface MetricWeight {\n compute: (session: Session) => MetricResult;\n weight: number;\n /** Convert metric value to 0-100 score. Higher is better. */\n score: (value: number) => number;\n}\n\nconst METRIC_WEIGHTS: MetricWeight[] = [\n {\n compute: computeReadsPerEdit,\n weight: 0.2,\n // 0 reads → 0, 2 reads → 50, 4+ reads → 100\n score: (v) => clamp(v / 4 * 100, 0, 100),\n },\n {\n compute: computeRewriteRatio,\n weight: 0.15,\n // 0 ratio → 100, 0.25 → 50, 0.5+ → 0 (inverted: lower is better)\n score: (v) => clamp((1 - v / 0.5) * 100, 0, 100),\n },\n {\n compute: computeCacheHitRate,\n weight: 0.15,\n // 0% → 0, 50% → 100\n score: (v) => clamp(v / 0.5 * 100, 0, 100),\n },\n {\n compute: computeTaskCompletion,\n weight: 0.15,\n // 0.7 → 0, 0.9 → 50, 1.0 → 100\n score: (v) => clamp((v - 0.7) / 0.3 * 100, 0, 100),\n },\n {\n compute: computeRetryDensity,\n weight: 0.1,\n // 0% → 100, 10% → 60, 25%+ → 0 (inverted)\n score: (v) => clamp((1 - v / 0.25) * 100, 0, 100),\n },\n {\n compute: computeToolDiversity,\n weight: 0.05,\n // 0 → 0, 0.4 → 50, 0.6+ → 100\n score: (v) => clamp(v / 0.6 * 100, 0, 100),\n },\n {\n compute: computeTokensPerEdit,\n weight: 0.15,\n // 5000 → 100, 10000 → 50, 15000+ → 0 (inverted)\n score: (v) => clamp((1 - (v - 5000) / 10000) * 100, 0, 100),\n },\n {\n compute: computeSubagentOverhead,\n weight: 0.05,\n // main ratio 0 → 100, 0.6 → 100 (threshold), 0.8 → 50, 1.0 → 0 (inverted)\n score: (v) => clamp((1 - v) / 0.4 * 100, 0, 100),\n },\n];\n\nconst GRADE_THRESHOLDS: Array<[number, string]> = [\n [97, \"A+\"],\n [93, \"A\"],\n [90, \"A-\"],\n [87, \"B+\"],\n [83, \"B\"],\n [80, \"B-\"],\n [77, \"C+\"],\n [73, \"C\"],\n [70, \"C-\"],\n [67, \"D+\"],\n [63, \"D\"],\n [60, \"D-\"],\n [0, \"F\"],\n];\n\nexport function gradeLetterFromScore(score: number): string {\n return GRADE_THRESHOLDS.find(([threshold]) => score >= threshold)?.[1] ?? \"F\";\n}\n\nexport function gradeSession(session: Session): GradeResult {\n const metrics: MetricResult[] = [];\n let weightedSum = 0;\n let totalWeight = 0;\n\n for (const mw of METRIC_WEIGHTS) {\n const result = mw.compute(session);\n metrics.push(result);\n\n if (result.value !== null) {\n weightedSum += mw.score(result.value) * mw.weight;\n totalWeight += mw.weight;\n }\n }\n\n // Normalize if some metrics returned null (insufficient data)\n const compositeScore = totalWeight > 0 ? weightedSum / totalWeight : 0;\n\n const letter = gradeLetterFromScore(compositeScore);\n\n return {\n letter,\n score: Math.round(compositeScore),\n metrics,\n };\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, value));\n}\n","/**\n * Terminal output formatting using chalk and cli-table3.\n */\n\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\nimport type { GradeResult, MetricResult, Session } from \"../parser/types.js\";\nimport type { RegressionResult } from \"../anomaly/regression-detector.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\nimport { formatDuration, shortSessionId, projectNameFromSlug, formatNumber } from \"../utils/format.js\";\nimport { getAllTips } from \"./tips.js\";\nimport { VERSION } from \"../version.js\";\n\nconst STATUS_ICONS: Record<string, string> = {\n healthy: chalk.green(\"✓\"),\n warning: chalk.yellow(\"⚠\"),\n critical: chalk.red(\"✗\"),\n};\n\nconst STATUS_LABELS: Record<string, string> = {\n healthy: chalk.green(\"healthy\"),\n warning: chalk.yellow(\"warning\"),\n critical: chalk.red(\"critical\"),\n};\n\nconst METRIC_DISPLAY_NAMES: Record<string, string> = {\n \"reads-per-edit\": \"Reads/edit\",\n \"rewrite-ratio\": \"Rewrite ratio\",\n \"cache-hit-rate\": \"Cache hit rate\",\n \"task-completion\": \"Task completion\",\n \"retry-density\": \"Retry density\",\n \"tool-diversity\": \"Tool diversity\",\n \"tokens-per-edit\": \"Tokens/useful-edit\",\n \"subagent-overhead\": \"Subagent delegation\",\n};\n\nexport function renderAuditReport(session: Session, grade: GradeResult): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(` inspecto v${VERSION}`) + chalk.dim(\" — Claude Code Session Quality Analyzer\"));\n lines.push(\"\");\n\n const agentInfo = session.subagentCount > 0\n ? `${session.subagentCount} subagents | ${session.turns.length} turns`\n : `${session.turns.length} turns`;\n\n const sessionInfo = [\n `Session: ${chalk.cyan(shortSessionId(session.id))}`,\n projectNameFromSlug(session.projectSlug),\n formatDuration(session.durationMs),\n session.model,\n agentInfo,\n ].join(chalk.dim(\" | \"));\n lines.push(` ${sessionInfo}`);\n lines.push(\"\");\n\n const gradeColor = getGradeColor(grade.letter);\n lines.push(` Overall grade: ${gradeColor(chalk.bold(grade.letter))}`);\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Metric\", \"Value\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const metric of grade.metrics) {\n const displayName = METRIC_DISPLAY_NAMES[metric.name] ?? metric.name;\n const icon = STATUS_ICONS[metric.status] ?? \"\";\n table.push([displayName, metric.label, `${icon} ${STATUS_LABELS[metric.status] ?? metric.status}`]);\n }\n\n lines.push(table.toString());\n\n const tips = getAllTips(grade.metrics);\n if (tips.length > 0) {\n lines.push(\"\");\n lines.push(chalk.yellow(\" Tips:\"));\n for (const tip of tips) {\n lines.push(` ${chalk.dim(\"→\")} ${tip}`);\n }\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nexport function renderTrendReport(\n results: RegressionResult[],\n sessionCount: number,\n period: string,\n): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(` Trend report: last ${period}`) + chalk.dim(` (${sessionCount} sessions)`));\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Metric\", \"Recent avg\", \"Full avg\", \"Change\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const result of results) {\n const displayName = METRIC_DISPLAY_NAMES[result.name] ?? result.name;\n const recentStr = result.recentAvg !== null ? result.recentAvg.toFixed(2) : \"N/A\";\n const fullStr = result.fullAvg !== null ? result.fullAvg.toFixed(2) : \"N/A\";\n\n let changeStr = \"N/A\";\n if (result.changePercent !== null) {\n const arrow = result.changePercent > 0 ? \"▲\" : result.changePercent < 0 ? \"▼\" : \"\";\n changeStr = `${arrow} ${Math.abs(Math.round(result.changePercent))}%`;\n }\n\n const statusStr = formatRegressionStatus(result.status);\n table.push([displayName, recentStr, fullStr, changeStr, statusStr]);\n }\n\n lines.push(table.toString());\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nexport function renderCacheCheckReport(results: CacheCheckResult[]): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(\" Cache health check\"));\n lines.push(\"\");\n\n const table = new Table({\n head: [\"Session\", \"Project\", \"Cache Hit\", \"Status\"].map((h) => chalk.dim(h)),\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const result of results) {\n const hitStr = result.cacheHitRate !== null ? result.cacheHitRate.toFixed(2) : \"N/A\";\n const statusStr = result.isAnomaly\n ? chalk.red(\"✗ ANOMALY\")\n : chalk.green(\"✓ normal\");\n\n table.push([\n shortSessionId(result.sessionId),\n projectNameFromSlug(result.projectSlug),\n hitStr,\n statusStr,\n ]);\n }\n\n lines.push(table.toString());\n\n const anomalies = results.filter((r) => r.isAnomaly);\n if (anomalies.length > 0) {\n lines.push(\"\");\n lines.push(\n chalk.yellow(` ⚠ ${anomalies.length} session(s) with abnormally low cache hit rate.`),\n );\n for (const a of anomalies) {\n if (a.estimatedInflation) {\n lines.push(\n ` ${chalk.dim(\"→\")} Session ${shortSessionId(a.sessionId)} consumed ~${a.estimatedInflation}x more input tokens than expected.`,\n );\n }\n }\n lines.push(\n chalk.dim(\" Try: restart Claude Code or downgrade to a previous version.\"),\n );\n } else {\n lines.push(\"\");\n lines.push(chalk.green(\" ✓ No cache anomalies detected.\"));\n }\n\n lines.push(\"\");\n return lines.join(\"\\n\");\n}\n\nfunction getGradeColor(letter: string): (text: string) => string {\n if (letter.startsWith(\"A\")) return chalk.green;\n if (letter.startsWith(\"B\")) return chalk.cyan;\n if (letter.startsWith(\"C\")) return chalk.yellow;\n return chalk.red;\n}\n\nfunction formatRegressionStatus(status: string): string {\n switch (status) {\n case \"stable\":\n return chalk.green(\"✓ stable\");\n case \"declining\":\n return chalk.yellow(\"⚠ declining\");\n case \"regression\":\n return chalk.red(\"⚠ REGRESSION\");\n default:\n return status;\n }\n}\n","/**\n * Number and string formatting helpers for terminal output.\n */\n\n/** Format a number with comma separators: 3218 → \"3,218\" */\nexport function formatNumber(n: number): string {\n return n.toLocaleString(\"en-US\");\n}\n\n/** Format a ratio as a fixed-2 decimal: 0.734 → \"0.73\" */\nexport function formatRatio(n: number): string {\n return n.toFixed(2);\n}\n\n/** Format a percentage: 0.734 → \"73%\" */\nexport function formatPercent(n: number): string {\n return `${Math.round(n * 100)}%`;\n}\n\n/** Format milliseconds into a human-readable duration: 2820000 → \"47 min\" */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000);\n if (seconds < 60) return `${seconds}s`;\n\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes} min`;\n\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n if (remainingMinutes === 0) return `${hours}h`;\n return `${hours}h ${remainingMinutes}m`;\n}\n\n/** Truncate a session ID for display: \"31f3f224-abcd-...\" → \"31f3f224\" */\nexport function shortSessionId(id: string): string {\n return id.slice(0, 8);\n}\n\n/** Extract a human-readable project name from a slug like \"-Users-foo-my-app\" */\nexport function projectNameFromSlug(slug: string): string {\n const parts = slug.split(\"-\").filter(Boolean);\n return parts[parts.length - 1] || slug;\n}\n","/**\n * Contextual tips based on metric values.\n * Maps poor-scoring metrics to actionable suggestions.\n */\n\nimport type { MetricResult } from \"../parser/types.js\";\n\nconst TIPS: Record<string, Record<string, string>> = {\n \"reads-per-edit\": {\n warning: \"Claude is editing with less context. Add 'Always read files before editing' to your CLAUDE.md.\",\n critical: \"Very low reads before edits. Claude is making blind changes. Consider adding explicit read instructions.\",\n },\n \"rewrite-ratio\": {\n warning: \"High ratio of full-file rewrites. Add 'Prefer Edit over Write for existing files' to CLAUDE.md.\",\n critical: \"Claude is rewriting entire files instead of making surgical edits. This wastes tokens and risks data loss.\",\n },\n \"cache-hit-rate\": {\n warning: \"Cache hit rate is below normal. Sessions may be too short for caching to help.\",\n critical: \"Very low cache hit rate — possible cache bug. Try restarting Claude Code or downgrading to a previous version.\",\n },\n \"task-completion\": {\n warning: \"Claude is occasionally promising actions without following through.\",\n critical: \"Frequent unfulfilled promises. Claude says it will do things but doesn't. Try breaking tasks into smaller steps.\",\n },\n \"retry-density\": {\n warning: \"Some user messages look like retries. Claude may be misunderstanding requests.\",\n critical: \"High retry rate. Users are frequently re-asking. Consider providing more context in prompts.\",\n },\n \"tool-diversity\": {\n warning: \"Low tool diversity. Claude is over-relying on a narrow set of tools.\",\n critical: \"Very narrow tool usage. Claude may be stuck in a pattern. Try prompting for specific tool usage.\",\n },\n \"tokens-per-edit\": {\n warning: \"Tokens per edit is above average. Claude may be verbose without being productive.\",\n critical: \"Very high token cost per edit. Claude is burning tokens without proportional output.\",\n },\n};\n\nexport function getTip(metric: MetricResult): string | null {\n if (metric.status === \"healthy\") return null;\n\n const metricTips = TIPS[metric.name];\n if (!metricTips) return null;\n\n return metricTips[metric.status] ?? null;\n}\n\nexport function getAllTips(metrics: MetricResult[]): string[] {\n return metrics\n .map(getTip)\n .filter((tip): tip is string => tip !== null);\n}\n","import { createRequire } from \"node:module\";\n\nconst _require = createRequire(import.meta.url);\nexport const VERSION: string = (_require(\"../package.json\") as { version: string }).version;\n","/**\n * JSON output mode for scripting and CI.\n */\n\nimport type { GradeResult, Session } from \"../parser/types.js\";\nimport type { RegressionResult } from \"../anomaly/regression-detector.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\n\nexport interface AuditJsonOutput {\n session: {\n id: string;\n project: string;\n model: string;\n durationMs: number;\n startTime: string;\n };\n grade: string;\n score: number;\n metrics: Array<{\n name: string;\n value: number | null;\n status: string;\n label: string;\n }>;\n}\n\nexport function formatAuditJson(session: Session, grade: GradeResult): string {\n const output: AuditJsonOutput = {\n session: {\n id: session.id,\n project: session.projectSlug,\n model: session.model,\n durationMs: session.durationMs,\n startTime: session.startTime,\n },\n grade: grade.letter,\n score: grade.score,\n metrics: grade.metrics.map((m) => ({\n name: m.name,\n value: m.value,\n status: m.status,\n label: m.label,\n })),\n };\n\n return JSON.stringify(output, null, 2);\n}\n\nexport function formatTrendJson(results: RegressionResult[]): string {\n return JSON.stringify({ trend: results }, null, 2);\n}\n\nexport function formatCacheCheckJson(results: CacheCheckResult[]): string {\n return JSON.stringify({ cacheCheck: results }, null, 2);\n}\n","/**\n * Compute rolling averages from multiple sessions for trend analysis.\n */\n\nimport type { GradeResult } from \"../parser/types.js\";\n\nexport interface MetricAverage {\n name: string;\n recentAvg: number | null;\n fullAvg: number | null;\n changePercent: number | null;\n}\n\n/**\n * Compute per-metric averages for a recent window vs. full range.\n * @param grades - All graded sessions, sorted most recent first\n * @param recentCount - Number of sessions in the \"recent\" window\n */\nexport function computeBaselines(\n grades: GradeResult[],\n recentCount: number,\n): MetricAverage[] {\n if (grades.length === 0) return [];\n\n const recent = grades.slice(0, recentCount);\n const full = grades;\n\n const metricNames = grades[0].metrics.map((m) => m.name);\n\n return metricNames.map((name) => {\n const recentValues = extractValues(recent, name);\n const fullValues = extractValues(full, name);\n\n const recentAvg = average(recentValues);\n const fullAvg = average(fullValues);\n\n let changePercent: number | null = null;\n if (recentAvg !== null && fullAvg !== null && fullAvg !== 0) {\n changePercent = ((recentAvg - fullAvg) / Math.abs(fullAvg)) * 100;\n }\n\n return { name, recentAvg, fullAvg, changePercent };\n });\n}\n\nfunction extractValues(grades: GradeResult[], metricName: string): number[] {\n const values: number[] = [];\n for (const grade of grades) {\n const metric = grade.metrics.find((m) => m.name === metricName);\n if (metric?.value !== null && metric?.value !== undefined) {\n values.push(metric.value);\n }\n }\n return values;\n}\n\nfunction average(values: number[]): number | null {\n if (values.length === 0) return null;\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n","/**\n * Z-score based regression detection.\n *\n * Compares recent metric values against historical baseline to flag\n * statistically significant regressions.\n */\n\nimport type { MetricAverage } from \"./baseline.js\";\n\nexport type RegressionStatus = \"stable\" | \"declining\" | \"regression\";\n\nexport interface RegressionResult {\n name: string;\n recentAvg: number | null;\n fullAvg: number | null;\n changePercent: number | null;\n status: RegressionStatus;\n}\n\n/** Metrics where HIGHER values are WORSE (inverted for regression detection). */\nconst INVERTED_METRICS = new Set([\n \"rewrite-ratio\",\n \"retry-density\",\n \"tokens-per-edit\",\n]);\n\n/**\n * Detect regressions from baseline averages.\n * A change > 30% in the \"bad\" direction is a regression.\n * A change > 10% is \"declining\".\n */\nexport function detectRegressions(\n baselines: MetricAverage[],\n): RegressionResult[] {\n return baselines.map((b) => {\n let status: RegressionStatus = \"stable\";\n\n if (b.changePercent !== null) {\n const isInverted = INVERTED_METRICS.has(b.name);\n // For normal metrics, negative change is bad. For inverted, positive change is bad.\n const badDirection = isInverted ? b.changePercent > 0 : b.changePercent < 0;\n const magnitude = Math.abs(b.changePercent);\n\n if (badDirection && magnitude > 30) {\n status = \"regression\";\n } else if (badDirection && magnitude > 10) {\n status = \"declining\";\n }\n }\n\n return {\n name: b.name,\n recentAvg: b.recentAvg,\n fullAvg: b.fullAvg,\n changePercent: b.changePercent,\n status,\n };\n });\n}\n","/**\n * Parse human-readable duration strings into Date offsets.\n */\n\n/**\n * Parse a duration string like \"7d\", \"14d\", \"30d\" into a Date\n * representing that many days before `now`.\n */\nexport function parseDuration(duration: string, now = new Date()): Date {\n const match = duration.match(/^(\\d+)d$/);\n if (!match) {\n throw new Error(\n `Invalid duration: \"${duration}\". Use format like \"7d\", \"14d\", \"30d\".`,\n );\n }\n\n const days = parseInt(match[1], 10);\n const result = new Date(now);\n result.setDate(result.getDate() - days);\n return result;\n}\n","/**\n * Runs fn over items with at most `limit` concurrent in-flight promises.\n * Returns settled results in input order, matching Promise.allSettled semantics.\n * One rejection never aborts the remaining items.\n */\nexport async function concurrentSettled<T, R>(\n items: T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<PromiseSettledResult<R>[]> {\n const results = new Array<PromiseSettledResult<R>>(items.length);\n let next = 0;\n\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const i = next++;\n try {\n results[i] = { status: \"fulfilled\", value: await fn(items[i]) };\n } catch (error) {\n results[i] = { status: \"rejected\", reason: error };\n }\n }\n };\n\n await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));\n return results;\n}\n","import { DatabaseSync } from \"node:sqlite\";\nimport { createHash } from \"node:crypto\";\nimport type { GradeResult } from \"../parser/types.js\";\nimport { getCacheFilePath } from \"../utils/paths.js\";\n\nlet db: DatabaseSync | null = null;\n\nfunction getDb(): DatabaseSync {\n if (!db) {\n db = new DatabaseSync(getCacheFilePath());\n db.exec(`\n CREATE TABLE IF NOT EXISTS grade_cache (\n cache_key TEXT PRIMARY KEY,\n grade_result TEXT NOT NULL\n )\n `);\n }\n return db;\n}\n\nfunction makeCacheKey(sessionPath: string, mtime: Date): string {\n return createHash(\"sha256\")\n .update(`${sessionPath}:${mtime.getTime()}`)\n .digest(\"hex\");\n}\n\nexport function getCachedGrade(sessionPath: string, mtime: Date): GradeResult | null {\n try {\n const key = makeCacheKey(sessionPath, mtime);\n const stmt = getDb().prepare(\"SELECT grade_result FROM grade_cache WHERE cache_key = ?\");\n const row = stmt.get(key) as { grade_result: string } | undefined;\n if (!row) return null;\n return JSON.parse(row.grade_result) as GradeResult;\n } catch {\n process.stderr.write(\"[inspecto cache] read error, skipping cache\\n\");\n return null;\n }\n}\n\nexport function setCachedGrade(sessionPath: string, mtime: Date, grade: GradeResult): void {\n try {\n const key = makeCacheKey(sessionPath, mtime);\n const stmt = getDb().prepare(\n \"INSERT OR REPLACE INTO grade_cache (cache_key, grade_result) VALUES (?, ?)\",\n );\n stmt.run(key, JSON.stringify(grade));\n } catch {\n process.stderr.write(\"[inspecto cache] write error, skipping cache\\n\");\n }\n}\n","/**\n * Trend analysis command — detect regressions over time.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession } from \"../metrics/grader.js\";\nimport { computeBaselines } from \"../anomaly/baseline.js\";\nimport { detectRegressions } from \"../anomaly/regression-detector.js\";\nimport { renderTrendReport } from \"../reporter/terminal.js\";\nimport { formatTrendJson } from \"../reporter/json-reporter.js\";\nimport { parseDuration } from \"../utils/duration.js\";\nimport { concurrentSettled } from \"../utils/concurrent.js\";\nimport { getCachedGrade, setCachedGrade } from \"../cache/grade-cache.js\";\nimport type { GradeResult, SessionFile } from \"../parser/types.js\";\n\nconst CONCURRENCY = 16;\n\nexport interface TrendOptions {\n since?: string;\n json?: boolean;\n dataDir?: string;\n project?: string;\n}\n\nexport async function runTrend(options: TrendOptions): Promise<void> {\n const duration = options.since ?? \"7d\";\n const sinceDate = parseDuration(duration);\n\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: options.project,\n since: sinceDate,\n });\n\n if (sessionFiles.length === 0) {\n console.log(`No sessions found in the last ${duration}.`);\n return;\n }\n\n const settled = await concurrentSettled(sessionFiles, CONCURRENCY, async (sf: SessionFile) => {\n const cached = getCachedGrade(sf.path, sf.mtime);\n if (cached) return cached;\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);\n const grade = gradeSession(session);\n setCachedGrade(sf.path, sf.mtime, grade);\n return grade;\n });\n\n const grades: GradeResult[] = settled\n .filter((r): r is PromiseFulfilledResult<GradeResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (grades.length === 0) {\n console.log(\"No valid sessions found to analyze.\");\n return;\n }\n\n // Use half the sessions as the \"recent\" window, minimum 1\n const recentCount = Math.max(1, Math.floor(grades.length / 2));\n const baselines = computeBaselines(grades, recentCount);\n const regressions = detectRegressions(baselines);\n\n if (options.json) {\n console.log(formatTrendJson(regressions));\n } else {\n console.log(renderTrendReport(regressions, grades.length, duration));\n }\n}\n","/**\n * Cache hit rate anomaly detection.\n *\n * Specifically checks for the prompt cache bug that caused 10-20x\n * token cost inflation by detecting sessions with near-zero cache hit rates.\n */\n\nimport type { Session } from \"../parser/types.js\";\nimport { computeCacheHitRate } from \"../metrics/cache-hit-rate.js\";\n\nexport interface CacheCheckResult {\n sessionId: string;\n projectSlug: string;\n timestamp: string;\n cacheHitRate: number | null;\n isAnomaly: boolean;\n estimatedInflation: number | null;\n}\n\nconst ANOMALY_THRESHOLD = 0.05;\nconst NORMAL_CACHE_RATE = 0.65;\n\n/**\n * Check a single session for cache hit rate anomalies.\n */\nexport function checkCacheAnomaly(session: Session): CacheCheckResult {\n const metric = computeCacheHitRate(session);\n\n const isAnomaly = metric.value !== null && metric.value < ANOMALY_THRESHOLD;\n\n let estimatedInflation: number | null = null;\n if (isAnomaly && metric.value !== null) {\n // If normal rate is 65% cache reads, the effective input cost multiplier\n // when cache is broken is roughly 1 / (1 - normalRate)\n // Normal: 35% full-price tokens. Broken: 100% full-price tokens.\n estimatedInflation = Math.round(1 / (1 - NORMAL_CACHE_RATE));\n }\n\n return {\n sessionId: session.id,\n projectSlug: session.projectSlug,\n timestamp: session.startTime,\n cacheHitRate: metric.value,\n isAnomaly,\n estimatedInflation,\n };\n}\n","/**\n * Cache bug detection command.\n * Scans recent sessions for abnormally low cache hit rates.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { checkCacheAnomaly } from \"../anomaly/cache-anomaly.js\";\nimport { renderCacheCheckReport } from \"../reporter/terminal.js\";\nimport { formatCacheCheckJson } from \"../reporter/json-reporter.js\";\nimport { parseDuration } from \"../utils/duration.js\";\nimport type { CacheCheckResult } from \"../anomaly/cache-anomaly.js\";\n\nexport interface CacheCheckOptions {\n since?: string;\n json?: boolean;\n dataDir?: string;\n}\n\nexport async function runCacheCheck(options: CacheCheckOptions): Promise<void> {\n const duration = options.since ?? \"7d\";\n const sinceDate = parseDuration(duration);\n\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n since: sinceDate,\n });\n\n if (sessionFiles.length === 0) {\n console.log(`No sessions found in the last ${duration}.`);\n return;\n }\n\n const settled = await Promise.allSettled(\n sessionFiles.map(async (sf) => {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug, sf.subagentPaths);\n return checkCacheAnomaly(session);\n }),\n );\n const results: CacheCheckResult[] = settled\n .filter((r): r is PromiseFulfilledResult<CacheCheckResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (results.length === 0) {\n console.log(\"No valid sessions found to analyze.\");\n return;\n }\n\n if (options.json) {\n console.log(formatCacheCheckJson(results));\n } else {\n console.log(renderCacheCheckReport(results));\n }\n}\n","/**\n * Cross-project comparison command.\n * Compares average quality metrics across multiple projects.\n */\n\nimport { scanSessions } from \"../parser/project-scanner.js\";\nimport { readJsonl } from \"../parser/jsonl-reader.js\";\nimport { buildSession } from \"../parser/session-builder.js\";\nimport { gradeSession, gradeLetterFromScore } from \"../metrics/grader.js\";\nimport { concurrentSettled } from \"../utils/concurrent.js\";\nimport { getCachedGrade, setCachedGrade } from \"../cache/grade-cache.js\";\nimport chalk from \"chalk\";\nimport Table from \"cli-table3\";\nimport type { GradeResult, SessionFile } from \"../parser/types.js\";\n\nconst CONCURRENCY = 16;\n\nexport interface CompareOptions {\n projects: string;\n json?: boolean;\n dataDir?: string;\n since?: string;\n}\n\ninterface ProjectSummary {\n name: string;\n sessionCount: number;\n avgGrade: number;\n avgLetter: string;\n metrics: Map<string, number>;\n}\n\nexport async function runCompare(options: CompareOptions): Promise<void> {\n const projectNames = options.projects.split(\",\").map((p) => p.trim());\n const summaries: ProjectSummary[] = [];\n\n const projectSummaries = await Promise.all(\n projectNames.map(async (projectFilter) => {\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: projectFilter,\n });\n\n if (sessionFiles.length === 0) return null;\n\n const settled = await concurrentSettled(\n sessionFiles,\n CONCURRENCY,\n async (sf: SessionFile) => {\n const cached = getCachedGrade(sf.path, sf.mtime);\n if (cached) return cached;\n const records = readJsonl(sf.path);\n const session = await buildSession(\n records,\n sf.sessionId,\n sf.projectSlug,\n sf.subagentPaths,\n );\n const grade = gradeSession(session);\n setCachedGrade(sf.path, sf.mtime, grade);\n return grade;\n },\n );\n\n const grades: GradeResult[] = settled\n .filter((r): r is PromiseFulfilledResult<GradeResult> => r.status === \"fulfilled\")\n .map((r) => r.value);\n\n if (grades.length === 0) return null;\n\n const avgScore = grades.reduce((s, g) => s + g.score, 0) / grades.length;\n const metricAvgs = new Map<string, number>();\n for (const metric of grades[0].metrics) {\n const values = grades\n .map((g) => g.metrics.find((m) => m.name === metric.name)?.value)\n .filter((v): v is number => v !== null);\n if (values.length > 0) {\n metricAvgs.set(metric.name, values.reduce((a, b) => a + b, 0) / values.length);\n }\n }\n\n return {\n name: projectFilter,\n sessionCount: grades.length,\n avgGrade: Math.round(avgScore),\n avgLetter: gradeLetterFromScore(avgScore),\n metrics: metricAvgs,\n } as ProjectSummary;\n }),\n );\n\n for (const s of projectSummaries) {\n if (s !== null) summaries.push(s);\n }\n\n if (summaries.length === 0) {\n console.log(\"No matching projects found.\");\n return;\n }\n\n if (options.json) {\n const jsonOutput = summaries.map((s) => ({\n project: s.name,\n sessions: s.sessionCount,\n grade: s.avgLetter,\n score: s.avgGrade,\n metrics: Object.fromEntries(s.metrics),\n }));\n console.log(JSON.stringify({ compare: jsonOutput }, null, 2));\n return;\n }\n\n const lines: string[] = [];\n lines.push(\"\");\n lines.push(chalk.bold(\" Project comparison\"));\n lines.push(\"\");\n\n const head = [\"Project\", \"Sessions\", \"Grade\", ...summaries[0]?.metrics.keys() ?? []].map(\n (h) => chalk.dim(h),\n );\n\n const table = new Table({\n head,\n style: { head: [], border: [], \"padding-left\": 2, \"padding-right\": 2 },\n chars: {\n top: \"─\", \"top-mid\": \"─\", \"top-left\": \" \", \"top-right\": \"\",\n bottom: \"─\", \"bottom-mid\": \"─\", \"bottom-left\": \" \", \"bottom-right\": \"\",\n left: \" \", \"left-mid\": \" \", mid: \"─\", \"mid-mid\": \"─\",\n right: \"\", \"right-mid\": \"\", middle: \" \",\n },\n });\n\n for (const summary of summaries) {\n const row: string[] = [\n summary.name,\n summary.sessionCount.toString(),\n summary.avgLetter,\n ];\n for (const [, value] of summary.metrics) {\n row.push(value.toFixed(2));\n }\n table.push(row);\n }\n\n lines.push(table.toString());\n lines.push(\"\");\n console.log(lines.join(\"\\n\"));\n}\n\n"],"mappings":";;;AAOA,SAAS,eAAe;AACxB,SAAS,cAAc;;;ACJvB,OAAOA,YAAW;;;ACGlB,SAAS,SAAS,YAAY;AAC9B,SAAS,QAAAC,OAAM,UAAU,eAAe;;;ACJxC,SAAS,YAAY;AACrB,SAAS,eAAe;AAOjB,SAAS,eAAuB;AACrC,SAAO,KAAK,QAAQ,GAAG,SAAS;AAClC;AAEO,SAAS,mBAA2B;AACzC,SAAO,KAAK,aAAa,GAAG,mBAAmB;AACjD;;;ADFA,eAAsB,aAAa,SAIR;AACzB,QAAM,YAAY,SAAS,WAAW,aAAa;AACnD,QAAM,cAAcC,MAAK,WAAW,UAAU;AAE9C,MAAI;AACJ,MAAI;AACF,kBAAc,MAAM,QAAQ,WAAW;AAAA,EACzC,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,YAEe,WAAW;AAAA,IAC5B;AAAA,EACF;AAGA,MAAI,SAAS,SAAS;AACpB,kBAAc,YAAY;AAAA,MAAO,CAAC,QAChC,IAAI,YAAY,EAAE,SAAS,QAAQ,QAAS,YAAY,CAAC;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,iBAAiB,MAAM,QAAQ;AAAA,IACnC,YACG,OAAO,CAAC,QAAQ,CAAC,IAAI,WAAW,GAAG,CAAC,EACpC,IAAI,OAAO,eAAe;AACzB,YAAM,iBAAiBA,MAAK,aAAa,UAAU;AACnD,UAAI;AACJ,UAAI;AACF,kBAAU,MAAM,QAAQ,cAAc;AAAA,MACxC,QAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAEA,YAAM,cAAc,MAAM,QAAQ;AAAA,QAChC,QACG,OAAO,CAAC,UAAU,QAAQ,KAAK,MAAM,QAAQ,EAC7C,IAAI,OAAO,UAAU;AACpB,gBAAM,WAAWA,MAAK,gBAAgB,KAAK;AAC3C,gBAAM,YAAY,SAAS,OAAO,QAAQ;AAC1C,cAAI;AACF,kBAAM,WAAW,MAAM,KAAK,QAAQ;AACpC,gBAAI,SAAS,SAAS,SAAS,QAAQ,QAAQ,MAAO,QAAO;AAE7D,gBAAI;AACJ,gBAAI;AACF,oBAAM,cAAcA,MAAK,gBAAgB,WAAW,WAAW;AAC/D,oBAAM,aAAa,MAAM,QAAQ,WAAW;AAC5C,oBAAM,QAAQ,WACX,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,KAAK,EAAE,SAAS,QAAQ,CAAC,EAC5D,IAAI,CAAC,MAAMA,MAAK,aAAa,CAAC,CAAC;AAClC,kBAAI,MAAM,SAAS,EAAG,iBAAgB;AAAA,YACxC,QAAQ;AAAA,YAER;AAEA,mBAAO;AAAA,cACL,MAAM;AAAA,cACN;AAAA,cACA,aAAa;AAAA,cACb,OAAO,SAAS;AAAA,cAChB;AAAA,YACF;AAAA,UACF,QAAQ;AACN,mBAAO;AAAA,UACT;AAAA,QACF,CAAC;AAAA,MACL;AACA,aAAO,YAAY,OAAO,CAAC,MAAwB,MAAM,IAAI;AAAA,IAC/D,CAAC;AAAA,EACL;AAEA,QAAM,WAA0B,eAAe,KAAK;AAGpD,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC7D,SAAO;AACT;AAKA,eAAsB,qBAAqB,SAGlB;AACvB,QAAM,WAAW,MAAM,aAAa,OAAO;AAC3C,MAAI,SAAS,WAAW,GAAG;AACzB,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO,SAAS,CAAC;AACnB;;;AE5GA,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAOhC,gBAAuB,UAAU,UAA6C;AAC5E,QAAM,SAAS,iBAAiB,UAAU,EAAE,UAAU,QAAQ,CAAC;AAC/D,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,WAAW,SAAS,CAAC;AAEjE,mBAAiB,QAAQ,IAAI;AAC3B,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,EAAG;AAE1B,QAAI;AACF,YAAM,SAAS,KAAK,MAAM,OAAO;AACjC,UAAI,UAAU,OAAO,WAAW,YAAY,UAAU,QAAQ;AAC5D,cAAM;AAAA,MACR;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;ACrBA,SAAS,YAAAC,iBAAgB;;;ACmIlB,IAAM,aAAa,oBAAI,IAAI;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;;;AD5GD,eAAsB,aACpB,SACA,WACA,aACA,eACkB;AAClB,QAAM,QAAsB,CAAC;AAC7B,QAAM,qBAAqB,oBAAI,IAAY;AAE3C,MAAI,MAAM;AACV,MAAI,YAA2B;AAC/B,MAAI,QAAQ;AACZ,MAAI,iBAAiB;AACrB,MAAI,gBAAgB;AACpB,MAAI,gBAAgB;AAEpB,iBAAe,eACb,QACA,SACA;AACA,UAAM,kBAAkB,oBAAI,IAAkC;AAE9D,qBAAiB,UAAU,QAAQ;AACjC,UAAI,CAAC,iBAAiB,aAAa,UAAU,OAAO,OAAO,YAAY,UAAU;AAC/E,wBAAgB,OAAO;AAAA,MACzB;AAEA,UAAI,WAAW,IAAI,OAAO,IAAI,EAAG;AAEjC,UAAI,OAAO,SAAS,QAAQ;AAC1B,cAAM,aAAa;AACnB,yBAAiB,YAAY,OAAO;AACpC,wBAAgB,UAAU;AAAA,MAC5B,WAAW,OAAO,SAAS,aAAa;AACtC,cAAM,kBAAkB;AACxB,YAAI,gBAAgB,QAAQ,UAAU,cAAe;AACrD,YAAI,gBAAgB,MAAO;AAC3B,6BAAqB,iBAAiB,eAAe;AACrD,wBAAgB,eAAe;AAAA,MACjC,OAAO;AACL,2BAAmB,IAAI,OAAO,IAAI;AAAA,MACpC;AAAA,IACF;AAGA,eAAW,CAAC,EAAE,GAAG,KAAK,iBAAiB;AACrC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,SAAS,IAAI;AAAA,QACb,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAW,IAAI;AAAA,QACf,aAAa;AAAA,QACb,OAAO,IAAI;AAAA,QACX;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,eAAe,SAAS,MAAS;AAEvC,aAAW,aAAa,iBAAiB,CAAC,GAAG;AAC3C,UAAM,UAAUC,UAAS,WAAW,QAAQ;AAC5C,UAAM,eAAe,UAAU,SAAS,GAAG,OAAO;AAAA,EACpD;AAGA,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAE3D,QAAM,cAAc,IAAI;AAAA,IACtB,MAAM,OAAO,CAAC,MAAM,EAAE,YAAY,MAAS,EAAE,IAAI,CAAC,MAAM,EAAE,OAAQ;AAAA,EACpE;AACA,QAAM,oBAAoB,MAAM,OAAO,CAAC,MAAM,EAAE,YAAY,MAAS,EAAE;AAEvE,SAAO;AAAA,IACL,IAAI;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,YACE,kBAAkB,gBACd,IAAI,KAAK,aAAa,EAAE,QAAQ,IAAI,IAAI,KAAK,cAAc,EAAE,QAAQ,IACrE;AAAA,IACN,eAAe,YAAY;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAIA,WAAS,gBAAgB,QAAsC;AAC7D,QAAI,CAAC,kBAAkB,OAAO,WAAW;AACvC,uBAAiB,OAAO;AAAA,IAC1B;AACA,QAAI,OAAO,WAAW;AACpB,sBAAgB,OAAO;AAAA,IACzB;AACA,QAAI,CAAC,OAAO,OAAO,KAAK;AACtB,YAAM,OAAO;AAAA,IACf;AACA,QAAI,cAAc,QAAQ,OAAO,WAAW;AAC1C,kBAAY,OAAO;AAAA,IACrB;AACA,QAAI,CAAC,SAAS,OAAO,SAAS,aAAa;AACzC,YAAM,KAAK;AACX,UAAI,GAAG,QAAQ,SAAS,GAAG,QAAQ,UAAU,eAAe;AAC1D,gBAAQ,GAAG,QAAQ;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEA,WAAS,iBAAiB,QAAoB,SAA6B;AACzE,UAAM,UAAU,OAAO,QAAQ;AAC/B,UAAM,cAAc,OAAO,YAAY,YAAY,CAAC,OAAO;AAC3D,UAAM,KAAK;AAAA,MACT,MAAM;AAAA,MACN,SAAS,iBAAiB,OAAO;AAAA,MACjC,OAAO;AAAA,MACP,UAAU;AAAA,MACV,WAAW,OAAO;AAAA,MAClB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,qBACP,QACA,QACA;AACA,UAAM,YAAY,OAAO,QAAQ;AACjC,QAAI,MAAM,OAAO,IAAI,SAAS;AAE9B,QAAI,CAAC,KAAK;AACR,YAAM;AAAA,QACJ,SAAS,CAAC;AAAA,QACV,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW,OAAO;AAAA,QAClB,OAAO,OAAO,QAAQ;AAAA,MACxB;AACA,aAAO,IAAI,WAAW,GAAG;AAAA,IAC3B;AAGA,eAAW,SAAS,OAAO,QAAQ,SAAS;AAC1C,UAAI,QAAQ,KAAK,KAAK;AAAA,IACxB;AAGA,QAAI,OAAO,QAAQ,gBAAgB,MAAM;AACvC,UAAI,WAAW;AACf,UAAI,QAAQ,OAAO,QAAQ;AAAA,IAC7B;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,SAAkD;AAC1E,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,EACzC;AACA,SAAO;AACT;;;AElMA,IAAM,aAAa,oBAAI,IAAI,CAAC,SAAS,QAAQ,cAAc,CAAC;AAC5D,IAAM,YAAY;AAEX,SAAS,oBAAoB,SAAgC;AAClE,MAAI,qBAAqB;AACzB,QAAM,SAAmB,CAAC;AAE1B,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,YAAa;AAE/B,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,MAAM,SAAS,WAAY;AAC/B,YAAM,YAAY;AAElB,UAAI,UAAU,SAAS,WAAW;AAChC;AAAA,MACF,WAAW,WAAW,IAAI,UAAU,IAAI,GAAG;AACzC,eAAO,KAAK,kBAAkB;AAC9B,6BAAqB;AAAA,MACvB;AAAA,IACF;AAAA,EACF;AAEA,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAMC,WAAU,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO;AAE3D,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,MAAMA,QAAO;AAAA,IACpB,QAAQA,YAAW,IAAM,YAAYA,YAAW,IAAM,YAAY;AAAA,IAClE,OAAO,MAAMA,QAAO,EAAE,SAAS;AAAA,EACjC;AACF;AAEA,SAAS,MAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;AC7CO,SAAS,oBAAoB,SAAgC;AAClE,MAAI,SAAS;AACb,MAAI,QAAQ;AAEZ,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,YAAa;AAE/B,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,MAAM,SAAS,WAAY;AAC/B,YAAM,YAAY;AAElB,UAAI,UAAU,SAAS,QAAS;AAAA,eACvB,UAAU,SAAS,UAAU,UAAU,SAAS,eAAgB;AAAA,IAC3E;AAAA,EACF;AAEA,QAAM,QAAQ,SAAS;AACvB,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,QAAQ,SAAS;AAEvB,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOC,OAAM,KAAK;AAAA,IAClB,QAAQ,SAAS,OAAO,YAAY,SAAS,MAAM,YAAY;AAAA,IAC/D,OAAOA,OAAM,KAAK,EAAE,SAAS;AAAA,EAC/B;AACF;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;ACpCO,SAAS,oBAAoB,SAAgC;AAClE,MAAI,iBAAiB;AACrB,MAAI,qBAAqB;AAEzB,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,eAAe,CAAC,KAAK,SAAS,CAAC,KAAK,SAAU;AAEhE,sBAAkB,KAAK,MAAM;AAC7B,0BAAsB,KAAK,MAAM;AAAA,EACnC;AAEA,QAAM,aAAa,iBAAiB;AACpC,MAAI,eAAe,GAAG;AACpB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,OAAO,iBAAiB;AAE9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOC,OAAM,IAAI;AAAA,IACjB,QAAQ,QAAQ,MAAM,YAAY,QAAQ,MAAM,YAAY;AAAA,IAC5D,OAAOA,OAAM,IAAI,EAAE,SAAS;AAAA,EAC9B;AACF;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;ACrCA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,sBAAsB,SAAgC;AACpE,QAAM,iBAAiB,QAAQ,MAAM;AAAA,IACnC,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE;AAAA,EACrC;AAEA,MAAI,eAAe;AACnB,MAAI,qBAAqB;AAEzB,aAAW,QAAQ,gBAAgB;AACjC,UAAM,YAAY,gBAAgB,IAAI;AACtC,QAAI,CAAC,UAAW;AAEhB;AAMA,UAAM,aAAa,KAAK,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU;AACjE,QAAI,CAAC,YAAY;AACf;AAAA,IACF;AAAA,EACF;AAEA,MAAI,iBAAiB,GAAG;AACtB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,OAAO,IAAI,qBAAqB;AAEtC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOC,OAAM,IAAI;AAAA,IACjB,QAAQ,QAAQ,MAAM,YAAY,QAAQ,MAAM,YAAY;AAAA,IAC5D,OAAOA,OAAM,IAAI,EAAE,SAAS;AAAA,EAC9B;AACF;AAEA,SAAS,gBAAgB,MAA2B;AAClD,aAAW,SAAS,KAAK,SAAS;AAChC,QAAI,MAAM,SAAS,QAAQ;AACzB,YAAM,YAAY;AAClB,UAAI,gBAAgB,KAAK,CAAC,MAAM,EAAE,KAAK,UAAU,IAAI,CAAC,GAAG;AACvD,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;ACpEO,SAAS,oBAAoB,GAAW,GAAmB;AAChE,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,EAAE,WAAW,EAAG,QAAO,EAAE;AAC7B,MAAI,EAAE,WAAW,EAAG,QAAO,EAAE;AAG7B,MAAI,EAAE,SAAS,EAAE,OAAQ,EAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC;AAEvC,QAAM,OAAO,EAAE;AACf,QAAM,OAAO,EAAE;AACf,QAAM,MAAM,IAAI,MAAc,OAAO,CAAC;AAEtC,WAAS,IAAI,GAAG,KAAK,MAAM,IAAK,KAAI,CAAC,IAAI;AAEzC,WAAS,IAAI,GAAG,KAAK,MAAM,KAAK;AAC9B,QAAI,OAAO,IAAI,CAAC;AAChB,QAAI,CAAC,IAAI;AAET,aAAS,IAAI,GAAG,KAAK,MAAM,KAAK;AAC9B,YAAM,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI;AACzC,YAAM,OAAO,IAAI,CAAC;AAClB,UAAI,CAAC,IAAI,KAAK;AAAA,QACZ,IAAI,CAAC,IAAI;AAAA;AAAA,QACT,IAAI,IAAI,CAAC,IAAI;AAAA;AAAA,QACb,OAAO;AAAA;AAAA,MACT;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,IAAI,IAAI;AACjB;AAMO,SAAS,qBACd,GACA,GACA,SAAS,KACD;AACR,QAAM,SAAS,EAAE,MAAM,GAAG,MAAM;AAChC,QAAM,SAAS,EAAE,MAAM,GAAG,MAAM;AAChC,QAAM,YAAY,KAAK,IAAI,OAAO,QAAQ,OAAO,MAAM;AAEvD,MAAI,cAAc,EAAG,QAAO;AAE5B,QAAM,WAAW,oBAAoB,QAAQ,MAAM;AACnD,SAAO,IAAI,WAAW;AACxB;;;ACjDO,SAAS,oBAAoB,SAAgC;AAElE,QAAM,aAAuB,CAAC;AAC9B,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,CAAC,KAAK,YAAa;AACvB,UAAM,OAAO,KAAK,QACf,OAAO,CAAC,MAAsB,EAAE,SAAS,MAAM,EAC/C,IAAI,CAAC,MAAM,EAAE,IAAI,EACjB,KAAK,GAAG;AACX,QAAI,KAAK,SAAS,EAAG,YAAW,KAAK,IAAI;AAAA,EAC3C;AAEA,MAAI,WAAW,SAAS,GAAG;AACzB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,UAAU;AACd,QAAM,QAAQ,WAAW,SAAS;AAElC,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,aAAa,qBAAqB,WAAW,CAAC,GAAG,WAAW,IAAI,CAAC,CAAC;AACxE,QAAI,aAAa,KAAK;AACpB;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,UAAU;AAE1B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOC,OAAM,OAAO;AAAA,IACpB,QAAQ,WAAW,MAAM,YAAY,WAAW,OAAO,YAAY;AAAA,IACnE,OAAOA,OAAM,OAAO,EAAE,SAAS;AAAA,EACjC;AACF;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;AC5CO,SAAS,qBAAqB,SAAgC;AACnE,QAAM,aAAa,oBAAI,IAAoB;AAE3C,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,YAAa;AAE/B,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,MAAM,SAAS,WAAY;AAC/B,YAAM,YAAY;AAClB,iBAAW,IAAI,UAAU,OAAO,WAAW,IAAI,UAAU,IAAI,KAAK,KAAK,CAAC;AAAA,IAC1E;AAAA,EACF;AAEA,QAAM,cAAc,WAAW;AAC/B,MAAI,eAAe,GAAG;AACpB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO,gBAAgB,IAAI,OAAO;AAAA,MAClC,QAAQ,gBAAgB,IAAI,YAAY;AAAA,MACxC,OAAO,gBAAgB,IAAI,QAAQ;AAAA,MACnC,QAAQ,gBAAgB,IACpB,kCACA,uBAAuB,CAAC,GAAG,WAAW,KAAK,CAAC,EAAE,CAAC,CAAC;AAAA,IACtD;AAAA,EACF;AAEA,QAAM,aAAa,CAAC,GAAG,WAAW,OAAO,CAAC,EAAE,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC;AACrE,QAAM,aAAa,KAAK,KAAK,WAAW;AAExC,MAAI,UAAU;AACd,aAAW,SAAS,WAAW,OAAO,GAAG;AACvC,UAAM,IAAI,QAAQ;AAClB,eAAW,IAAI,KAAK,KAAK,CAAC;AAAA,EAC5B;AAEA,QAAM,aAAa,UAAU;AAG7B,QAAM,SAAS,CAAC,GAAG,WAAW,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AACnE,QAAM,UAAU,OAAO,CAAC;AACxB,QAAM,aAAa,KAAK,MAAO,QAAQ,CAAC,IAAI,aAAc,GAAG;AAC7D,QAAM,SAAS,cAAc,QAAQ,CAAC,CAAC,KAAK,UAAU;AAEtD,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAOC,OAAM,UAAU;AAAA,IACvB,QAAQ,cAAc,MAAM,YAAY,cAAc,MAAM,YAAY;AAAA,IACxE,OAAOA,OAAM,UAAU,EAAE,SAAS;AAAA,IAClC;AAAA,EACF;AACF;AAEA,SAASA,OAAM,GAAmB;AAChC,SAAO,KAAK,MAAM,IAAI,GAAG,IAAI;AAC/B;;;ACtDA,IAAMC,cAAa,oBAAI,IAAI,CAAC,SAAS,QAAQ,cAAc,CAAC;AAErD,SAAS,qBAAqB,SAAgC;AACnE,MAAI,oBAAoB;AACxB,MAAI,YAAY;AAEhB,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,YAAa;AAG/B,QAAI,KAAK,YAAY,KAAK,OAAO;AAC/B,2BAAqB,KAAK,MAAM;AAAA,IAClC;AAGA,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,MAAM,SAAS,WAAY;AAC/B,YAAM,YAAY;AAClB,UAAIA,YAAW,IAAI,UAAU,IAAI,EAAG;AAAA,IACtC;AAAA,EACF;AAEA,MAAI,cAAc,GAAG;AACnB,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAoB;AAElC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,KAAK,MAAM,KAAK;AAAA,IACvB,QAAQ,SAAS,MAAO,YAAY,SAAS,OAAQ,YAAY;AAAA,IACjE,OAAO,KAAK,MAAM,KAAK,EAAE,eAAe,OAAO;AAAA,EACjD;AACF;;;ACxCO,SAAS,wBAAwB,SAAgC;AACtE,MAAI,QAAQ,kBAAkB,GAAG;AAC/B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,aAAa;AACjB,MAAI,iBAAiB;AAErB,aAAW,QAAQ,QAAQ,OAAO;AAChC,QAAI,KAAK,SAAS,eAAe,CAAC,KAAK,MAAO;AAC9C,UAAM,MAAM,KAAK,MAAM,iBAAiB;AACxC,QAAI,KAAK,YAAY,QAAW;AAC9B,oBAAc;AAAA,IAChB,OAAO;AACL,wBAAkB;AAAA,IACpB;AAAA,EACF;AAEA,QAAM,QAAQ,aAAa;AAC3B,MAAI,UAAU,GAAG;AACf,WAAO;AAAA,MACL,MAAM;AAAA,MACN,OAAO;AAAA,MACP,QAAQ;AAAA,MACR,OAAO;AAAA,MACP,QAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,QAAQ,aAAa;AAC3B,QAAM,SAAS,QAAQ,MAAM,YAAY,QAAQ,MAAM,YAAY;AAEnE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,OAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AAAA,IACjC;AAAA,IACA,OAAO,GAAG,KAAK,MAAM,QAAQ,GAAG,CAAC;AAAA,EACnC;AACF;;;AC9BA,IAAM,iBAAiC;AAAA,EACrC;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,MAAM,IAAI,IAAI,KAAK,GAAG,GAAG;AAAA,EACzC;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,GAAG,GAAG;AAAA,EACjD;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,MAAM,IAAI,MAAM,KAAK,GAAG,GAAG;AAAA,EAC3C;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,OAAO,IAAI,OAAO,MAAM,KAAK,GAAG,GAAG;AAAA,EACnD;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,GAAG,GAAG;AAAA,EAClD;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,MAAM,IAAI,MAAM,KAAK,GAAG,GAAG;AAAA,EAC3C;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,OAAO,KAAK,IAAI,OAAQ,OAAS,KAAK,GAAG,GAAG;AAAA,EAC5D;AAAA,EACA;AAAA,IACE,SAAS;AAAA,IACT,QAAQ;AAAA;AAAA,IAER,OAAO,CAAC,MAAM,OAAO,IAAI,KAAK,MAAM,KAAK,GAAG,GAAG;AAAA,EACjD;AACF;AAEA,IAAM,mBAA4C;AAAA,EAChD,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,GAAG;AAAA,EACR,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,GAAG;AAAA,EACR,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,GAAG;AAAA,EACR,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,IAAI,GAAG;AAAA,EACR,CAAC,IAAI,IAAI;AAAA,EACT,CAAC,GAAG,GAAG;AACT;AAEO,SAAS,qBAAqB,OAAuB;AAC1D,SAAO,iBAAiB,KAAK,CAAC,CAAC,SAAS,MAAM,SAAS,SAAS,IAAI,CAAC,KAAK;AAC5E;AAEO,SAAS,aAAa,SAA+B;AAC1D,QAAM,UAA0B,CAAC;AACjC,MAAI,cAAc;AAClB,MAAI,cAAc;AAElB,aAAW,MAAM,gBAAgB;AAC/B,UAAM,SAAS,GAAG,QAAQ,OAAO;AACjC,YAAQ,KAAK,MAAM;AAEnB,QAAI,OAAO,UAAU,MAAM;AACzB,qBAAe,GAAG,MAAM,OAAO,KAAK,IAAI,GAAG;AAC3C,qBAAe,GAAG;AAAA,IACpB;AAAA,EACF;AAGA,QAAM,iBAAiB,cAAc,IAAI,cAAc,cAAc;AAErE,QAAM,SAAS,qBAAqB,cAAc;AAElD,SAAO;AAAA,IACL;AAAA,IACA,OAAO,KAAK,MAAM,cAAc;AAAA,IAChC;AAAA,EACF;AACF;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;AAC9D,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;;ACxHA,OAAO,WAAW;AAClB,OAAO,WAAW;;;ACeX,SAAS,eAAe,IAAoB;AACjD,QAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AAEnC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,MAAI,UAAU,GAAI,QAAO,GAAG,OAAO;AAEnC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,QAAM,mBAAmB,UAAU;AACnC,MAAI,qBAAqB,EAAG,QAAO,GAAG,KAAK;AAC3C,SAAO,GAAG,KAAK,KAAK,gBAAgB;AACtC;AAGO,SAAS,eAAe,IAAoB;AACjD,SAAO,GAAG,MAAM,GAAG,CAAC;AACtB;AAGO,SAAS,oBAAoB,MAAsB;AACxD,QAAM,QAAQ,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO;AAC5C,SAAO,MAAM,MAAM,SAAS,CAAC,KAAK;AACpC;;;ACnCA,IAAM,OAA+C;AAAA,EACnD,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,iBAAiB;AAAA,IACf,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,kBAAkB;AAAA,IAChB,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AAAA,EACA,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,UAAU;AAAA,EACZ;AACF;AAEO,SAAS,OAAO,QAAqC;AAC1D,MAAI,OAAO,WAAW,UAAW,QAAO;AAExC,QAAM,aAAa,KAAK,OAAO,IAAI;AACnC,MAAI,CAAC,WAAY,QAAO;AAExB,SAAO,WAAW,OAAO,MAAM,KAAK;AACtC;AAEO,SAAS,WAAW,SAAmC;AAC5D,SAAO,QACJ,IAAI,MAAM,EACV,OAAO,CAAC,QAAuB,QAAQ,IAAI;AAChD;;;ACnDA,SAAS,qBAAqB;AAE9B,IAAM,WAAW,cAAc,YAAY,GAAG;AACvC,IAAM,UAAmB,SAAS,iBAAiB,EAA0B;;;AHUpF,IAAM,eAAuC;AAAA,EAC3C,SAAS,MAAM,MAAM,QAAG;AAAA,EACxB,SAAS,MAAM,OAAO,QAAG;AAAA,EACzB,UAAU,MAAM,IAAI,QAAG;AACzB;AAEA,IAAM,gBAAwC;AAAA,EAC5C,SAAS,MAAM,MAAM,SAAS;AAAA,EAC9B,SAAS,MAAM,OAAO,SAAS;AAAA,EAC/B,UAAU,MAAM,IAAI,UAAU;AAChC;AAEA,IAAM,uBAA+C;AAAA,EACnD,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,qBAAqB;AACvB;AAEO,SAAS,kBAAkB,SAAkB,OAA4B;AAC9E,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,MAAM,KAAK,eAAe,OAAO,EAAE,IAAI,MAAM,IAAI,8CAAyC,CAAC;AACtG,QAAM,KAAK,EAAE;AAEb,QAAM,YAAY,QAAQ,gBAAgB,IACtC,GAAG,QAAQ,aAAa,gBAAgB,QAAQ,MAAM,MAAM,WAC5D,GAAG,QAAQ,MAAM,MAAM;AAE3B,QAAM,cAAc;AAAA,IAClB,YAAY,MAAM,KAAK,eAAe,QAAQ,EAAE,CAAC,CAAC;AAAA,IAClD,oBAAoB,QAAQ,WAAW;AAAA,IACvC,eAAe,QAAQ,UAAU;AAAA,IACjC,QAAQ;AAAA,IACR;AAAA,EACF,EAAE,KAAK,MAAM,IAAI,KAAK,CAAC;AACvB,QAAM,KAAK,KAAK,WAAW,EAAE;AAC7B,QAAM,KAAK,EAAE;AAEb,QAAM,aAAa,cAAc,MAAM,MAAM;AAC7C,QAAM,KAAK,oBAAoB,WAAW,MAAM,KAAK,MAAM,MAAM,CAAC,CAAC,EAAE;AACrE,QAAM,KAAK,EAAE;AAEb,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,MAAM,CAAC,UAAU,SAAS,QAAQ,EAAE,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC;AAAA,IAC3D,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,gBAAgB,GAAG,iBAAiB,EAAE;AAAA,IACrE,OAAO;AAAA,MACL,KAAK;AAAA,MAAK,WAAW;AAAA,MAAK,YAAY;AAAA,MAAM,aAAa;AAAA,MACzD,QAAQ;AAAA,MAAK,cAAc;AAAA,MAAK,eAAe;AAAA,MAAM,gBAAgB;AAAA,MACrE,MAAM;AAAA,MAAM,YAAY;AAAA,MAAM,KAAK;AAAA,MAAK,WAAW;AAAA,MACnD,OAAO;AAAA,MAAI,aAAa;AAAA,MAAI,QAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAED,aAAW,UAAU,MAAM,SAAS;AAClC,UAAM,cAAc,qBAAqB,OAAO,IAAI,KAAK,OAAO;AAChE,UAAM,OAAO,aAAa,OAAO,MAAM,KAAK;AAC5C,UAAM,KAAK,CAAC,aAAa,OAAO,OAAO,GAAG,IAAI,IAAI,cAAc,OAAO,MAAM,KAAK,OAAO,MAAM,EAAE,CAAC;AAAA,EACpG;AAEA,QAAM,KAAK,MAAM,SAAS,CAAC;AAE3B,QAAM,OAAO,WAAW,MAAM,OAAO;AACrC,MAAI,KAAK,SAAS,GAAG;AACnB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,MAAM,OAAO,SAAS,CAAC;AAClC,eAAW,OAAO,MAAM;AACtB,YAAM,KAAK,KAAK,MAAM,IAAI,QAAG,CAAC,IAAI,GAAG,EAAE;AAAA,IACzC;AAAA,EACF;AAEA,QAAM,KAAK,EAAE;AACb,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,kBACd,SACA,cACA,QACQ;AACR,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,MAAM,KAAK,wBAAwB,MAAM,EAAE,IAAI,MAAM,IAAI,KAAK,YAAY,YAAY,CAAC;AAClG,QAAM,KAAK,EAAE;AAEb,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,MAAM,CAAC,UAAU,cAAc,YAAY,UAAU,QAAQ,EAAE,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC;AAAA,IACtF,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,gBAAgB,GAAG,iBAAiB,EAAE;AAAA,IACrE,OAAO;AAAA,MACL,KAAK;AAAA,MAAK,WAAW;AAAA,MAAK,YAAY;AAAA,MAAM,aAAa;AAAA,MACzD,QAAQ;AAAA,MAAK,cAAc;AAAA,MAAK,eAAe;AAAA,MAAM,gBAAgB;AAAA,MACrE,MAAM;AAAA,MAAM,YAAY;AAAA,MAAM,KAAK;AAAA,MAAK,WAAW;AAAA,MACnD,OAAO;AAAA,MAAI,aAAa;AAAA,MAAI,QAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAED,aAAW,UAAU,SAAS;AAC5B,UAAM,cAAc,qBAAqB,OAAO,IAAI,KAAK,OAAO;AAChE,UAAM,YAAY,OAAO,cAAc,OAAO,OAAO,UAAU,QAAQ,CAAC,IAAI;AAC5E,UAAM,UAAU,OAAO,YAAY,OAAO,OAAO,QAAQ,QAAQ,CAAC,IAAI;AAEtE,QAAI,YAAY;AAChB,QAAI,OAAO,kBAAkB,MAAM;AACjC,YAAM,QAAQ,OAAO,gBAAgB,IAAI,WAAM,OAAO,gBAAgB,IAAI,WAAM;AAChF,kBAAY,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,OAAO,aAAa,CAAC,CAAC;AAAA,IACpE;AAEA,UAAM,YAAY,uBAAuB,OAAO,MAAM;AACtD,UAAM,KAAK,CAAC,aAAa,WAAW,SAAS,WAAW,SAAS,CAAC;AAAA,EACpE;AAEA,QAAM,KAAK,MAAM,SAAS,CAAC;AAC3B,QAAM,KAAK,EAAE;AACb,SAAO,MAAM,KAAK,IAAI;AACxB;AAEO,SAAS,uBAAuB,SAAqC;AAC1E,QAAM,QAAkB,CAAC;AAEzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,MAAM,KAAK,sBAAsB,CAAC;AAC7C,QAAM,KAAK,EAAE;AAEb,QAAM,QAAQ,IAAI,MAAM;AAAA,IACtB,MAAM,CAAC,WAAW,WAAW,aAAa,QAAQ,EAAE,IAAI,CAAC,MAAM,MAAM,IAAI,CAAC,CAAC;AAAA,IAC3E,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,gBAAgB,GAAG,iBAAiB,EAAE;AAAA,IACrE,OAAO;AAAA,MACL,KAAK;AAAA,MAAK,WAAW;AAAA,MAAK,YAAY;AAAA,MAAM,aAAa;AAAA,MACzD,QAAQ;AAAA,MAAK,cAAc;AAAA,MAAK,eAAe;AAAA,MAAM,gBAAgB;AAAA,MACrE,MAAM;AAAA,MAAM,YAAY;AAAA,MAAM,KAAK;AAAA,MAAK,WAAW;AAAA,MACnD,OAAO;AAAA,MAAI,aAAa;AAAA,MAAI,QAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAED,aAAW,UAAU,SAAS;AAC5B,UAAM,SAAS,OAAO,iBAAiB,OAAO,OAAO,aAAa,QAAQ,CAAC,IAAI;AAC/E,UAAM,YAAY,OAAO,YACrB,MAAM,IAAI,gBAAW,IACrB,MAAM,MAAM,eAAU;AAE1B,UAAM,KAAK;AAAA,MACT,eAAe,OAAO,SAAS;AAAA,MAC/B,oBAAoB,OAAO,WAAW;AAAA,MACtC;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,KAAK,MAAM,SAAS,CAAC;AAE3B,QAAM,YAAY,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS;AACnD,MAAI,UAAU,SAAS,GAAG;AACxB,UAAM,KAAK,EAAE;AACb,UAAM;AAAA,MACJ,MAAM,OAAO,YAAO,UAAU,MAAM,iDAAiD;AAAA,IACvF;AACA,eAAW,KAAK,WAAW;AACzB,UAAI,EAAE,oBAAoB;AACxB,cAAM;AAAA,UACJ,KAAK,MAAM,IAAI,QAAG,CAAC,YAAY,eAAe,EAAE,SAAS,CAAC,cAAc,EAAE,kBAAkB;AAAA,QAC9F;AAAA,MACF;AAAA,IACF;AACA,UAAM;AAAA,MACJ,MAAM,IAAI,gEAAgE;AAAA,IAC5E;AAAA,EACF,OAAO;AACL,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,MAAM,MAAM,uCAAkC,CAAC;AAAA,EAC5D;AAEA,QAAM,KAAK,EAAE;AACb,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,cAAc,QAA0C;AAC/D,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,MAAM;AACzC,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,MAAM;AACzC,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,MAAM;AACzC,SAAO,MAAM;AACf;AAEA,SAAS,uBAAuB,QAAwB;AACtD,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,MAAM,MAAM,eAAU;AAAA,IAC/B,KAAK;AACH,aAAO,MAAM,OAAO,kBAAa;AAAA,IACnC,KAAK;AACH,aAAO,MAAM,IAAI,mBAAc;AAAA,IACjC;AACE,aAAO;AAAA,EACX;AACF;;;AI1LO,SAAS,gBAAgB,SAAkB,OAA4B;AAC5E,QAAM,SAA0B;AAAA,IAC9B,SAAS;AAAA,MACP,IAAI,QAAQ;AAAA,MACZ,SAAS,QAAQ;AAAA,MACjB,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,MACpB,WAAW,QAAQ;AAAA,IACrB;AAAA,IACA,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,SAAS,MAAM,QAAQ,IAAI,CAAC,OAAO;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,OAAO,EAAE;AAAA,MACT,QAAQ,EAAE;AAAA,MACV,OAAO,EAAE;AAAA,IACX,EAAE;AAAA,EACJ;AAEA,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;AAEO,SAAS,gBAAgB,SAAqC;AACnE,SAAO,KAAK,UAAU,EAAE,OAAO,QAAQ,GAAG,MAAM,CAAC;AACnD;AAEO,SAAS,qBAAqB,SAAqC;AACxE,SAAO,KAAK,UAAU,EAAE,YAAY,QAAQ,GAAG,MAAM,CAAC;AACxD;;;ApB1CA,IAAM,uBAAuB;AAS7B,eAAsB,SAAS,SAAsC;AACnE,QAAM,cAAc,MAAM,qBAAqB;AAAA,IAC7C,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,EACnB,CAAC;AAED,QAAM,UAAU,UAAU,YAAY,IAAI;AAC1C,QAAM,UAAU,MAAM;AAAA,IACpB;AAAA,IACA,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AAEA,QAAM,QAAQ,aAAa,OAAO;AAElC,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,gBAAgB,SAAS,KAAK,CAAC;AAC3C;AAAA,EACF;AAEA,MAAI,QAAQ,iBAAiB,QAAQ,kBAAkB,sBAAsB;AAC3E,YAAQ;AAAA,MACNC,OAAM;AAAA,QACJ,+BAA0B,QAAQ,aAAa,uBAAuB,oBAAoB;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AAEA,MAAI,QAAQ,mBAAmB,OAAO,GAAG;AACvC,UAAM,QAAQ,CAAC,GAAG,QAAQ,kBAAkB,EAAE,KAAK,EAAE,KAAK,IAAI;AAC9D,YAAQ,OAAO,MAAMA,OAAM,IAAI,uCAAuC,KAAK;AAAA,CAAI,CAAC;AAAA,EAClF;AAEA,UAAQ,IAAI,kBAAkB,SAAS,KAAK,CAAC;AAC/C;;;AqBtCO,SAAS,iBACd,QACA,aACiB;AACjB,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,SAAS,OAAO,MAAM,GAAG,WAAW;AAC1C,QAAM,OAAO;AAEb,QAAM,cAAc,OAAO,CAAC,EAAE,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI;AAEvD,SAAO,YAAY,IAAI,CAAC,SAAS;AAC/B,UAAM,eAAe,cAAc,QAAQ,IAAI;AAC/C,UAAM,aAAa,cAAc,MAAM,IAAI;AAE3C,UAAM,YAAY,QAAQ,YAAY;AACtC,UAAM,UAAU,QAAQ,UAAU;AAElC,QAAI,gBAA+B;AACnC,QAAI,cAAc,QAAQ,YAAY,QAAQ,YAAY,GAAG;AAC3D,uBAAkB,YAAY,WAAW,KAAK,IAAI,OAAO,IAAK;AAAA,IAChE;AAEA,WAAO,EAAE,MAAM,WAAW,SAAS,cAAc;AAAA,EACnD,CAAC;AACH;AAEA,SAAS,cAAc,QAAuB,YAA8B;AAC1E,QAAM,SAAmB,CAAC;AAC1B,aAAW,SAAS,QAAQ;AAC1B,UAAM,SAAS,MAAM,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU;AAC9D,QAAI,QAAQ,UAAU,QAAQ,QAAQ,UAAU,QAAW;AACzD,aAAO,KAAK,OAAO,KAAK;AAAA,IAC1B;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,QAAiC;AAChD,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO;AACpD;;;ACvCA,IAAM,mBAAmB,oBAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,kBACd,WACoB;AACpB,SAAO,UAAU,IAAI,CAAC,MAAM;AAC1B,QAAI,SAA2B;AAE/B,QAAI,EAAE,kBAAkB,MAAM;AAC5B,YAAM,aAAa,iBAAiB,IAAI,EAAE,IAAI;AAE9C,YAAM,eAAe,aAAa,EAAE,gBAAgB,IAAI,EAAE,gBAAgB;AAC1E,YAAM,YAAY,KAAK,IAAI,EAAE,aAAa;AAE1C,UAAI,gBAAgB,YAAY,IAAI;AAClC,iBAAS;AAAA,MACX,WAAW,gBAAgB,YAAY,IAAI;AACzC,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,WAAO;AAAA,MACL,MAAM,EAAE;AAAA,MACR,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,eAAe,EAAE;AAAA,MACjB;AAAA,IACF;AAAA,EACF,CAAC;AACH;;;AClDO,SAAS,cAAc,UAAkB,MAAM,oBAAI,KAAK,GAAS;AACtE,QAAM,QAAQ,SAAS,MAAM,UAAU;AACvC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,sBAAsB,QAAQ;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAClC,QAAM,SAAS,IAAI,KAAK,GAAG;AAC3B,SAAO,QAAQ,OAAO,QAAQ,IAAI,IAAI;AACtC,SAAO;AACT;;;ACfA,eAAsB,kBACpB,OACA,OACA,IACoC;AACpC,QAAM,UAAU,IAAI,MAA+B,MAAM,MAAM;AAC/D,MAAI,OAAO;AAEX,QAAM,SAAS,YAA2B;AACxC,WAAO,OAAO,MAAM,QAAQ;AAC1B,YAAM,IAAI;AACV,UAAI;AACF,gBAAQ,CAAC,IAAI,EAAE,QAAQ,aAAa,OAAO,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE;AAAA,MAChE,SAAS,OAAO;AACd,gBAAQ,CAAC,IAAI,EAAE,QAAQ,YAAY,QAAQ,MAAM;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,MAAM,MAAM,EAAE,GAAG,MAAM,CAAC;AAC/E,SAAO;AACT;;;AC1BA,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAI3B,IAAI,KAA0B;AAE9B,SAAS,QAAsB;AAC7B,MAAI,CAAC,IAAI;AACP,SAAK,IAAI,aAAa,iBAAiB,CAAC;AACxC,OAAG,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,KAKP;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,aAAa,aAAqB,OAAqB;AAC9D,SAAO,WAAW,QAAQ,EACvB,OAAO,GAAG,WAAW,IAAI,MAAM,QAAQ,CAAC,EAAE,EAC1C,OAAO,KAAK;AACjB;AAEO,SAAS,eAAe,aAAqB,OAAiC;AACnF,MAAI;AACF,UAAM,MAAM,aAAa,aAAa,KAAK;AAC3C,UAAM,OAAO,MAAM,EAAE,QAAQ,0DAA0D;AACvF,UAAM,MAAM,KAAK,IAAI,GAAG;AACxB,QAAI,CAAC,IAAK,QAAO;AACjB,WAAO,KAAK,MAAM,IAAI,YAAY;AAAA,EACpC,QAAQ;AACN,YAAQ,OAAO,MAAM,+CAA+C;AACpE,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,aAAqB,OAAa,OAA0B;AACzF,MAAI;AACF,UAAM,MAAM,aAAa,aAAa,KAAK;AAC3C,UAAM,OAAO,MAAM,EAAE;AAAA,MACnB;AAAA,IACF;AACA,SAAK,IAAI,KAAK,KAAK,UAAU,KAAK,CAAC;AAAA,EACrC,QAAQ;AACN,YAAQ,OAAO,MAAM,gDAAgD;AAAA,EACvE;AACF;;;AChCA,IAAM,cAAc;AASpB,eAAsB,SAAS,SAAsC;AACnE,QAAM,WAAW,QAAQ,SAAS;AAClC,QAAM,YAAY,cAAc,QAAQ;AAExC,QAAM,eAAe,MAAM,aAAa;AAAA,IACtC,SAAS,QAAQ;AAAA,IACjB,SAAS,QAAQ;AAAA,IACjB,OAAO;AAAA,EACT,CAAC;AAED,MAAI,aAAa,WAAW,GAAG;AAC7B,YAAQ,IAAI,iCAAiC,QAAQ,GAAG;AACxD;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,kBAAkB,cAAc,aAAa,OAAO,OAAoB;AAC5F,UAAM,SAAS,eAAe,GAAG,MAAM,GAAG,KAAK;AAC/C,QAAI,OAAQ,QAAO;AACnB,UAAM,UAAU,UAAU,GAAG,IAAI;AACjC,UAAM,UAAU,MAAM,aAAa,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa;AAC1F,UAAM,QAAQ,aAAa,OAAO;AAClC,mBAAe,GAAG,MAAM,GAAG,OAAO,KAAK;AACvC,WAAO;AAAA,EACT,CAAC;AAED,QAAM,SAAwB,QAC3B,OAAO,CAAC,MAAgD,EAAE,WAAW,WAAW,EAChF,IAAI,CAAC,MAAM,EAAE,KAAK;AAErB,MAAI,OAAO,WAAW,GAAG;AACvB,YAAQ,IAAI,qCAAqC;AACjD;AAAA,EACF;AAGA,QAAM,cAAc,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC;AAC7D,QAAM,YAAY,iBAAiB,QAAQ,WAAW;AACtD,QAAM,cAAc,kBAAkB,SAAS;AAE/C,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,gBAAgB,WAAW,CAAC;AAAA,EAC1C,OAAO;AACL,YAAQ,IAAI,kBAAkB,aAAa,OAAO,QAAQ,QAAQ,CAAC;AAAA,EACrE;AACF;;;ACnDA,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAKnB,SAAS,kBAAkB,SAAoC;AACpE,QAAM,SAAS,oBAAoB,OAAO;AAE1C,QAAM,YAAY,OAAO,UAAU,QAAQ,OAAO,QAAQ;AAE1D,MAAI,qBAAoC;AACxC,MAAI,aAAa,OAAO,UAAU,MAAM;AAItC,yBAAqB,KAAK,MAAM,KAAK,IAAI,kBAAkB;AAAA,EAC7D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,aAAa,QAAQ;AAAA,IACrB,WAAW,QAAQ;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB;AAAA,IACA;AAAA,EACF;AACF;;;AC1BA,eAAsB,cAAc,SAA2C;AAC7E,QAAM,WAAW,QAAQ,SAAS;AAClC,QAAM,YAAY,cAAc,QAAQ;AAExC,QAAM,eAAe,MAAM,aAAa;AAAA,IACtC,SAAS,QAAQ;AAAA,IACjB,OAAO;AAAA,EACT,CAAC;AAED,MAAI,aAAa,WAAW,GAAG;AAC7B,YAAQ,IAAI,iCAAiC,QAAQ,GAAG;AACxD;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,aAAa,IAAI,OAAO,OAAO;AAC7B,YAAM,UAAU,UAAU,GAAG,IAAI;AACjC,YAAM,UAAU,MAAM,aAAa,SAAS,GAAG,WAAW,GAAG,aAAa,GAAG,aAAa;AAC1F,aAAO,kBAAkB,OAAO;AAAA,IAClC,CAAC;AAAA,EACH;AACA,QAAM,UAA8B,QACjC,OAAO,CAAC,MAAqD,EAAE,WAAW,WAAW,EACrF,IAAI,CAAC,MAAM,EAAE,KAAK;AAErB,MAAI,QAAQ,WAAW,GAAG;AACxB,YAAQ,IAAI,qCAAqC;AACjD;AAAA,EACF;AAEA,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,qBAAqB,OAAO,CAAC;AAAA,EAC3C,OAAO;AACL,YAAQ,IAAI,uBAAuB,OAAO,CAAC;AAAA,EAC7C;AACF;;;AC5CA,OAAOC,YAAW;AAClB,OAAOC,YAAW;AAGlB,IAAMC,eAAc;AAiBpB,eAAsB,WAAW,SAAwC;AACvE,QAAM,eAAe,QAAQ,SAAS,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACpE,QAAM,YAA8B,CAAC;AAErC,QAAM,mBAAmB,MAAM,QAAQ;AAAA,IACrC,aAAa,IAAI,OAAO,kBAAkB;AACxC,YAAM,eAAe,MAAM,aAAa;AAAA,QACtC,SAAS,QAAQ;AAAA,QACjB,SAAS;AAAA,MACX,CAAC;AAED,UAAI,aAAa,WAAW,EAAG,QAAO;AAEtC,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACAA;AAAA,QACA,OAAO,OAAoB;AACzB,gBAAM,SAAS,eAAe,GAAG,MAAM,GAAG,KAAK;AAC/C,cAAI,OAAQ,QAAO;AACnB,gBAAM,UAAU,UAAU,GAAG,IAAI;AACjC,gBAAM,UAAU,MAAM;AAAA,YACpB;AAAA,YACA,GAAG;AAAA,YACH,GAAG;AAAA,YACH,GAAG;AAAA,UACL;AACA,gBAAM,QAAQ,aAAa,OAAO;AAClC,yBAAe,GAAG,MAAM,GAAG,OAAO,KAAK;AACvC,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,YAAM,SAAwB,QAC3B,OAAO,CAAC,MAAgD,EAAE,WAAW,WAAW,EAChF,IAAI,CAAC,MAAM,EAAE,KAAK;AAErB,UAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,YAAM,WAAW,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC,IAAI,OAAO;AAClE,YAAM,aAAa,oBAAI,IAAoB;AAC3C,iBAAW,UAAU,OAAO,CAAC,EAAE,SAAS;AACtC,cAAM,SAAS,OACZ,IAAI,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,IAAI,GAAG,KAAK,EAC/D,OAAO,CAAC,MAAmB,MAAM,IAAI;AACxC,YAAI,OAAO,SAAS,GAAG;AACrB,qBAAW,IAAI,OAAO,MAAM,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,OAAO,MAAM;AAAA,QAC/E;AAAA,MACF;AAEA,aAAO;AAAA,QACL,MAAM;AAAA,QACN,cAAc,OAAO;AAAA,QACrB,UAAU,KAAK,MAAM,QAAQ;AAAA,QAC7B,WAAW,qBAAqB,QAAQ;AAAA,QACxC,SAAS;AAAA,MACX;AAAA,IACF,CAAC;AAAA,EACH;AAEA,aAAW,KAAK,kBAAkB;AAChC,QAAI,MAAM,KAAM,WAAU,KAAK,CAAC;AAAA,EAClC;AAEA,MAAI,UAAU,WAAW,GAAG;AAC1B,YAAQ,IAAI,6BAA6B;AACzC;AAAA,EACF;AAEA,MAAI,QAAQ,MAAM;AAChB,UAAM,aAAa,UAAU,IAAI,CAAC,OAAO;AAAA,MACvC,SAAS,EAAE;AAAA,MACX,UAAU,EAAE;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,OAAO,EAAE;AAAA,MACT,SAAS,OAAO,YAAY,EAAE,OAAO;AAAA,IACvC,EAAE;AACF,YAAQ,IAAI,KAAK,UAAU,EAAE,SAAS,WAAW,GAAG,MAAM,CAAC,CAAC;AAC5D;AAAA,EACF;AAEA,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAKF,OAAM,KAAK,sBAAsB,CAAC;AAC7C,QAAM,KAAK,EAAE;AAEb,QAAM,OAAO,CAAC,WAAW,YAAY,SAAS,GAAG,UAAU,CAAC,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC,EAAE;AAAA,IACnF,CAAC,MAAMA,OAAM,IAAI,CAAC;AAAA,EACpB;AAEA,QAAM,QAAQ,IAAIC,OAAM;AAAA,IACtB;AAAA,IACA,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,gBAAgB,GAAG,iBAAiB,EAAE;AAAA,IACrE,OAAO;AAAA,MACL,KAAK;AAAA,MAAK,WAAW;AAAA,MAAK,YAAY;AAAA,MAAM,aAAa;AAAA,MACzD,QAAQ;AAAA,MAAK,cAAc;AAAA,MAAK,eAAe;AAAA,MAAM,gBAAgB;AAAA,MACrE,MAAM;AAAA,MAAM,YAAY;AAAA,MAAM,KAAK;AAAA,MAAK,WAAW;AAAA,MACnD,OAAO;AAAA,MAAI,aAAa;AAAA,MAAI,QAAQ;AAAA,IACtC;AAAA,EACF,CAAC;AAED,aAAW,WAAW,WAAW;AAC/B,UAAM,MAAgB;AAAA,MACpB,QAAQ;AAAA,MACR,QAAQ,aAAa,SAAS;AAAA,MAC9B,QAAQ;AAAA,IACV;AACA,eAAW,CAAC,EAAE,KAAK,KAAK,QAAQ,SAAS;AACvC,UAAI,KAAK,MAAM,QAAQ,CAAC,CAAC;AAAA,IAC3B;AACA,UAAM,KAAK,GAAG;AAAA,EAChB;AAEA,QAAM,KAAK,MAAM,SAAS,CAAC;AAC3B,QAAM,KAAK,EAAE;AACb,UAAQ,IAAI,MAAM,KAAK,IAAI,CAAC;AAC9B;;;A9BnIA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,UAAU,EACf,YAAY,kGAA6F,EACzG,QAAQ,OAAO;AAElB,QACG,QAAQ,SAAS,EAAE,WAAW,KAAK,CAAC,EACpC,YAAY,2CAA2C,EACvD,OAAO,UAAU,gBAAgB,EACjC,OAAO,aAAa,4BAA4B,EAChD,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,oBAAoB,8BAA8B,EACzD,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,SAAS,OAAO;AAAA,EACxB,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,QACG,QAAQ,OAAO,EACf,YAAY,yDAAyD,EACrE,OAAO,sBAAsB,4BAA4B,IAAI,EAC7D,OAAO,UAAU,gBAAgB,EACjC,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,oBAAoB,8BAA8B,EACzD,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,SAAS,OAAO;AAAA,EACxB,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,QACG,QAAQ,aAAa,EACrB,YAAY,mDAAmD,EAC/D,OAAO,sBAAsB,4BAA4B,IAAI,EAC7D,OAAO,UAAU,gBAAgB,EACjC,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,cAAc,OAAO;AAAA,EAC7B,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,yCAAyC,EACrD,eAAe,sBAAsB,+BAA+B,EACpE,OAAO,UAAU,gBAAgB,EACjC,OAAO,qBAAqB,8BAA8B,EAC1D,OAAO,sBAAsB,0BAA0B,EACvD,OAAO,OAAO,YAAY;AACzB,MAAI;AACF,UAAM,WAAW,OAAO;AAAA,EAC1B,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,IAAM,QAAQ,QACX,QAAQ,OAAO,EACf,YAAY,iCAAiC;AAEhD,MACG,QAAQ,OAAO,EACf,YAAY,2DAA2D,EACvE,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,YAAY,iBAAiB;AACnC,QAAI;AACF,YAAM,OAAO,SAAS;AACtB,cAAQ,IAAI,kBAAkB,SAAS,EAAE;AAAA,IAC3C,SAAS,KAAK;AACZ,UAAK,IAA8B,SAAS,UAAU;AACpD,gBAAQ,IAAI,sBAAsB;AAAA,MACpC,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,gBAAY,KAAK;AAAA,EACnB;AACF,CAAC;AAEH,SAAS,YAAY,OAAsB;AACzC,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,UAAQ,MAAM;AAAA,SAAY,OAAO;AAAA,CAAI;AACrC,UAAQ,KAAK,CAAC;AAChB;AAEA,QAAQ,MAAM;","names":["chalk","join","join","basename","basename","average","round","round","round","round","round","EDIT_TOOLS","chalk","chalk","Table","CONCURRENCY"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inspecto",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "inspecto — Claude Code session quality analyzer. Grade sessions, detect regressions, catch cache bugs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",