inspecto 1.0.0 → 1.0.1
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 -14
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
#
|
|
1
|
+
# inspecto
|
|
2
2
|
|
|
3
3
|
**Claude Code session quality analyzer — grade sessions, detect regressions, catch cache bugs.**
|
|
4
4
|
|
|
5
5
|
> AMD's AI director manually analyzed 7,000 Claude Code sessions to prove it got worse.
|
|
6
|
-
> `
|
|
6
|
+
> `inspecto` automates that analysis for every developer.
|
|
7
7
|
|
|
8
8
|
Three tools already count tokens (`ccusage`, `claude-usage`, `Claude-Code-Usage-Monitor`). They answer *"how much did I spend?"*
|
|
9
9
|
|
|
10
|
-
`
|
|
10
|
+
`inspecto` answers a different question: **"Is Claude Code getting worse for me, and can I prove it?"**
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install -g
|
|
17
|
+
npm install -g inspecto
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Or run without installing:
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
npx
|
|
23
|
+
npx inspecto
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
Requires Node.js >= 18. Works on macOS, Linux, and Windows.
|
|
@@ -32,11 +32,11 @@ Requires Node.js >= 18. Works on macOS, Linux, and Windows.
|
|
|
32
32
|
### Grade your most recent session
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
npx
|
|
35
|
+
npx inspecto
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
-
|
|
39
|
+
inspecto v1.0.0 — Claude Code Session Quality Analyzer
|
|
40
40
|
|
|
41
41
|
Session: 31f3f224 | my-app | 47 min | claude-opus-4-6
|
|
42
42
|
|
|
@@ -56,7 +56,7 @@ npx cc-audit
|
|
|
56
56
|
### Detect regressions over time
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npx
|
|
59
|
+
npx inspecto trend --since 14d
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Compares the most recent half of your sessions against the full period and flags metrics that have regressed by more than 30%.
|
|
@@ -64,7 +64,7 @@ Compares the most recent half of your sessions against the full period and flags
|
|
|
64
64
|
### Catch the prompt cache bug
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
|
-
npx
|
|
67
|
+
npx inspecto cache-check
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
On March 31, 2026, the leaked Claude Code source revealed two cache bugs that silently inflate token costs 10-20x. This command detects sessions where the cache hit rate is suspiciously low.
|
|
@@ -72,7 +72,7 @@ On March 31, 2026, the leaked Claude Code source revealed two cache bugs that si
|
|
|
72
72
|
### Compare projects
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
npx
|
|
75
|
+
npx inspecto compare --projects my-app,api-gateway,shared-lib
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
### Global options
|
|
@@ -106,7 +106,7 @@ Each metric is a pure function computed from your local session files. No data l
|
|
|
106
106
|
|
|
107
107
|
Claude Code writes one JSONL session file per conversation to `~/.claude/projects/{project}/{sessionId}.jsonl`. Each line is a JSON record — user messages, assistant responses (streamed as multiple chunks), tool calls, and tool results.
|
|
108
108
|
|
|
109
|
-
`
|
|
109
|
+
`inspecto` streams these files line-by-line (never loading 100MB+ files into memory), merges streaming chunks by `message.id`, extracts tool-use patterns and token usage, and computes the 7 metrics above.
|
|
110
110
|
|
|
111
111
|
The composite grade is a weighted average mapped to a letter grade from **A+** to **F**.
|
|
112
112
|
|
|
@@ -121,15 +121,15 @@ In the last 30 days before this tool was built:
|
|
|
121
121
|
- **Mar 31, 2026** — Claude Code's source leaked via npm, revealing 2 cache bugs that silently inflate costs 10-20x
|
|
122
122
|
- **Mar 26, 2026** — Users on the $100/mo plan reported burning through limits in 90 minutes instead of 5 hours
|
|
123
123
|
|
|
124
|
-
The tools that track token spending tell you *what* you used. `
|
|
124
|
+
The tools that track token spending tell you *what* you used. `inspecto` tells you *whether it was worth it*.
|
|
125
125
|
|
|
126
126
|
---
|
|
127
127
|
|
|
128
128
|
## Development
|
|
129
129
|
|
|
130
130
|
```bash
|
|
131
|
-
git clone https://github.com/rahulbhardwaj94/
|
|
132
|
-
cd
|
|
131
|
+
git clone https://github.com/rahulbhardwaj94/inspecto.git
|
|
132
|
+
cd inspecto
|
|
133
133
|
npm install
|
|
134
134
|
npm test
|
|
135
135
|
npm run build
|
package/dist/index.js
CHANGED
|
@@ -695,7 +695,7 @@ var METRIC_DISPLAY_NAMES = {
|
|
|
695
695
|
function renderAuditReport(session, grade) {
|
|
696
696
|
const lines = [];
|
|
697
697
|
lines.push("");
|
|
698
|
-
lines.push(chalk.bold("
|
|
698
|
+
lines.push(chalk.bold(" inspecto v1.0.0") + chalk.dim(" \u2014 Claude Code Session Quality Analyzer"));
|
|
699
699
|
lines.push("");
|
|
700
700
|
const sessionInfo = [
|
|
701
701
|
`Session: ${chalk.cyan(shortSessionId(session.id))}`,
|
|
@@ -1194,7 +1194,7 @@ function getLetterGrade(score) {
|
|
|
1194
1194
|
|
|
1195
1195
|
// src/index.ts
|
|
1196
1196
|
var program = new Command();
|
|
1197
|
-
program.name("
|
|
1197
|
+
program.name("inspecto").description("Claude Code session quality analyzer \u2014 grade sessions, detect regressions, catch cache bugs").version("1.0.0");
|
|
1198
1198
|
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) => {
|
|
1199
1199
|
try {
|
|
1200
1200
|
await runAudit(options);
|
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/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 * cc-audit — 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(\"cc-audit\")\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 sessions: SessionFile[] = [];\n\n for (const projectDir of projectDirs) {\n // Skip hidden directories\n if (projectDir.startsWith(\".\")) continue;\n\n const fullProjectDir = join(projectsDir, projectDir);\n let entries: string[];\n try {\n entries = await readdir(fullProjectDir);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (extname(entry) !== \".jsonl\") continue;\n\n const filePath = join(fullProjectDir, entry);\n const sessionId = basename(entry, \".jsonl\");\n\n try {\n const fileStat = await stat(filePath);\n\n // Filter by date if requested\n if (options?.since && fileStat.mtime < options.since) continue;\n\n sessions.push({\n path: filePath,\n sessionId,\n projectSlug: projectDir,\n mtime: fileStat.mtime,\n });\n } catch {\n continue;\n }\n }\n }\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 type {\n AssistantRecord,\n ContentBlock,\n MergedTurn,\n RawRecord,\n Session,\n SKIP_TYPES,\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 */\nexport async function buildSession(\n records: AsyncIterable<RawRecord>,\n sessionId: string,\n projectSlug: string,\n): Promise<Session> {\n const assistantChunks = new Map<string, AssistantAccumulator>();\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 for await (const record of records) {\n // Skip non-conversation record types\n if (isSkippable(record.type)) continue;\n\n if (record.type === \"user\") {\n const userRecord = record as UserRecord;\n handleUserRecord(userRecord, turns);\n captureMetadata(userRecord);\n } else if (record.type === \"assistant\") {\n const assistantRecord = record as AssistantRecord;\n\n // Skip synthetic context-management records\n if (assistantRecord.message.model === \"<synthetic>\") continue;\n // Skip errored API responses\n if (assistantRecord.error) continue;\n\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 });\n }\n\n // Sort all turns by timestamp\n turns.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n return {\n id: sessionId,\n projectSlug,\n model,\n turns,\n startTime: firstTimestamp,\n endTime: lastTimestamp,\n cwd,\n gitBranch,\n durationMs: firstTimestamp && lastTimestamp\n ? new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime()\n : 0,\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, turns: MergedTurn[]) {\n const content = record.message.content;\n const isHumanTurn =\n typeof content === \"string\" && !record.isMeta;\n\n turns.push({\n role: \"user\",\n content: normalizeContent(content),\n usage: null,\n complete: true,\n timestamp: record.timestamp,\n isHumanTurn,\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 * Composite grading from all 7 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\";\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.1,\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\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};\n\nexport function renderAuditReport(session: Session, grade: GradeResult): string {\n const lines: string[] = [];\n\n lines.push(\"\");\n lines.push(chalk.bold(\" cc-audit v1.0.0\") + chalk.dim(\" — Claude Code Session Quality Analyzer\"));\n lines.push(\"\");\n\n const sessionInfo = [\n `Session: ${chalk.cyan(shortSessionId(session.id))}`,\n projectNameFromSlug(session.projectSlug),\n formatDuration(session.durationMs),\n session.model,\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 );\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 grades: GradeResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n grades.push(gradeSession(session));\n } catch {\n // Skip sessions that fail to parse\n }\n }\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 results: CacheCheckResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n results.push(checkCacheAnomaly(session));\n } catch {\n // Skip sessions that fail to parse\n }\n }\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 for (const projectFilter of projectNames) {\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: projectFilter,\n });\n\n if (sessionFiles.length === 0) continue;\n\n const grades: GradeResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n grades.push(gradeSession(session));\n } catch {\n continue;\n }\n }\n\n if (grades.length === 0) continue;\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 summaries.push({\n name: projectFilter,\n sessionCount: grades.length,\n avgGrade: Math.round(avgScore),\n avgLetter: getLetterGrade(avgScore),\n metrics: metricAvgs,\n });\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","turns","average","round","round","round","round","round","EDIT_TOOLS","chalk","Table"]}
|
|
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/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 sessions: SessionFile[] = [];\n\n for (const projectDir of projectDirs) {\n // Skip hidden directories\n if (projectDir.startsWith(\".\")) continue;\n\n const fullProjectDir = join(projectsDir, projectDir);\n let entries: string[];\n try {\n entries = await readdir(fullProjectDir);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n if (extname(entry) !== \".jsonl\") continue;\n\n const filePath = join(fullProjectDir, entry);\n const sessionId = basename(entry, \".jsonl\");\n\n try {\n const fileStat = await stat(filePath);\n\n // Filter by date if requested\n if (options?.since && fileStat.mtime < options.since) continue;\n\n sessions.push({\n path: filePath,\n sessionId,\n projectSlug: projectDir,\n mtime: fileStat.mtime,\n });\n } catch {\n continue;\n }\n }\n }\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 type {\n AssistantRecord,\n ContentBlock,\n MergedTurn,\n RawRecord,\n Session,\n SKIP_TYPES,\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 */\nexport async function buildSession(\n records: AsyncIterable<RawRecord>,\n sessionId: string,\n projectSlug: string,\n): Promise<Session> {\n const assistantChunks = new Map<string, AssistantAccumulator>();\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 for await (const record of records) {\n // Skip non-conversation record types\n if (isSkippable(record.type)) continue;\n\n if (record.type === \"user\") {\n const userRecord = record as UserRecord;\n handleUserRecord(userRecord, turns);\n captureMetadata(userRecord);\n } else if (record.type === \"assistant\") {\n const assistantRecord = record as AssistantRecord;\n\n // Skip synthetic context-management records\n if (assistantRecord.message.model === \"<synthetic>\") continue;\n // Skip errored API responses\n if (assistantRecord.error) continue;\n\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 });\n }\n\n // Sort all turns by timestamp\n turns.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\n return {\n id: sessionId,\n projectSlug,\n model,\n turns,\n startTime: firstTimestamp,\n endTime: lastTimestamp,\n cwd,\n gitBranch,\n durationMs: firstTimestamp && lastTimestamp\n ? new Date(lastTimestamp).getTime() - new Date(firstTimestamp).getTime()\n : 0,\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, turns: MergedTurn[]) {\n const content = record.message.content;\n const isHumanTurn =\n typeof content === \"string\" && !record.isMeta;\n\n turns.push({\n role: \"user\",\n content: normalizeContent(content),\n usage: null,\n complete: true,\n timestamp: record.timestamp,\n isHumanTurn,\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 * Composite grading from all 7 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\";\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.1,\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\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};\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 sessionInfo = [\n `Session: ${chalk.cyan(shortSessionId(session.id))}`,\n projectNameFromSlug(session.projectSlug),\n formatDuration(session.durationMs),\n session.model,\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 );\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 grades: GradeResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n grades.push(gradeSession(session));\n } catch {\n // Skip sessions that fail to parse\n }\n }\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 results: CacheCheckResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n results.push(checkCacheAnomaly(session));\n } catch {\n // Skip sessions that fail to parse\n }\n }\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 for (const projectFilter of projectNames) {\n const sessionFiles = await scanSessions({\n dataDir: options.dataDir,\n project: projectFilter,\n });\n\n if (sessionFiles.length === 0) continue;\n\n const grades: GradeResult[] = [];\n for (const sf of sessionFiles) {\n try {\n const records = readJsonl(sf.path);\n const session = await buildSession(records, sf.sessionId, sf.projectSlug);\n grades.push(gradeSession(session));\n } catch {\n continue;\n }\n }\n\n if (grades.length === 0) continue;\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 summaries.push({\n name: projectFilter,\n sessionCount: grades.length,\n avgGrade: Math.round(avgScore),\n avgLetter: getLetterGrade(avgScore),\n metrics: metricAvgs,\n });\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","turns","average","round","round","round","round","round","EDIT_TOOLS","chalk","Table"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inspecto",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Claude Code session quality analyzer
|
|
3
|
+
"version": "1.0.1",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
},
|
|
48
48
|
"repository": {
|
|
49
49
|
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/rahulbhardwaj94/
|
|
50
|
+
"url": "git+https://github.com/rahulbhardwaj94/inspecto.git"
|
|
51
51
|
},
|
|
52
52
|
"bugs": {
|
|
53
|
-
"url": "https://github.com/rahulbhardwaj94/
|
|
53
|
+
"url": "https://github.com/rahulbhardwaj94/inspecto/issues"
|
|
54
54
|
},
|
|
55
|
-
"homepage": "https://github.com/rahulbhardwaj94/
|
|
55
|
+
"homepage": "https://github.com/rahulbhardwaj94/inspecto#readme"
|
|
56
56
|
}
|