inspecto 1.0.8 → 1.0.10
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 +14 -3
- package/dist/index.js +164 -47
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/inspecto)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org)
|
|
6
6
|
|
|
7
7
|
**Claude Code session quality analyzer — grade sessions, detect regressions, catch cache bugs.**
|
|
8
8
|
|
|
@@ -40,7 +40,7 @@ Or run without installing:
|
|
|
40
40
|
npx inspecto
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Requires Node.js >=
|
|
43
|
+
Requires Node.js >= 22 (uses the built-in `node:sqlite` module). Works on macOS, Linux, and Windows.
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -92,6 +92,14 @@ On March 31, 2026, the leaked Claude Code source revealed two cache bugs that si
|
|
|
92
92
|
npx inspecto compare --projects my-app,api-gateway,shared-lib
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
+
### Manage the grade cache
|
|
96
|
+
|
|
97
|
+
`inspecto trend` and `inspecto compare` cache computed grade results in `~/.claude/inspecto-cache.db` so re-runs over the same sessions are near-instant. The cache is keyed by file path + mtime, so it invalidates automatically when Claude Code writes new data to a session.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx inspecto cache clear # delete the cache file (~/.claude/inspecto-cache.db)
|
|
101
|
+
```
|
|
102
|
+
|
|
95
103
|
### Global options
|
|
96
104
|
|
|
97
105
|
| Flag | Description |
|
|
@@ -180,7 +188,8 @@ src/
|
|
|
180
188
|
├── anomaly/ # Baseline computation + regression detection + cache anomaly
|
|
181
189
|
├── reporter/ # Terminal (chalk + cli-table3) and JSON output modes
|
|
182
190
|
├── commands/ # audit, trend, cache-check, compare
|
|
183
|
-
|
|
191
|
+
├── cache/ # SQLite grade-result cache (node:sqlite, ~/.claude/inspecto-cache.db)
|
|
192
|
+
└── utils/ # Levenshtein, paths, duration parsing, formatting, concurrency helper
|
|
184
193
|
```
|
|
185
194
|
|
|
186
195
|
Key technical details:
|
|
@@ -188,6 +197,8 @@ Key technical details:
|
|
|
188
197
|
- **Chunk deduplication**: Assistant responses come as multiple JSONL records sharing `message.id`; content blocks are merged and only the final chunk's `output_tokens` is used
|
|
189
198
|
- **No external APIs**: All analysis is local. No network calls. Works offline
|
|
190
199
|
- **Real token cost**: `input_tokens` is always a streaming placeholder — actual input = `cache_read_input_tokens + cache_creation_input_tokens`
|
|
200
|
+
- **Concurrency**: `trend` and `compare` parse up to 16 session files in parallel (semaphore-limited) so large histories don't block
|
|
201
|
+
- **Grade cache**: computed `GradeResult` objects are persisted in `~/.claude/inspecto-cache.db` (SQLite via `node:sqlite`). Cache key = `sha256(path:mtime)`. Re-runs over unchanged sessions skip parsing entirely — typically 2–3× faster
|
|
191
202
|
|
|
192
203
|
---
|
|
193
204
|
|
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 (
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
994
|
-
|
|
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
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
|
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
|
|
1174
|
-
sessionFiles
|
|
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(
|
|
1177
|
-
|
|
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:
|
|
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(
|
|
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) =>
|
|
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(
|
|
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":"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","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.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"author": "Rahul Bhardwaj",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"engines": {
|
|
32
|
-
"node": ">=
|
|
32
|
+
"node": ">=22"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
35
|
"dist"
|