@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,226 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Claude Code JSONL message schema (permissive — we only extract what we need)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Each line in a Claude Code conversation file is one of these entry types.
|
|
8
|
+
const MessageEntrySchema = z.object({
|
|
9
|
+
type: z.string(),
|
|
10
|
+
uuid: z.string().optional(),
|
|
11
|
+
sessionId: z.string().optional(),
|
|
12
|
+
timestamp: z.string().optional(),
|
|
13
|
+
message: z
|
|
14
|
+
.object({
|
|
15
|
+
role: z.enum(['user', 'assistant']).optional(),
|
|
16
|
+
content: z
|
|
17
|
+
.union([
|
|
18
|
+
z.string(),
|
|
19
|
+
z.array(z.union([
|
|
20
|
+
z.object({
|
|
21
|
+
type: z.string(),
|
|
22
|
+
text: z.string().optional(),
|
|
23
|
+
name: z.string().optional(),
|
|
24
|
+
input: z.unknown().optional(),
|
|
25
|
+
}),
|
|
26
|
+
z.unknown(),
|
|
27
|
+
])),
|
|
28
|
+
])
|
|
29
|
+
.optional(),
|
|
30
|
+
})
|
|
31
|
+
.optional(),
|
|
32
|
+
// Some entries use a top-level content array (e.g. tool_result lines)
|
|
33
|
+
content: z
|
|
34
|
+
.union([
|
|
35
|
+
z.string(),
|
|
36
|
+
z.array(z.unknown()),
|
|
37
|
+
])
|
|
38
|
+
.optional(),
|
|
39
|
+
});
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Heuristics
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Phrases that suggest the assistant is correcting itself
|
|
44
|
+
const REWORK_PHRASES = [
|
|
45
|
+
/let me try again/i,
|
|
46
|
+
/actually[,\s]/i,
|
|
47
|
+
/i made a mistake/i,
|
|
48
|
+
/i was wrong/i,
|
|
49
|
+
/apologies[,\s]/i,
|
|
50
|
+
/i apologise/i,
|
|
51
|
+
/i apologize/i,
|
|
52
|
+
/let me reconsider/i,
|
|
53
|
+
/i need to correct/i,
|
|
54
|
+
/i got that wrong/i,
|
|
55
|
+
/that was incorrect/i,
|
|
56
|
+
/allow me to redo/i,
|
|
57
|
+
/let me redo/i,
|
|
58
|
+
/i misunderstood/i,
|
|
59
|
+
/i overlooked/i,
|
|
60
|
+
];
|
|
61
|
+
function detectRework(text) {
|
|
62
|
+
return REWORK_PHRASES.some((re) => re.test(text));
|
|
63
|
+
}
|
|
64
|
+
function extractTextFromContent(content) {
|
|
65
|
+
if (!content)
|
|
66
|
+
return '';
|
|
67
|
+
if (typeof content === 'string')
|
|
68
|
+
return content;
|
|
69
|
+
if (Array.isArray(content)) {
|
|
70
|
+
return content
|
|
71
|
+
.map((block) => {
|
|
72
|
+
if (block &&
|
|
73
|
+
typeof block === 'object' &&
|
|
74
|
+
'type' in block &&
|
|
75
|
+
block.type === 'text' &&
|
|
76
|
+
'text' in block) {
|
|
77
|
+
return block.text ?? '';
|
|
78
|
+
}
|
|
79
|
+
return '';
|
|
80
|
+
})
|
|
81
|
+
.join(' ');
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
function extractToolName(content) {
|
|
86
|
+
if (!Array.isArray(content))
|
|
87
|
+
return [];
|
|
88
|
+
const names = [];
|
|
89
|
+
for (const block of content) {
|
|
90
|
+
if (block &&
|
|
91
|
+
typeof block === 'object' &&
|
|
92
|
+
'type' in block &&
|
|
93
|
+
block.type === 'tool_use' &&
|
|
94
|
+
'name' in block) {
|
|
95
|
+
names.push(block.name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return names;
|
|
99
|
+
}
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// JSONL parsing
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
function parseJsonlFile(filePath) {
|
|
104
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
105
|
+
const entries = [];
|
|
106
|
+
for (const line of raw.split('\n')) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (!trimmed)
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const parsed = JSON.parse(trimmed);
|
|
112
|
+
const result = MessageEntrySchema.safeParse(parsed);
|
|
113
|
+
if (result.success) {
|
|
114
|
+
entries.push(result.data);
|
|
115
|
+
}
|
|
116
|
+
// Silently skip lines that don't match — real logs have metadata entries too
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Malformed JSON line — skip
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return entries;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Parse a single JSONL conversation file and return one ParsedSession per
|
|
126
|
+
* unique sessionId found. Claude Code can store multiple sessions in the same
|
|
127
|
+
* file, so we group by sessionId.
|
|
128
|
+
*/
|
|
129
|
+
export function parseConversationFile(filePath, projectName) {
|
|
130
|
+
const entries = parseJsonlFile(filePath);
|
|
131
|
+
// Group entries by sessionId
|
|
132
|
+
const bySession = new Map();
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
// sessionId can live on the entry directly or be derived from the uuid
|
|
135
|
+
const sid = entry.sessionId ??
|
|
136
|
+
(entry.uuid ? entry.uuid.split('-').slice(0, 3).join('-') : null);
|
|
137
|
+
if (!sid)
|
|
138
|
+
continue;
|
|
139
|
+
if (!bySession.has(sid)) {
|
|
140
|
+
bySession.set(sid, []);
|
|
141
|
+
}
|
|
142
|
+
bySession.get(sid).push(entry);
|
|
143
|
+
}
|
|
144
|
+
// If nothing was grouped (no sessionId fields), treat the whole file as one
|
|
145
|
+
// session using the filename as the id.
|
|
146
|
+
if (bySession.size === 0 && entries.length > 0) {
|
|
147
|
+
const fallbackId = path.basename(filePath, path.extname(filePath));
|
|
148
|
+
bySession.set(fallbackId, entries);
|
|
149
|
+
}
|
|
150
|
+
const sessions = [];
|
|
151
|
+
for (const [session_id, sessionEntries] of bySession) {
|
|
152
|
+
let turn_count = 0;
|
|
153
|
+
let char_count = 0;
|
|
154
|
+
const tool_calls = {};
|
|
155
|
+
let had_rework = false;
|
|
156
|
+
let earliest_timestamp = '';
|
|
157
|
+
for (const entry of sessionEntries) {
|
|
158
|
+
// Count message turns
|
|
159
|
+
if (entry.message) {
|
|
160
|
+
const role = entry.message.role;
|
|
161
|
+
if (role === 'user' || role === 'assistant') {
|
|
162
|
+
turn_count++;
|
|
163
|
+
}
|
|
164
|
+
const content = entry.message.content;
|
|
165
|
+
const text = extractTextFromContent(content);
|
|
166
|
+
char_count += text.length;
|
|
167
|
+
// Rework detection on assistant messages
|
|
168
|
+
if (role === 'assistant' && text && detectRework(text)) {
|
|
169
|
+
had_rework = true;
|
|
170
|
+
}
|
|
171
|
+
// Tool call counting from assistant messages
|
|
172
|
+
if (role === 'assistant') {
|
|
173
|
+
const toolNames = extractToolName(content);
|
|
174
|
+
for (const name of toolNames) {
|
|
175
|
+
tool_calls[name] = (tool_calls[name] ?? 0) + 1;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Track earliest timestamp for the session
|
|
180
|
+
if (entry.timestamp && !earliest_timestamp) {
|
|
181
|
+
earliest_timestamp = entry.timestamp;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
sessions.push({
|
|
185
|
+
session_id,
|
|
186
|
+
project: projectName,
|
|
187
|
+
timestamp: earliest_timestamp || new Date().toISOString(),
|
|
188
|
+
turn_count,
|
|
189
|
+
estimated_tokens: Math.ceil(char_count / 4),
|
|
190
|
+
tool_calls,
|
|
191
|
+
had_rework,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return sessions;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Scan ~/.claude/projects/ and return all projects that have a conversations
|
|
198
|
+
* JSONL file. Claude Code stores conversations at different paths depending on
|
|
199
|
+
* the version:
|
|
200
|
+
* ~/.claude/projects/<slug>/conversations.jsonl (older)
|
|
201
|
+
* ~/.claude/projects/<slug>/<uuid>.jsonl (newer, one file per session)
|
|
202
|
+
*/
|
|
203
|
+
export function discoverProjects(claudeDir) {
|
|
204
|
+
const baseDir = claudeDir ?? path.join(process.env.HOME ?? '~', '.claude', 'projects');
|
|
205
|
+
if (!fs.existsSync(baseDir)) {
|
|
206
|
+
return [];
|
|
207
|
+
}
|
|
208
|
+
const projects = [];
|
|
209
|
+
const projectDirs = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
210
|
+
for (const dirent of projectDirs) {
|
|
211
|
+
if (!dirent.isDirectory())
|
|
212
|
+
continue;
|
|
213
|
+
const projectPath = path.join(baseDir, dirent.name);
|
|
214
|
+
const projectName = dirent.name;
|
|
215
|
+
// Collect all JSONL files in this project directory
|
|
216
|
+
const files = fs.readdirSync(projectPath).filter((f) => f.endsWith('.jsonl'));
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
projects.push({
|
|
219
|
+
name: projectName,
|
|
220
|
+
conversationFile: path.join(projectPath, file),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return projects;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/lib/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const PATINA_DIR = ".patina";
|
|
3
|
+
export declare const SESSIONS_DIR: string;
|
|
4
|
+
export declare const CYCLES_DIR: string;
|
|
5
|
+
export declare const CAPTURES_DIR: string;
|
|
6
|
+
export declare const METRICS_FILE: string;
|
|
7
|
+
export declare const LIVING_DOC_FILE: string;
|
|
8
|
+
export declare function patinaExists(cwd?: string): boolean;
|
|
9
|
+
export declare function assertInitialised(cwd?: string): void;
|
|
10
|
+
export declare const SessionSummarySchema: z.ZodObject<{
|
|
11
|
+
session_id: z.ZodString;
|
|
12
|
+
project: z.ZodString;
|
|
13
|
+
timestamp: z.ZodString;
|
|
14
|
+
turn_count: z.ZodNumber;
|
|
15
|
+
estimated_tokens: z.ZodNumber;
|
|
16
|
+
tool_calls: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
17
|
+
had_rework: z.ZodBoolean;
|
|
18
|
+
ingested_at: z.ZodString;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
session_id: string;
|
|
21
|
+
project: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
turn_count: number;
|
|
24
|
+
estimated_tokens: number;
|
|
25
|
+
tool_calls: Record<string, number>;
|
|
26
|
+
had_rework: boolean;
|
|
27
|
+
ingested_at: string;
|
|
28
|
+
}, {
|
|
29
|
+
session_id: string;
|
|
30
|
+
project: string;
|
|
31
|
+
timestamp: string;
|
|
32
|
+
turn_count: number;
|
|
33
|
+
estimated_tokens: number;
|
|
34
|
+
tool_calls: Record<string, number>;
|
|
35
|
+
had_rework: boolean;
|
|
36
|
+
ingested_at: string;
|
|
37
|
+
}>;
|
|
38
|
+
export type SessionSummary = z.infer<typeof SessionSummarySchema>;
|
|
39
|
+
export declare const MetricsSchema: z.ZodObject<{
|
|
40
|
+
last_updated: z.ZodString;
|
|
41
|
+
cycles: z.ZodArray<z.ZodObject<{
|
|
42
|
+
cycle_id: z.ZodString;
|
|
43
|
+
created_at: z.ZodString;
|
|
44
|
+
session_count: z.ZodNumber;
|
|
45
|
+
total_tokens: z.ZodNumber;
|
|
46
|
+
rework_count: z.ZodNumber;
|
|
47
|
+
}, "strip", z.ZodTypeAny, {
|
|
48
|
+
cycle_id: string;
|
|
49
|
+
created_at: string;
|
|
50
|
+
session_count: number;
|
|
51
|
+
total_tokens: number;
|
|
52
|
+
rework_count: number;
|
|
53
|
+
}, {
|
|
54
|
+
cycle_id: string;
|
|
55
|
+
created_at: string;
|
|
56
|
+
session_count: number;
|
|
57
|
+
total_tokens: number;
|
|
58
|
+
rework_count: number;
|
|
59
|
+
}>, "many">;
|
|
60
|
+
}, "strip", z.ZodTypeAny, {
|
|
61
|
+
cycles: {
|
|
62
|
+
cycle_id: string;
|
|
63
|
+
created_at: string;
|
|
64
|
+
session_count: number;
|
|
65
|
+
total_tokens: number;
|
|
66
|
+
rework_count: number;
|
|
67
|
+
}[];
|
|
68
|
+
last_updated: string;
|
|
69
|
+
}, {
|
|
70
|
+
cycles: {
|
|
71
|
+
cycle_id: string;
|
|
72
|
+
created_at: string;
|
|
73
|
+
session_count: number;
|
|
74
|
+
total_tokens: number;
|
|
75
|
+
rework_count: number;
|
|
76
|
+
}[];
|
|
77
|
+
last_updated: string;
|
|
78
|
+
}>;
|
|
79
|
+
export type Metrics = z.infer<typeof MetricsSchema>;
|
|
80
|
+
export declare function ensureDir(dirPath: string): void;
|
|
81
|
+
export declare function writeJson(filePath: string, data: unknown): void;
|
|
82
|
+
export declare function readJson<T>(filePath: string): T;
|
|
83
|
+
export declare function fileExists(filePath: string): boolean;
|
|
84
|
+
export declare function sessionFilePath(sessionId: string, cwd?: string): string;
|
|
85
|
+
export declare function sessionExists(sessionId: string, cwd?: string): boolean;
|
|
86
|
+
export declare function writeSession(summary: SessionSummary, cwd?: string): void;
|
|
87
|
+
export declare function readAllSessions(cwd?: string): SessionSummary[];
|
|
88
|
+
export declare function readMetrics(cwd?: string): Metrics;
|
|
89
|
+
export declare function writeMetrics(metrics: Metrics, cwd?: string): void;
|
|
90
|
+
export interface PendingDiff {
|
|
91
|
+
section: string;
|
|
92
|
+
rationale: string;
|
|
93
|
+
diff: string;
|
|
94
|
+
timestamp: string;
|
|
95
|
+
}
|
|
96
|
+
export declare const PENDING_DIFF_FILE: string;
|
|
97
|
+
export declare function writePendingDiff(diff: PendingDiff, cwd?: string): void;
|
|
98
|
+
export declare function readPendingDiff(cwd?: string): PendingDiff | null;
|
|
99
|
+
export declare function cycleFilePath(date: string, cwd?: string): string;
|
|
100
|
+
export declare function getLatestCycleDate(cwd?: string): string | null;
|
|
101
|
+
export declare function writeCycleFile(date: string, content: string, cwd?: string): void;
|
|
102
|
+
export declare const CAPTURE_TAGS: readonly ["near-miss", "went-well", "frustration", "pattern", "other"];
|
|
103
|
+
export type CaptureTag = typeof CAPTURE_TAGS[number];
|
|
104
|
+
export declare const CaptureSchema: z.ZodObject<{
|
|
105
|
+
id: z.ZodString;
|
|
106
|
+
text: z.ZodString;
|
|
107
|
+
tag: z.ZodOptional<z.ZodEnum<["near-miss", "went-well", "frustration", "pattern", "other"]>>;
|
|
108
|
+
author: z.ZodString;
|
|
109
|
+
timestamp: z.ZodString;
|
|
110
|
+
}, "strip", z.ZodTypeAny, {
|
|
111
|
+
timestamp: string;
|
|
112
|
+
id: string;
|
|
113
|
+
text: string;
|
|
114
|
+
author: string;
|
|
115
|
+
tag?: "near-miss" | "went-well" | "frustration" | "pattern" | "other" | undefined;
|
|
116
|
+
}, {
|
|
117
|
+
timestamp: string;
|
|
118
|
+
id: string;
|
|
119
|
+
text: string;
|
|
120
|
+
author: string;
|
|
121
|
+
tag?: "near-miss" | "went-well" | "frustration" | "pattern" | "other" | undefined;
|
|
122
|
+
}>;
|
|
123
|
+
export type Capture = z.infer<typeof CaptureSchema>;
|
|
124
|
+
export declare function writeCapture(capture: Capture, cwd?: string): void;
|
|
125
|
+
export declare function readCaptures(cwd?: string, sinceDate?: string | null): Capture[];
|
|
126
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Paths
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
export const PATINA_DIR = '.patina';
|
|
8
|
+
export const SESSIONS_DIR = path.join(PATINA_DIR, 'sessions');
|
|
9
|
+
export const CYCLES_DIR = path.join(PATINA_DIR, 'cycles');
|
|
10
|
+
export const CAPTURES_DIR = path.join(PATINA_DIR, 'captures');
|
|
11
|
+
export const METRICS_FILE = path.join(PATINA_DIR, 'metrics.json');
|
|
12
|
+
export const LIVING_DOC_FILE = path.join(PATINA_DIR, 'patina.md');
|
|
13
|
+
export function patinaExists(cwd = process.cwd()) {
|
|
14
|
+
return fs.existsSync(path.join(cwd, PATINA_DIR));
|
|
15
|
+
}
|
|
16
|
+
export function assertInitialised(cwd = process.cwd()) {
|
|
17
|
+
if (!patinaExists(cwd)) {
|
|
18
|
+
console.error('Error: .patina/ not found. Run `patina init` first.');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Session summary schema
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const SessionSummarySchema = z.object({
|
|
26
|
+
session_id: z.string(),
|
|
27
|
+
project: z.string(),
|
|
28
|
+
timestamp: z.string(), // ISO-8601
|
|
29
|
+
turn_count: z.number().int().nonnegative(),
|
|
30
|
+
estimated_tokens: z.number().int().nonnegative(),
|
|
31
|
+
tool_calls: z.record(z.string(), z.number().int().nonnegative()),
|
|
32
|
+
had_rework: z.boolean(),
|
|
33
|
+
ingested_at: z.string(), // ISO-8601
|
|
34
|
+
});
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Metrics schema
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
export const MetricsSchema = z.object({
|
|
39
|
+
last_updated: z.string(),
|
|
40
|
+
cycles: z.array(z.object({
|
|
41
|
+
cycle_id: z.string(),
|
|
42
|
+
created_at: z.string(),
|
|
43
|
+
session_count: z.number(),
|
|
44
|
+
total_tokens: z.number(),
|
|
45
|
+
rework_count: z.number(),
|
|
46
|
+
})),
|
|
47
|
+
});
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// File I/O helpers
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export function ensureDir(dirPath) {
|
|
52
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
export function writeJson(filePath, data) {
|
|
55
|
+
ensureDir(path.dirname(filePath));
|
|
56
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
export function readJson(filePath) {
|
|
59
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
+
return JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
export function fileExists(filePath) {
|
|
63
|
+
return fs.existsSync(filePath);
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Session helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
export function sessionFilePath(sessionId, cwd = process.cwd()) {
|
|
69
|
+
// Use a safe filename derived from the session_id
|
|
70
|
+
const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
71
|
+
return path.join(cwd, SESSIONS_DIR, `${safe}.json`);
|
|
72
|
+
}
|
|
73
|
+
export function sessionExists(sessionId, cwd = process.cwd()) {
|
|
74
|
+
return fileExists(sessionFilePath(sessionId, cwd));
|
|
75
|
+
}
|
|
76
|
+
export function writeSession(summary, cwd = process.cwd()) {
|
|
77
|
+
const validated = SessionSummarySchema.parse(summary);
|
|
78
|
+
writeJson(sessionFilePath(summary.session_id, cwd), validated);
|
|
79
|
+
}
|
|
80
|
+
export function readAllSessions(cwd = process.cwd()) {
|
|
81
|
+
const dir = path.join(cwd, SESSIONS_DIR);
|
|
82
|
+
if (!fs.existsSync(dir))
|
|
83
|
+
return [];
|
|
84
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
85
|
+
const sessions = [];
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
try {
|
|
88
|
+
const raw = readJson(path.join(dir, file));
|
|
89
|
+
const parsed = SessionSummarySchema.safeParse(raw);
|
|
90
|
+
if (parsed.success) {
|
|
91
|
+
sessions.push(parsed.data);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.warn(`Warning: skipping malformed session file: ${file}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
console.warn(`Warning: could not read session file: ${file}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return sessions;
|
|
102
|
+
}
|
|
103
|
+
export function readMetrics(cwd = process.cwd()) {
|
|
104
|
+
const file = path.join(cwd, METRICS_FILE);
|
|
105
|
+
if (!fileExists(file))
|
|
106
|
+
return { last_updated: new Date().toISOString(), cycles: [] };
|
|
107
|
+
try {
|
|
108
|
+
const raw = readJson(file);
|
|
109
|
+
return MetricsSchema.parse(raw);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return { last_updated: new Date().toISOString(), cycles: [] };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export function writeMetrics(metrics, cwd = process.cwd()) {
|
|
116
|
+
const validated = MetricsSchema.parse(metrics);
|
|
117
|
+
writeJson(path.join(cwd, METRICS_FILE), validated);
|
|
118
|
+
}
|
|
119
|
+
export const PENDING_DIFF_FILE = path.join(PATINA_DIR, 'pending-diff.json');
|
|
120
|
+
export function writePendingDiff(diff, cwd = process.cwd()) {
|
|
121
|
+
writeJson(path.join(cwd, PENDING_DIFF_FILE), diff);
|
|
122
|
+
}
|
|
123
|
+
export function readPendingDiff(cwd = process.cwd()) {
|
|
124
|
+
const file = path.join(cwd, PENDING_DIFF_FILE);
|
|
125
|
+
if (!fileExists(file))
|
|
126
|
+
return null;
|
|
127
|
+
try {
|
|
128
|
+
return readJson(file);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Cycle file helpers
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
export function cycleFilePath(date, cwd = process.cwd()) {
|
|
138
|
+
return path.join(cwd, CYCLES_DIR, `${date}.md`);
|
|
139
|
+
}
|
|
140
|
+
export function getLatestCycleDate(cwd = process.cwd()) {
|
|
141
|
+
const dir = path.join(cwd, CYCLES_DIR);
|
|
142
|
+
if (!fs.existsSync(dir))
|
|
143
|
+
return null;
|
|
144
|
+
const files = fs
|
|
145
|
+
.readdirSync(dir)
|
|
146
|
+
.filter((f) => /^\d{4}-\d{2}-\d{2}\.md$/.test(f))
|
|
147
|
+
.sort();
|
|
148
|
+
if (files.length === 0)
|
|
149
|
+
return null;
|
|
150
|
+
// Remove .md suffix to return just the date string
|
|
151
|
+
return files[files.length - 1].replace(/\.md$/, '');
|
|
152
|
+
}
|
|
153
|
+
export function writeCycleFile(date, content, cwd = process.cwd()) {
|
|
154
|
+
const filePath = cycleFilePath(date, cwd);
|
|
155
|
+
ensureDir(path.dirname(filePath));
|
|
156
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
157
|
+
}
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Capture helpers (event-driven capture between retro cycles)
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
export const CAPTURE_TAGS = ['near-miss', 'went-well', 'frustration', 'pattern', 'other'];
|
|
162
|
+
export const CaptureSchema = z.object({
|
|
163
|
+
id: z.string(),
|
|
164
|
+
text: z.string(),
|
|
165
|
+
tag: z.enum(CAPTURE_TAGS).optional(),
|
|
166
|
+
author: z.string(),
|
|
167
|
+
timestamp: z.string(), // ISO-8601
|
|
168
|
+
});
|
|
169
|
+
export function writeCapture(capture, cwd = process.cwd()) {
|
|
170
|
+
const validated = CaptureSchema.parse(capture);
|
|
171
|
+
const safe = capture.id.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
172
|
+
const filePath = path.join(cwd, CAPTURES_DIR, `${safe}.json`);
|
|
173
|
+
ensureDir(path.dirname(filePath));
|
|
174
|
+
writeJson(filePath, validated);
|
|
175
|
+
}
|
|
176
|
+
export function readCaptures(cwd = process.cwd(), sinceDate) {
|
|
177
|
+
const dir = path.join(cwd, CAPTURES_DIR);
|
|
178
|
+
if (!fs.existsSync(dir))
|
|
179
|
+
return [];
|
|
180
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
181
|
+
const captures = [];
|
|
182
|
+
for (const file of files) {
|
|
183
|
+
try {
|
|
184
|
+
const raw = readJson(path.join(dir, file));
|
|
185
|
+
const parsed = CaptureSchema.safeParse(raw);
|
|
186
|
+
if (parsed.success) {
|
|
187
|
+
captures.push(parsed.data);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
// skip malformed capture files
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const sorted = captures.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
195
|
+
if (sinceDate) {
|
|
196
|
+
const cutoff = new Date(sinceDate + 'T00:00:00Z').getTime();
|
|
197
|
+
return sorted.filter((c) => new Date(c.timestamp).getTime() > cutoff);
|
|
198
|
+
}
|
|
199
|
+
return sorted;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,8EAA8E;AAC9E,QAAQ;AACR,8EAA8E;AAE9E,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC9D,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAElE,MAAM,UAAU,YAAY,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC9C,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACnD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CACX,qDAAqD,CACtD,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,yBAAyB;AACzB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW;IAClC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC1C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAChD,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAChE,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW;CACrC,CAAC,CAAC;AAIH,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;IACxB,MAAM,EAAE,CAAC,CAAC,KAAK,CACb,CAAC,CAAC,MAAM,CAAC;QACP,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;QACzB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;QACxB,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE;KACzB,CAAC,CACH;CACF,CAAC,CAAC;AAIH,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAa;IACvD,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,QAAgB;IAC1C,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E,MAAM,UAAU,eAAe,CAAC,SAAiB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACpE,kDAAkD;IAClD,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAClE,OAAO,UAAU,CAAC,eAAe,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAuB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACvE,MAAM,SAAS,GAAG,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtD,SAAS,CAAC,eAAe,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,EAAE,SAAS,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACjD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAqB,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAU,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YACnD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CAAC,6CAA6C,IAAI,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;QAChE,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACrF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,QAAQ,CAAU,IAAI,CAAC,CAAC;QACpC,OAAO,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAChE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAChE,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,SAAS,CAAC,CAAC;AACrD,CAAC;AAaD,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;AAE5E,MAAM,UAAU,gBAAgB,CAAC,IAAiB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACrE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,EAAE,IAAI,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,OAAO,QAAQ,CAAc,IAAI,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,qBAAqB;AACrB,8EAA8E;AAE9E,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAErC,MAAM,KAAK,GAAG,EAAE;SACb,WAAW,CAAC,GAAG,CAAC;SAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SAChD,IAAI,EAAE,CAAC;IAEV,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,mDAAmD;IACnD,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,OAAe,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC/E,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC;AAED,8EAA8E;AAC9E,8DAA8D;AAC9D,8EAA8E;AAE9E,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,CAAU,CAAC;AAGnG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,EAAE;IACpC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW;CACnC,CAAC,CAAC;AAIH,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAChE,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;IAC9D,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,EAAE,SAAyB;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAU,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;YAC5C,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACnB,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,+BAA+B;QACjC,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAE/E,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,GAAG,YAAY,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lcvbeek/patina",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-assisted retrospective tool for Claude Code teams",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"patina": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/lcvbeek/patina.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/lcvbeek/patina#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/lcvbeek/patina/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ai",
|
|
24
|
+
"retrospective",
|
|
25
|
+
"claude",
|
|
26
|
+
"developer-tools",
|
|
27
|
+
"team"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "tsx src/index.ts",
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"start": "node dist/index.js",
|
|
33
|
+
"prepublishOnly": "npm run build"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@anthropic-ai/sdk": "^0.37.0",
|
|
37
|
+
"commander": "^12.1.0",
|
|
38
|
+
"zod": "^3.23.8"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^22.10.0",
|
|
42
|
+
"tsx": "^4.19.2",
|
|
43
|
+
"typescript": "^5.7.2"
|
|
44
|
+
}
|
|
45
|
+
}
|