@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.
- package/README.md +195 -0
- package/dist/commands/apply.d.ts +2 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +186 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/capture.d.ts +5 -0
- package/dist/commands/capture.d.ts.map +1 -0
- package/dist/commands/capture.js +88 -0
- package/dist/commands/capture.js.map +1 -0
- package/dist/commands/diff.d.ts +2 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +43 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/ingest.d.ts +14 -0
- package/dist/commands/ingest.d.ts.map +1 -0
- package/dist/commands/ingest.js +111 -0
- package/dist/commands/ingest.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/layers.d.ts +2 -0
- package/dist/commands/layers.d.ts.map +1 -0
- package/dist/commands/layers.js +141 -0
- package/dist/commands/layers.js.map +1 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +275 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +526 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +121 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/claude.d.ts +12 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +70 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/metrics.d.ts +29 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +96 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/parser.d.ts +28 -0
- package/dist/lib/parser.d.ts.map +1 -0
- package/dist/lib/parser.js +226 -0
- package/dist/lib/parser.js.map +1 -0
- package/dist/lib/storage.d.ts +126 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +201 -0
- package/dist/lib/storage.js.map +1 -0
- 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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|