@lcvbeek/patina 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +195 -0
  2. package/dist/commands/apply.d.ts +2 -0
  3. package/dist/commands/apply.d.ts.map +1 -0
  4. package/dist/commands/apply.js +186 -0
  5. package/dist/commands/apply.js.map +1 -0
  6. package/dist/commands/capture.d.ts +5 -0
  7. package/dist/commands/capture.d.ts.map +1 -0
  8. package/dist/commands/capture.js +88 -0
  9. package/dist/commands/capture.js.map +1 -0
  10. package/dist/commands/diff.d.ts +2 -0
  11. package/dist/commands/diff.d.ts.map +1 -0
  12. package/dist/commands/diff.js +43 -0
  13. package/dist/commands/diff.js.map +1 -0
  14. package/dist/commands/ingest.d.ts +14 -0
  15. package/dist/commands/ingest.d.ts.map +1 -0
  16. package/dist/commands/ingest.js +111 -0
  17. package/dist/commands/ingest.js.map +1 -0
  18. package/dist/commands/init.d.ts +2 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +165 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/layers.d.ts +2 -0
  23. package/dist/commands/layers.d.ts.map +1 -0
  24. package/dist/commands/layers.js +141 -0
  25. package/dist/commands/layers.js.map +1 -0
  26. package/dist/commands/onboard.d.ts +2 -0
  27. package/dist/commands/onboard.d.ts.map +1 -0
  28. package/dist/commands/onboard.js +275 -0
  29. package/dist/commands/onboard.js.map +1 -0
  30. package/dist/commands/run.d.ts +4 -0
  31. package/dist/commands/run.d.ts.map +1 -0
  32. package/dist/commands/run.js +526 -0
  33. package/dist/commands/run.js.map +1 -0
  34. package/dist/commands/status.d.ts +2 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +121 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +108 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/claude.d.ts +12 -0
  43. package/dist/lib/claude.d.ts.map +1 -0
  44. package/dist/lib/claude.js +70 -0
  45. package/dist/lib/claude.js.map +1 -0
  46. package/dist/lib/metrics.d.ts +29 -0
  47. package/dist/lib/metrics.d.ts.map +1 -0
  48. package/dist/lib/metrics.js +96 -0
  49. package/dist/lib/metrics.js.map +1 -0
  50. package/dist/lib/parser.d.ts +28 -0
  51. package/dist/lib/parser.d.ts.map +1 -0
  52. package/dist/lib/parser.js +226 -0
  53. package/dist/lib/parser.js.map +1 -0
  54. package/dist/lib/storage.d.ts +126 -0
  55. package/dist/lib/storage.d.ts.map +1 -0
  56. package/dist/lib/storage.js +201 -0
  57. package/dist/lib/storage.js.map +1 -0
  58. package/package.json +45 -0
@@ -0,0 +1,121 @@
1
+ import { assertInitialised, readAllSessions, getLatestCycleDate } from '../lib/storage.js';
2
+ import { computeAggregates, computeTrend, formatNumber, formatDate, trendArrow, } from '../lib/metrics.js';
3
+ // ---------------------------------------------------------------------------
4
+ // ANSI colour helpers (no deps)
5
+ // ---------------------------------------------------------------------------
6
+ const isTTY = process.stdout.isTTY;
7
+ function bold(s) {
8
+ return isTTY ? `\x1b[1m${s}\x1b[0m` : s;
9
+ }
10
+ function dim(s) {
11
+ return isTTY ? `\x1b[2m${s}\x1b[0m` : s;
12
+ }
13
+ function green(s) {
14
+ return isTTY ? `\x1b[32m${s}\x1b[0m` : s;
15
+ }
16
+ function yellow(s) {
17
+ return isTTY ? `\x1b[33m${s}\x1b[0m` : s;
18
+ }
19
+ function red(s) {
20
+ return isTTY ? `\x1b[31m${s}\x1b[0m` : s;
21
+ }
22
+ function cyan(s) {
23
+ return isTTY ? `\x1b[36m${s}\x1b[0m` : s;
24
+ }
25
+ function section(title) {
26
+ console.log(`\n${bold(title)}`);
27
+ console.log(dim('─'.repeat(title.length)));
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Command
31
+ // ---------------------------------------------------------------------------
32
+ export async function statusCommand() {
33
+ assertInitialised();
34
+ const sessions = readAllSessions();
35
+ if (sessions.length === 0) {
36
+ console.log('No sessions found. Run `patina ingest` to import Claude Code logs.');
37
+ return;
38
+ }
39
+ const agg = computeAggregates(sessions);
40
+ const trend = computeTrend(sessions);
41
+ // Header
42
+ console.log(bold('\npatina — status report'));
43
+ if (agg.date_range) {
44
+ console.log(dim(`${formatDate(agg.date_range.earliest)} → ${formatDate(agg.date_range.latest)}`));
45
+ }
46
+ // ── Overview ──────────────────────────────────────────────────────────────
47
+ section('Overview');
48
+ console.log(` Total sessions ${bold(formatNumber(agg.total_sessions))}`);
49
+ console.log(` Total tokens (est.) ${bold(formatNumber(agg.total_tokens))}`);
50
+ console.log(` Avg tokens/session ${bold(formatNumber(agg.avg_tokens_per_session))}`);
51
+ console.log(` Sessions with rework ${bold(formatNumber(agg.rework_sessions))} ${dim(`(${agg.rework_rate_pct}%)`)}`);
52
+ // ── Trend ─────────────────────────────────────────────────────────────────
53
+ if (trend) {
54
+ section('Trend (first half → second half)');
55
+ const tokenArrow = trendArrow(trend.token_delta_pct);
56
+ const tokenColour = trend.token_delta_pct === null
57
+ ? dim
58
+ : trend.token_delta_pct > 10
59
+ ? yellow
60
+ : green;
61
+ console.log(` Avg tokens/session ${tokenColour(tokenArrow)}`);
62
+ const reworkArrow = trendArrow(trend.rework_delta_pct);
63
+ const reworkColour = trend.rework_delta_pct === null
64
+ ? dim
65
+ : trend.rework_delta_pct > 0
66
+ ? red
67
+ : green;
68
+ console.log(` Rework rate ${reworkColour(reworkArrow)}`);
69
+ console.log(dim(`\n Previous period: ${formatNumber(trend.previous.total_sessions)} sessions, ${formatNumber(trend.previous.avg_tokens_per_session)} avg tokens, ${trend.previous.rework_rate_pct}% rework`));
70
+ console.log(dim(` Current period: ${formatNumber(trend.current.total_sessions)} sessions, ${formatNumber(trend.current.avg_tokens_per_session)} avg tokens, ${trend.current.rework_rate_pct}% rework`));
71
+ }
72
+ else if (sessions.length < 4) {
73
+ section('Trend');
74
+ console.log(dim(' Not enough data for trend analysis (need ≥ 4 sessions).'));
75
+ }
76
+ // ── Tool usage ────────────────────────────────────────────────────────────
77
+ if (agg.tool_usage.length > 0) {
78
+ section('Top tool usage');
79
+ const topN = agg.tool_usage.slice(0, 10);
80
+ const maxCount = topN[0].count;
81
+ for (const { tool, count } of topN) {
82
+ const barLen = Math.round((count / maxCount) * 20);
83
+ const bar = cyan('█'.repeat(barLen));
84
+ const pct = Math.round((count / agg.total_sessions) * 100);
85
+ console.log(` ${tool.padEnd(28)} ${bar} ${dim(formatNumber(count))} calls ${dim(`(${pct}% of sessions)`)}`);
86
+ }
87
+ }
88
+ // ── By project ────────────────────────────────────────────────────────────
89
+ const projects = Object.entries(agg.sessions_by_project).sort(([, a], [, b]) => b - a);
90
+ if (projects.length > 1) {
91
+ section('Sessions by project');
92
+ for (const [project, count] of projects) {
93
+ const pct = Math.round((count / agg.total_sessions) * 100);
94
+ console.log(` ${project.padEnd(40)} ${bold(formatNumber(count))} ${dim(`${pct}%`)}`);
95
+ }
96
+ }
97
+ // ── Rework sessions ───────────────────────────────────────────────────────
98
+ if (agg.rework_sessions > 0) {
99
+ section('Sessions with rework detected');
100
+ const reworkList = sessions
101
+ .filter((s) => s.had_rework)
102
+ .sort((a, b) => b.estimated_tokens - a.estimated_tokens)
103
+ .slice(0, 5);
104
+ for (const s of reworkList) {
105
+ console.log(` ${dim(formatDate(s.timestamp))} ${s.project.slice(0, 32).padEnd(32)} ~${formatNumber(s.estimated_tokens)} tokens ${dim(s.session_id.slice(0, 12))}`);
106
+ }
107
+ if (agg.rework_sessions > 5) {
108
+ console.log(dim(` … and ${agg.rework_sessions - 5} more`));
109
+ }
110
+ }
111
+ // Footer
112
+ const lastCycle = getLatestCycleDate();
113
+ if (lastCycle === null) {
114
+ console.log(`\n${dim('No cycles yet. Run')} ${bold('patina run')} ${dim('to set up your AI operating agreements (takes ~10 min).')}`);
115
+ }
116
+ else {
117
+ console.log(`\n${dim('Last cycle:')} ${dim(lastCycle)}${dim('. Run')} ${bold('patina run')} ${dim('to start the next cycle.')}`);
118
+ }
119
+ console.log();
120
+ }
121
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initCommand } from './commands/init.js';
4
+ import { ingestCommand } from './commands/ingest.js';
5
+ import { statusCommand } from './commands/status.js';
6
+ import { runCommand } from './commands/run.js';
7
+ import { diffCommand } from './commands/diff.js';
8
+ import { applyCommand } from './commands/apply.js';
9
+ import { captureCommand } from './commands/capture.js';
10
+ import { layersCommand } from './commands/layers.js';
11
+ const program = new Command();
12
+ program
13
+ .name('patina')
14
+ .description('AI-assisted retrospective tool for Claude Code teams')
15
+ .version('0.1.0');
16
+ // ---------------------------------------------------------------------------
17
+ // patina init
18
+ // ---------------------------------------------------------------------------
19
+ program
20
+ .command('init')
21
+ .description('Scaffold .patina/ in the current directory and create patina.md')
22
+ .action(async () => {
23
+ await initCommand();
24
+ });
25
+ // ---------------------------------------------------------------------------
26
+ // patina ingest
27
+ // ---------------------------------------------------------------------------
28
+ program
29
+ .command('ingest')
30
+ .description('Parse ~/.claude/projects/ JSONL logs and store session summaries')
31
+ .option('--claude-dir <path>', 'Override the Claude projects directory (default: ~/.claude/projects/)')
32
+ .option('-v, --verbose', 'Print each session as it is ingested')
33
+ .action(async (options) => {
34
+ await ingestCommand({
35
+ claudeDir: options.claudeDir,
36
+ verbose: options.verbose,
37
+ });
38
+ });
39
+ // ---------------------------------------------------------------------------
40
+ // patina status
41
+ // ---------------------------------------------------------------------------
42
+ program
43
+ .command('status')
44
+ .description('Show metrics since the last cycle (or baseline if first run)')
45
+ .action(async () => {
46
+ await statusCommand();
47
+ });
48
+ // ---------------------------------------------------------------------------
49
+ // patina run
50
+ // ---------------------------------------------------------------------------
51
+ program
52
+ .command('run')
53
+ .description('Start an async retrospective session with AI synthesis')
54
+ .option('--onboard', 'Run the framework-driven onboarding flow, even if prior cycles exist')
55
+ .action(async (options) => {
56
+ await runCommand(options);
57
+ });
58
+ // ---------------------------------------------------------------------------
59
+ // patina diff
60
+ // ---------------------------------------------------------------------------
61
+ program
62
+ .command('diff')
63
+ .description('Show proposed changes to patina.md / CLAUDE.md')
64
+ .action(async () => {
65
+ await diffCommand();
66
+ });
67
+ // ---------------------------------------------------------------------------
68
+ // patina capture
69
+ // ---------------------------------------------------------------------------
70
+ program
71
+ .command('capture [text]')
72
+ .description('Capture a notable moment while it\'s fresh — feeds into your next retro')
73
+ .option('-t, --tag <tag>', 'near-miss | went-well | frustration | pattern | other')
74
+ .action(async (text, options) => {
75
+ await captureCommand(text, options);
76
+ });
77
+ // ---------------------------------------------------------------------------
78
+ // patina buff (primary) / patina apply (alias)
79
+ // ---------------------------------------------------------------------------
80
+ program
81
+ .command('buff')
82
+ .description('Apply approved changes from patina diff to patina.md')
83
+ .action(async () => {
84
+ await applyCommand();
85
+ });
86
+ program
87
+ .command('apply')
88
+ .description('Alias for patina buff')
89
+ .action(async () => {
90
+ await applyCommand();
91
+ });
92
+ // ---------------------------------------------------------------------------
93
+ // patina layers
94
+ // ---------------------------------------------------------------------------
95
+ program
96
+ .command('layers')
97
+ .description('Visualise the patina you\'ve built — one layer per retro cycle')
98
+ .action(() => {
99
+ layersCommand();
100
+ });
101
+ // ---------------------------------------------------------------------------
102
+ // Parse and run
103
+ // ---------------------------------------------------------------------------
104
+ program.parseAsync(process.argv).catch((err) => {
105
+ console.error('Unexpected error:', err instanceof Error ? err.message : String(err));
106
+ process.exit(1);
107
+ });
108
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared Claude invocation layer.
3
+ *
4
+ * Priority order:
5
+ * 1. Claude Code CLI (`claude` on PATH) — uses existing auth, respects Max plan
6
+ * 2. ANTHROPIC_API_KEY env var — direct SDK call, billed separately
7
+ *
8
+ * Both paths accept a prompt string and return the raw text response.
9
+ * JSON parsing is left to the caller.
10
+ */
11
+ export declare function callClaudeForJson<T>(prompt: string): Promise<T>;
12
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/lib/claude.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwDH,wBAAsB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAsBrE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Shared Claude invocation layer.
3
+ *
4
+ * Priority order:
5
+ * 1. Claude Code CLI (`claude` on PATH) — uses existing auth, respects Max plan
6
+ * 2. ANTHROPIC_API_KEY env var — direct SDK call, billed separately
7
+ *
8
+ * Both paths accept a prompt string and return the raw text response.
9
+ * JSON parsing is left to the caller.
10
+ */
11
+ import { spawnSync } from 'child_process';
12
+ import Anthropic from '@anthropic-ai/sdk';
13
+ const MODEL = 'claude-sonnet-4-5';
14
+ function claudeCliAvailable() {
15
+ const result = spawnSync('claude', ['--version'], { encoding: 'utf8' });
16
+ return !result.error;
17
+ }
18
+ function callViaCli(prompt) {
19
+ const result = spawnSync('claude', ['-p', '--output-format', 'text', '--model', 'sonnet'], {
20
+ input: prompt,
21
+ encoding: 'utf8',
22
+ maxBuffer: 10 * 1024 * 1024,
23
+ timeout: 120000,
24
+ });
25
+ if (result.error) {
26
+ throw result.error;
27
+ }
28
+ if (result.status !== 0) {
29
+ throw new Error(result.stderr || 'Claude CLI exited with non-zero status');
30
+ }
31
+ return result.stdout.trim();
32
+ }
33
+ async function callViaApi(prompt) {
34
+ const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
35
+ const message = await client.messages.create({
36
+ model: MODEL,
37
+ max_tokens: 4096,
38
+ messages: [{ role: 'user', content: prompt }],
39
+ });
40
+ const block = message.content[0];
41
+ if (block.type !== 'text') {
42
+ throw new Error('Unexpected response type from Anthropic API');
43
+ }
44
+ return block.text.trim();
45
+ }
46
+ function stripFences(raw) {
47
+ return raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim();
48
+ }
49
+ export async function callClaudeForJson(prompt) {
50
+ let raw;
51
+ if (claudeCliAvailable()) {
52
+ raw = callViaCli(prompt);
53
+ }
54
+ else if (process.env.ANTHROPIC_API_KEY) {
55
+ raw = await callViaApi(prompt);
56
+ }
57
+ else {
58
+ throw new Error('No Claude access found.\n' +
59
+ ' Option 1: Install Claude Code — https://claude.ai/code\n' +
60
+ ' Option 2: Set the ANTHROPIC_API_KEY environment variable');
61
+ }
62
+ const jsonStr = stripFences(raw);
63
+ try {
64
+ return JSON.parse(jsonStr);
65
+ }
66
+ catch {
67
+ throw new Error(`Could not parse Claude response as JSON.\n\nRaw response:\n${raw.slice(0, 500)}`);
68
+ }
69
+ }
70
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/lib/claude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { SessionSummary } from './storage.js';
2
+ export interface AggregateMetrics {
3
+ total_sessions: number;
4
+ total_tokens: number;
5
+ avg_tokens_per_session: number;
6
+ rework_sessions: number;
7
+ rework_rate_pct: number;
8
+ tool_usage: Array<{
9
+ tool: string;
10
+ count: number;
11
+ }>;
12
+ sessions_by_project: Record<string, number>;
13
+ date_range: {
14
+ earliest: string;
15
+ latest: string;
16
+ } | null;
17
+ }
18
+ export declare function computeAggregates(sessions: SessionSummary[]): AggregateMetrics;
19
+ export interface TrendComparison {
20
+ previous: AggregateMetrics;
21
+ current: AggregateMetrics;
22
+ token_delta_pct: number | null;
23
+ rework_delta_pct: number | null;
24
+ }
25
+ export declare function computeTrend(sessions: SessionSummary[]): TrendComparison | null;
26
+ export declare function formatNumber(n: number): string;
27
+ export declare function formatDate(iso: string): string;
28
+ export declare function trendArrow(delta: number | null): string;
29
+ //# sourceMappingURL=metrics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/lib/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CACzD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,gBAAgB,CAwD9E;AAMD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,eAAe,GAAG,IAAI,CA0B/E;AAMD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU9C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAKvD"}
@@ -0,0 +1,96 @@
1
+ export function computeAggregates(sessions) {
2
+ if (sessions.length === 0) {
3
+ return {
4
+ total_sessions: 0,
5
+ total_tokens: 0,
6
+ avg_tokens_per_session: 0,
7
+ rework_sessions: 0,
8
+ rework_rate_pct: 0,
9
+ tool_usage: [],
10
+ sessions_by_project: {},
11
+ date_range: null,
12
+ };
13
+ }
14
+ const total_tokens = sessions.reduce((sum, s) => sum + s.estimated_tokens, 0);
15
+ const rework_sessions = sessions.filter((s) => s.had_rework).length;
16
+ // Tool usage aggregation
17
+ const toolCounts = {};
18
+ for (const session of sessions) {
19
+ for (const [tool, count] of Object.entries(session.tool_calls)) {
20
+ toolCounts[tool] = (toolCounts[tool] ?? 0) + count;
21
+ }
22
+ }
23
+ const tool_usage = Object.entries(toolCounts)
24
+ .sort(([, a], [, b]) => b - a)
25
+ .map(([tool, count]) => ({ tool, count }));
26
+ // Project breakdown
27
+ const sessions_by_project = {};
28
+ for (const session of sessions) {
29
+ sessions_by_project[session.project] =
30
+ (sessions_by_project[session.project] ?? 0) + 1;
31
+ }
32
+ // Date range
33
+ const timestamps = sessions
34
+ .map((s) => s.timestamp)
35
+ .filter(Boolean)
36
+ .sort();
37
+ const date_range = timestamps.length > 0
38
+ ? { earliest: timestamps[0], latest: timestamps[timestamps.length - 1] }
39
+ : null;
40
+ return {
41
+ total_sessions: sessions.length,
42
+ total_tokens,
43
+ avg_tokens_per_session: Math.round(total_tokens / sessions.length),
44
+ rework_sessions,
45
+ rework_rate_pct: Math.round((rework_sessions / sessions.length) * 100),
46
+ tool_usage,
47
+ sessions_by_project,
48
+ date_range,
49
+ };
50
+ }
51
+ export function computeTrend(sessions) {
52
+ if (sessions.length < 4)
53
+ return null; // not enough data for meaningful trend
54
+ // Sort by timestamp and split in half
55
+ const sorted = [...sessions].sort((a, b) => a.timestamp.localeCompare(b.timestamp));
56
+ const mid = Math.floor(sorted.length / 2);
57
+ const previous = computeAggregates(sorted.slice(0, mid));
58
+ const current = computeAggregates(sorted.slice(mid));
59
+ const token_delta_pct = previous.avg_tokens_per_session > 0
60
+ ? Math.round(((current.avg_tokens_per_session - previous.avg_tokens_per_session) /
61
+ previous.avg_tokens_per_session) *
62
+ 100)
63
+ : null;
64
+ const rework_delta_pct = previous.rework_rate_pct !== null
65
+ ? current.rework_rate_pct - previous.rework_rate_pct
66
+ : null;
67
+ return { previous, current, token_delta_pct, rework_delta_pct };
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Formatting helpers
71
+ // ---------------------------------------------------------------------------
72
+ export function formatNumber(n) {
73
+ return n.toLocaleString('en-US');
74
+ }
75
+ export function formatDate(iso) {
76
+ try {
77
+ return new Date(iso).toLocaleDateString('en-US', {
78
+ year: 'numeric',
79
+ month: 'short',
80
+ day: 'numeric',
81
+ });
82
+ }
83
+ catch {
84
+ return iso;
85
+ }
86
+ }
87
+ export function trendArrow(delta) {
88
+ if (delta === null)
89
+ return '';
90
+ if (delta > 0)
91
+ return `▲ +${delta}%`;
92
+ if (delta < 0)
93
+ return `▼ ${delta}%`;
94
+ return '→ 0%';
95
+ }
96
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../src/lib/metrics.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export interface ParsedSession {
2
+ session_id: string;
3
+ project: string;
4
+ timestamp: string;
5
+ turn_count: number;
6
+ estimated_tokens: number;
7
+ tool_calls: Record<string, number>;
8
+ had_rework: boolean;
9
+ }
10
+ /**
11
+ * Parse a single JSONL conversation file and return one ParsedSession per
12
+ * unique sessionId found. Claude Code can store multiple sessions in the same
13
+ * file, so we group by sessionId.
14
+ */
15
+ export declare function parseConversationFile(filePath: string, projectName: string): ParsedSession[];
16
+ export interface DiscoveredProject {
17
+ name: string;
18
+ conversationFile: string;
19
+ }
20
+ /**
21
+ * Scan ~/.claude/projects/ and return all projects that have a conversations
22
+ * JSONL file. Claude Code stores conversations at different paths depending on
23
+ * the version:
24
+ * ~/.claude/projects/<slug>/conversations.jsonl (older)
25
+ * ~/.claude/projects/<slug>/<uuid>.jsonl (newer, one file per session)
26
+ */
27
+ export declare function discoverProjects(claudeDir?: string): DiscoveredProject[];
28
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/lib/parser.ts"],"names":[],"mappings":"AAmJA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,aAAa,EAAE,CAgFjB;AAMD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,iBAAiB,EAAE,CA6BxE"}