learning-agent 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/CHANGELOG.md +95 -0
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1045 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +780 -0
- package/dist/index.js +732 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { LlamaEmbeddingContext } from 'node-llama-cpp';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lesson type definitions using Zod schemas
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
declare const SourceSchema: z.ZodEnum<["user_correction", "self_correction", "test_failure", "manual"]>;
|
|
9
|
+
declare const ContextSchema: z.ZodObject<{
|
|
10
|
+
tool: z.ZodString;
|
|
11
|
+
intent: z.ZodString;
|
|
12
|
+
}, "strip", z.ZodTypeAny, {
|
|
13
|
+
tool: string;
|
|
14
|
+
intent: string;
|
|
15
|
+
}, {
|
|
16
|
+
tool: string;
|
|
17
|
+
intent: string;
|
|
18
|
+
}>;
|
|
19
|
+
declare const SeveritySchema: z.ZodEnum<["high", "medium", "low"]>;
|
|
20
|
+
declare const LessonTypeSchema: z.ZodEnum<["quick", "full"]>;
|
|
21
|
+
/**
|
|
22
|
+
* Unified Lesson schema.
|
|
23
|
+
*
|
|
24
|
+
* The `type` field is a semantic marker:
|
|
25
|
+
* - 'quick': Minimal lesson for fast capture
|
|
26
|
+
* - 'full': Important lesson (typically has evidence/severity)
|
|
27
|
+
*
|
|
28
|
+
* All fields except core identity are optional for flexibility.
|
|
29
|
+
* Semantic meaning is preserved through convention, not schema enforcement.
|
|
30
|
+
*/
|
|
31
|
+
declare const LessonSchema: z.ZodObject<{
|
|
32
|
+
id: z.ZodString;
|
|
33
|
+
type: z.ZodEnum<["quick", "full"]>;
|
|
34
|
+
trigger: z.ZodString;
|
|
35
|
+
insight: z.ZodString;
|
|
36
|
+
tags: z.ZodArray<z.ZodString, "many">;
|
|
37
|
+
source: z.ZodEnum<["user_correction", "self_correction", "test_failure", "manual"]>;
|
|
38
|
+
context: z.ZodObject<{
|
|
39
|
+
tool: z.ZodString;
|
|
40
|
+
intent: z.ZodString;
|
|
41
|
+
}, "strip", z.ZodTypeAny, {
|
|
42
|
+
tool: string;
|
|
43
|
+
intent: string;
|
|
44
|
+
}, {
|
|
45
|
+
tool: string;
|
|
46
|
+
intent: string;
|
|
47
|
+
}>;
|
|
48
|
+
created: z.ZodString;
|
|
49
|
+
confirmed: z.ZodBoolean;
|
|
50
|
+
supersedes: z.ZodArray<z.ZodString, "many">;
|
|
51
|
+
related: z.ZodArray<z.ZodString, "many">;
|
|
52
|
+
evidence: z.ZodOptional<z.ZodString>;
|
|
53
|
+
severity: z.ZodOptional<z.ZodEnum<["high", "medium", "low"]>>;
|
|
54
|
+
pattern: z.ZodOptional<z.ZodObject<{
|
|
55
|
+
bad: z.ZodString;
|
|
56
|
+
good: z.ZodString;
|
|
57
|
+
}, "strip", z.ZodTypeAny, {
|
|
58
|
+
bad: string;
|
|
59
|
+
good: string;
|
|
60
|
+
}, {
|
|
61
|
+
bad: string;
|
|
62
|
+
good: string;
|
|
63
|
+
}>>;
|
|
64
|
+
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
retrievalCount: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
}, "strip", z.ZodTypeAny, {
|
|
67
|
+
type: "quick" | "full";
|
|
68
|
+
id: string;
|
|
69
|
+
trigger: string;
|
|
70
|
+
insight: string;
|
|
71
|
+
tags: string[];
|
|
72
|
+
source: "user_correction" | "self_correction" | "test_failure" | "manual";
|
|
73
|
+
context: {
|
|
74
|
+
tool: string;
|
|
75
|
+
intent: string;
|
|
76
|
+
};
|
|
77
|
+
created: string;
|
|
78
|
+
confirmed: boolean;
|
|
79
|
+
supersedes: string[];
|
|
80
|
+
related: string[];
|
|
81
|
+
evidence?: string | undefined;
|
|
82
|
+
severity?: "high" | "medium" | "low" | undefined;
|
|
83
|
+
pattern?: {
|
|
84
|
+
bad: string;
|
|
85
|
+
good: string;
|
|
86
|
+
} | undefined;
|
|
87
|
+
deleted?: boolean | undefined;
|
|
88
|
+
retrievalCount?: number | undefined;
|
|
89
|
+
}, {
|
|
90
|
+
type: "quick" | "full";
|
|
91
|
+
id: string;
|
|
92
|
+
trigger: string;
|
|
93
|
+
insight: string;
|
|
94
|
+
tags: string[];
|
|
95
|
+
source: "user_correction" | "self_correction" | "test_failure" | "manual";
|
|
96
|
+
context: {
|
|
97
|
+
tool: string;
|
|
98
|
+
intent: string;
|
|
99
|
+
};
|
|
100
|
+
created: string;
|
|
101
|
+
confirmed: boolean;
|
|
102
|
+
supersedes: string[];
|
|
103
|
+
related: string[];
|
|
104
|
+
evidence?: string | undefined;
|
|
105
|
+
severity?: "high" | "medium" | "low" | undefined;
|
|
106
|
+
pattern?: {
|
|
107
|
+
bad: string;
|
|
108
|
+
good: string;
|
|
109
|
+
} | undefined;
|
|
110
|
+
deleted?: boolean | undefined;
|
|
111
|
+
retrievalCount?: number | undefined;
|
|
112
|
+
}>;
|
|
113
|
+
declare const TombstoneSchema: z.ZodObject<{
|
|
114
|
+
id: z.ZodString;
|
|
115
|
+
deleted: z.ZodLiteral<true>;
|
|
116
|
+
deletedAt: z.ZodString;
|
|
117
|
+
}, "strip", z.ZodTypeAny, {
|
|
118
|
+
id: string;
|
|
119
|
+
deleted: true;
|
|
120
|
+
deletedAt: string;
|
|
121
|
+
}, {
|
|
122
|
+
id: string;
|
|
123
|
+
deleted: true;
|
|
124
|
+
deletedAt: string;
|
|
125
|
+
}>;
|
|
126
|
+
type Lesson = z.infer<typeof LessonSchema>;
|
|
127
|
+
type LessonType = z.infer<typeof LessonTypeSchema>;
|
|
128
|
+
type Tombstone = z.infer<typeof TombstoneSchema>;
|
|
129
|
+
type Source = z.infer<typeof SourceSchema>;
|
|
130
|
+
type Severity = z.infer<typeof SeveritySchema>;
|
|
131
|
+
type Context = z.infer<typeof ContextSchema>;
|
|
132
|
+
/**
|
|
133
|
+
* Generate deterministic lesson ID from insight text.
|
|
134
|
+
* Format: L + 8 hex characters from SHA-256 hash
|
|
135
|
+
*/
|
|
136
|
+
declare function generateId(insight: string): string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* JSONL storage layer for lessons
|
|
140
|
+
*
|
|
141
|
+
* Append-only storage with last-write-wins deduplication.
|
|
142
|
+
* Source of truth - git trackable.
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/** Relative path to lessons file from repo root */
|
|
146
|
+
declare const LESSONS_PATH = ".claude/lessons/index.jsonl";
|
|
147
|
+
/** Options for reading lessons */
|
|
148
|
+
interface ReadLessonsOptions {
|
|
149
|
+
/** If true, throw on first parse error. Default: false (skip errors) */
|
|
150
|
+
strict?: boolean;
|
|
151
|
+
/** Callback for each parse error in non-strict mode */
|
|
152
|
+
onParseError?: (error: ParseError) => void;
|
|
153
|
+
}
|
|
154
|
+
/** Parse error details */
|
|
155
|
+
interface ParseError {
|
|
156
|
+
/** 1-based line number */
|
|
157
|
+
line: number;
|
|
158
|
+
/** Error message */
|
|
159
|
+
message: string;
|
|
160
|
+
/** Original error */
|
|
161
|
+
cause: unknown;
|
|
162
|
+
}
|
|
163
|
+
/** Result of reading lessons */
|
|
164
|
+
interface ReadLessonsResult {
|
|
165
|
+
/** Successfully parsed lessons */
|
|
166
|
+
lessons: Lesson[];
|
|
167
|
+
/** Number of lines skipped due to errors */
|
|
168
|
+
skippedCount: number;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Append a lesson to the JSONL file.
|
|
172
|
+
* Creates directory structure if missing.
|
|
173
|
+
*/
|
|
174
|
+
declare function appendLesson(repoRoot: string, lesson: Lesson): Promise<void>;
|
|
175
|
+
/**
|
|
176
|
+
* Read all non-deleted lessons from the JSONL file.
|
|
177
|
+
* Applies last-write-wins deduplication by ID.
|
|
178
|
+
* Returns result object with lessons and skippedCount.
|
|
179
|
+
*
|
|
180
|
+
* @param repoRoot - Repository root directory
|
|
181
|
+
* @param options - Optional settings for error handling
|
|
182
|
+
* @returns Result with lessons array and count of skipped lines
|
|
183
|
+
*/
|
|
184
|
+
declare function readLessons(repoRoot: string, options?: ReadLessonsOptions): Promise<ReadLessonsResult>;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* SQLite storage layer with FTS5 for full-text search
|
|
188
|
+
*
|
|
189
|
+
* Rebuildable index - not the source of truth.
|
|
190
|
+
* Stored in .claude/.cache (gitignored).
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/** Relative path to database file from repo root */
|
|
194
|
+
declare const DB_PATH = ".claude/.cache/lessons.sqlite";
|
|
195
|
+
/**
|
|
196
|
+
* Close the database connection and release resources.
|
|
197
|
+
*
|
|
198
|
+
* **Resource lifecycle:**
|
|
199
|
+
* - The database is opened lazily on first call to `openDb()` or any function that uses it
|
|
200
|
+
* (e.g., `searchKeyword`, `rebuildIndex`, `syncIfNeeded`, `getCachedEmbedding`)
|
|
201
|
+
* - Once opened, the connection remains active until `closeDb()` is called
|
|
202
|
+
* - After closing, subsequent database operations will reopen the connection
|
|
203
|
+
*
|
|
204
|
+
* **When to call:**
|
|
205
|
+
* - At the end of CLI commands to ensure clean process exit
|
|
206
|
+
* - When transitioning between repositories in long-running processes
|
|
207
|
+
* - Before process exit in graceful shutdown handlers
|
|
208
|
+
*
|
|
209
|
+
* **Best practices for long-running processes:**
|
|
210
|
+
* - In single-operation scripts: call before exit
|
|
211
|
+
* - In daemon/server processes: call in shutdown handler
|
|
212
|
+
* - Not necessary to call between operations in the same repository
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```typescript
|
|
216
|
+
* // CLI command pattern
|
|
217
|
+
* try {
|
|
218
|
+
* await searchKeyword(repoRoot, 'typescript', 10);
|
|
219
|
+
* // ... process results
|
|
220
|
+
* } finally {
|
|
221
|
+
* closeDb();
|
|
222
|
+
* }
|
|
223
|
+
*
|
|
224
|
+
* // Graceful shutdown pattern
|
|
225
|
+
* process.on('SIGTERM', () => {
|
|
226
|
+
* closeDb();
|
|
227
|
+
* process.exit(0);
|
|
228
|
+
* });
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
declare function closeDb(): void;
|
|
232
|
+
/**
|
|
233
|
+
* Rebuild the SQLite index from the JSONL source of truth.
|
|
234
|
+
* Preserves embeddings where content hash is unchanged.
|
|
235
|
+
* Updates the last sync mtime after successful rebuild.
|
|
236
|
+
*/
|
|
237
|
+
declare function rebuildIndex(repoRoot: string): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Search lessons using FTS5 keyword search.
|
|
240
|
+
* Returns matching lessons up to the specified limit.
|
|
241
|
+
* Increments retrieval count for all returned lessons.
|
|
242
|
+
*/
|
|
243
|
+
declare function searchKeyword(repoRoot: string, query: string, limit: number): Promise<Lesson[]>;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Embedding model resolution using node-llama-cpp's built-in resolver.
|
|
247
|
+
*
|
|
248
|
+
* Uses resolveModelFile for automatic download and caching.
|
|
249
|
+
* Model is stored in ~/.node-llama-cpp/models/ by default.
|
|
250
|
+
*/
|
|
251
|
+
/**
|
|
252
|
+
* HuggingFace URI for EmbeddingGemma-300M (Q4_0 quantization).
|
|
253
|
+
*
|
|
254
|
+
* - Size: ~278MB
|
|
255
|
+
* - Dimensions: 768 (default), supports MRL truncation to 512/256/128
|
|
256
|
+
* - Context: 2048 tokens
|
|
257
|
+
*/
|
|
258
|
+
declare const MODEL_URI = "hf:ggml-org/embeddinggemma-300M-qat-q4_0-GGUF/embeddinggemma-300M-qat-Q4_0.gguf";
|
|
259
|
+
/**
|
|
260
|
+
* Expected model filename after download.
|
|
261
|
+
* node-llama-cpp uses format: hf_{org}_{filename}
|
|
262
|
+
*/
|
|
263
|
+
declare const MODEL_FILENAME = "hf_ggml-org_embeddinggemma-300M-qat-Q4_0.gguf";
|
|
264
|
+
/**
|
|
265
|
+
* Check if the embedding model is available locally.
|
|
266
|
+
*
|
|
267
|
+
* @returns true if model file exists
|
|
268
|
+
*/
|
|
269
|
+
declare function isModelAvailable(): boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Resolve the embedding model path, downloading if necessary.
|
|
272
|
+
*
|
|
273
|
+
* Uses node-llama-cpp's resolveModelFile for automatic download with progress.
|
|
274
|
+
*
|
|
275
|
+
* @param options - Optional configuration
|
|
276
|
+
* @param options.cli - Show download progress in console (default: true)
|
|
277
|
+
* @returns Path to the resolved model file
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* const modelPath = await resolveModel();
|
|
282
|
+
* const llama = await getLlama();
|
|
283
|
+
* const model = await llama.loadModel({ modelPath });
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
declare function resolveModel(options?: {
|
|
287
|
+
cli?: boolean;
|
|
288
|
+
}): Promise<string>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Text embedding via node-llama-cpp with EmbeddingGemma model
|
|
292
|
+
*
|
|
293
|
+
* **Resource lifecycle:**
|
|
294
|
+
* - Model is loaded lazily on first embedding call (~150MB in memory)
|
|
295
|
+
* - Once loaded, the model remains in memory until `unloadEmbedding()` is called
|
|
296
|
+
* - Loading is slow (~1-3s); keeping loaded improves subsequent call performance
|
|
297
|
+
*
|
|
298
|
+
* **Memory usage:**
|
|
299
|
+
* - Embedding model: ~150MB RAM when loaded
|
|
300
|
+
* - Embeddings themselves: ~3KB per embedding (768 dimensions x 4 bytes)
|
|
301
|
+
*
|
|
302
|
+
* @see {@link unloadEmbedding} for releasing memory
|
|
303
|
+
* @see {@link getEmbedding} for the lazy-loading mechanism
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Get the LlamaEmbeddingContext instance for generating embeddings.
|
|
308
|
+
*
|
|
309
|
+
* **Lazy loading behavior:**
|
|
310
|
+
* - First call loads the embedding model (~150MB) into memory
|
|
311
|
+
* - Loading takes ~1-3 seconds depending on hardware
|
|
312
|
+
* - Subsequent calls return the cached instance immediately
|
|
313
|
+
* - Downloads model automatically if not present
|
|
314
|
+
*
|
|
315
|
+
* **Resource lifecycle:**
|
|
316
|
+
* - Once loaded, model stays in memory until `unloadEmbedding()` is called
|
|
317
|
+
* - For CLI commands: typically load once, use, then unload on exit
|
|
318
|
+
* - For long-running processes: keep loaded for performance
|
|
319
|
+
*
|
|
320
|
+
* @returns The singleton embedding context
|
|
321
|
+
* @throws Error if model download fails
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```typescript
|
|
325
|
+
* // Direct usage (prefer embedText for simple cases)
|
|
326
|
+
* const ctx = await getEmbedding();
|
|
327
|
+
* const result = await ctx.getEmbeddingFor('some text');
|
|
328
|
+
*
|
|
329
|
+
* // Ensure cleanup
|
|
330
|
+
* process.on('exit', () => unloadEmbedding());
|
|
331
|
+
* ```
|
|
332
|
+
*
|
|
333
|
+
* @see {@link embedText} for simpler text-to-vector conversion
|
|
334
|
+
* @see {@link unloadEmbedding} for releasing memory
|
|
335
|
+
*/
|
|
336
|
+
declare function getEmbedding(): Promise<LlamaEmbeddingContext>;
|
|
337
|
+
/**
|
|
338
|
+
* Unload the embedding context to free memory (~150MB).
|
|
339
|
+
*
|
|
340
|
+
* **Resource lifecycle:**
|
|
341
|
+
* - Disposes the underlying LlamaEmbeddingContext
|
|
342
|
+
* - Releases ~150MB of RAM used by the model
|
|
343
|
+
* - After unloading, subsequent embedding calls will reload the model
|
|
344
|
+
*
|
|
345
|
+
* **When to call:**
|
|
346
|
+
* - At the end of CLI commands to ensure clean process exit
|
|
347
|
+
* - In memory-constrained environments after batch processing
|
|
348
|
+
* - Before process exit in graceful shutdown handlers
|
|
349
|
+
* - When switching to a different model (if supported in future)
|
|
350
|
+
*
|
|
351
|
+
* **Best practices:**
|
|
352
|
+
* - For single-operation scripts: call before exit
|
|
353
|
+
* - For daemon/server processes: call in shutdown handler
|
|
354
|
+
* - Not needed between embedding calls in the same process
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* ```typescript
|
|
358
|
+
* // CLI command pattern
|
|
359
|
+
* try {
|
|
360
|
+
* const embedding = await embedText('some text');
|
|
361
|
+
* // ... use embedding
|
|
362
|
+
* } finally {
|
|
363
|
+
* unloadEmbedding();
|
|
364
|
+
* closeDb();
|
|
365
|
+
* }
|
|
366
|
+
*
|
|
367
|
+
* // Graceful shutdown pattern
|
|
368
|
+
* process.on('SIGTERM', () => {
|
|
369
|
+
* unloadEmbedding();
|
|
370
|
+
* closeDb();
|
|
371
|
+
* process.exit(0);
|
|
372
|
+
* });
|
|
373
|
+
* ```
|
|
374
|
+
*
|
|
375
|
+
* @see {@link getEmbedding} for loading the model
|
|
376
|
+
* @see {@link closeDb} for database cleanup (often used together)
|
|
377
|
+
*/
|
|
378
|
+
declare function unloadEmbedding(): void;
|
|
379
|
+
/**
|
|
380
|
+
* Embed a single text string into a vector.
|
|
381
|
+
*
|
|
382
|
+
* **Lazy loading:** First call loads the embedding model (~150MB, ~1-3s).
|
|
383
|
+
* Subsequent calls use the cached model and complete in milliseconds.
|
|
384
|
+
*
|
|
385
|
+
* @param text - The text to embed
|
|
386
|
+
* @returns A 768-dimensional vector (number[])
|
|
387
|
+
* @throws Error if model download fails
|
|
388
|
+
*
|
|
389
|
+
* @example
|
|
390
|
+
* ```typescript
|
|
391
|
+
* const vector = await embedText('TypeScript error handling');
|
|
392
|
+
* console.log(vector.length); // 768
|
|
393
|
+
*
|
|
394
|
+
* // Remember to clean up when done
|
|
395
|
+
* unloadEmbedding();
|
|
396
|
+
* ```
|
|
397
|
+
*
|
|
398
|
+
* @see {@link embedTexts} for batch embedding
|
|
399
|
+
* @see {@link unloadEmbedding} for releasing memory
|
|
400
|
+
*/
|
|
401
|
+
declare function embedText(text: string): Promise<number[]>;
|
|
402
|
+
/**
|
|
403
|
+
* Embed multiple texts into vectors.
|
|
404
|
+
*
|
|
405
|
+
* **Lazy loading:** First call loads the embedding model (~150MB, ~1-3s).
|
|
406
|
+
* Subsequent calls use the cached model.
|
|
407
|
+
*
|
|
408
|
+
* **Performance:** More efficient than calling `embedText` in a loop
|
|
409
|
+
* when processing multiple texts, as model loading happens only once.
|
|
410
|
+
*
|
|
411
|
+
* @param texts - Array of texts to embed
|
|
412
|
+
* @returns Array of 768-dimensional vectors, same order as input
|
|
413
|
+
* @throws Error if model download fails
|
|
414
|
+
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```typescript
|
|
417
|
+
* const texts = ['first text', 'second text'];
|
|
418
|
+
* const vectors = await embedTexts(texts);
|
|
419
|
+
* console.log(vectors.length); // 2
|
|
420
|
+
* console.log(vectors[0].length); // 768
|
|
421
|
+
*
|
|
422
|
+
* // Remember to clean up when done
|
|
423
|
+
* unloadEmbedding();
|
|
424
|
+
* ```
|
|
425
|
+
*
|
|
426
|
+
* @see {@link embedText} for single text embedding
|
|
427
|
+
* @see {@link unloadEmbedding} for releasing memory
|
|
428
|
+
*/
|
|
429
|
+
declare function embedTexts(texts: string[]): Promise<number[][]>;
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Vector search with cosine similarity
|
|
433
|
+
*
|
|
434
|
+
* Embeds query text and ranks lessons by semantic similarity.
|
|
435
|
+
* Uses SQLite cache to avoid recomputing embeddings.
|
|
436
|
+
*/
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Calculate cosine similarity between two vectors.
|
|
440
|
+
* Returns value between -1 (opposite) and 1 (identical).
|
|
441
|
+
*/
|
|
442
|
+
declare function cosineSimilarity(a: number[], b: number[]): number;
|
|
443
|
+
/** Lesson with similarity score */
|
|
444
|
+
interface ScoredLesson {
|
|
445
|
+
lesson: Lesson;
|
|
446
|
+
score: number;
|
|
447
|
+
}
|
|
448
|
+
/** Options for vector search */
|
|
449
|
+
interface SearchVectorOptions {
|
|
450
|
+
/** Maximum number of results to return (default: 10) */
|
|
451
|
+
limit?: number;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Search lessons by vector similarity to query text.
|
|
455
|
+
* Returns top N lessons sorted by similarity score (descending).
|
|
456
|
+
* Uses embedding cache to avoid recomputing embeddings.
|
|
457
|
+
*/
|
|
458
|
+
declare function searchVector(repoRoot: string, query: string, options?: SearchVectorOptions): Promise<ScoredLesson[]>;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Multi-factor lesson ranking system
|
|
462
|
+
*
|
|
463
|
+
* Combines vector similarity with semantic boosts:
|
|
464
|
+
* - Severity: high=1.5, medium=1.0, low=0.8
|
|
465
|
+
* - Recency: 1.2 for lessons ≤30 days old
|
|
466
|
+
* - Confirmation: 1.3 for confirmed lessons
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
/** Lesson with final ranked score */
|
|
470
|
+
interface RankedLesson extends ScoredLesson {
|
|
471
|
+
finalScore?: number;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Calculate severity boost based on lesson severity.
|
|
475
|
+
* Lessons without severity get 1.0 (medium boost).
|
|
476
|
+
*/
|
|
477
|
+
declare function severityBoost(lesson: Lesson): number;
|
|
478
|
+
/**
|
|
479
|
+
* Calculate recency boost based on lesson age.
|
|
480
|
+
* Lessons ≤30 days old get 1.2, older get 1.0.
|
|
481
|
+
*/
|
|
482
|
+
declare function recencyBoost(lesson: Lesson): number;
|
|
483
|
+
/**
|
|
484
|
+
* Calculate confirmation boost.
|
|
485
|
+
* Confirmed lessons get 1.3, unconfirmed get 1.0.
|
|
486
|
+
*/
|
|
487
|
+
declare function confirmationBoost(lesson: Lesson): number;
|
|
488
|
+
/**
|
|
489
|
+
* Calculate combined score for a lesson.
|
|
490
|
+
* score = vectorSimilarity * severity * recency * confirmation
|
|
491
|
+
*/
|
|
492
|
+
declare function calculateScore(lesson: Lesson, vectorSimilarity: number): number;
|
|
493
|
+
/**
|
|
494
|
+
* Rank lessons by combined score.
|
|
495
|
+
* Returns new array sorted by finalScore descending.
|
|
496
|
+
*/
|
|
497
|
+
declare function rankLessons(lessons: ScoredLesson[]): RankedLesson[];
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Quality filters for lesson capture
|
|
501
|
+
*
|
|
502
|
+
* Filters to ensure lessons are:
|
|
503
|
+
* - Novel (not duplicate)
|
|
504
|
+
* - Specific (not vague)
|
|
505
|
+
* - Actionable (contains action words)
|
|
506
|
+
*/
|
|
507
|
+
/** Result of novelty check */
|
|
508
|
+
interface NoveltyResult {
|
|
509
|
+
novel: boolean;
|
|
510
|
+
reason?: string;
|
|
511
|
+
existingId?: string;
|
|
512
|
+
}
|
|
513
|
+
/** Options for novelty check */
|
|
514
|
+
interface NoveltyOptions {
|
|
515
|
+
threshold?: number;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Check if an insight is novel (not a duplicate of existing lessons).
|
|
519
|
+
* Uses keyword search to find potentially similar lessons.
|
|
520
|
+
*/
|
|
521
|
+
declare function isNovel(repoRoot: string, insight: string, options?: NoveltyOptions): Promise<NoveltyResult>;
|
|
522
|
+
/** Result of specificity check */
|
|
523
|
+
interface SpecificityResult {
|
|
524
|
+
specific: boolean;
|
|
525
|
+
reason?: string;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Check if an insight is specific enough to be useful.
|
|
529
|
+
* Rejects vague, generic advice that doesn't provide actionable guidance.
|
|
530
|
+
*/
|
|
531
|
+
declare function isSpecific(insight: string): SpecificityResult;
|
|
532
|
+
/** Result of actionability check */
|
|
533
|
+
interface ActionabilityResult {
|
|
534
|
+
actionable: boolean;
|
|
535
|
+
reason?: string;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Check if an insight contains actionable guidance.
|
|
539
|
+
* Returns false for pure observations or questions.
|
|
540
|
+
*/
|
|
541
|
+
declare function isActionable(insight: string): ActionabilityResult;
|
|
542
|
+
/** Result of combined quality check */
|
|
543
|
+
interface ProposeResult {
|
|
544
|
+
shouldPropose: boolean;
|
|
545
|
+
reason?: string;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Combined quality check for lesson proposals.
|
|
549
|
+
* Returns true only if insight is novel, specific, AND actionable.
|
|
550
|
+
*/
|
|
551
|
+
declare function shouldPropose(repoRoot: string, insight: string): Promise<ProposeResult>;
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Trigger detection for automatic lesson capture
|
|
555
|
+
*
|
|
556
|
+
* Detects patterns that indicate potential learning opportunities:
|
|
557
|
+
* - User corrections
|
|
558
|
+
* - Self-corrections
|
|
559
|
+
* - Test failures
|
|
560
|
+
*/
|
|
561
|
+
|
|
562
|
+
/** Signal data for correction detection */
|
|
563
|
+
interface CorrectionSignal {
|
|
564
|
+
messages: string[];
|
|
565
|
+
context: Context;
|
|
566
|
+
}
|
|
567
|
+
/** Detected correction result */
|
|
568
|
+
interface DetectedCorrection {
|
|
569
|
+
trigger: string;
|
|
570
|
+
correctionMessage: string;
|
|
571
|
+
context: Context;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Detect user correction signals in conversation.
|
|
575
|
+
*
|
|
576
|
+
* Looks for patterns that indicate the user is correcting Claude's
|
|
577
|
+
* understanding or actions.
|
|
578
|
+
*
|
|
579
|
+
* @param signals - Messages and context to analyze
|
|
580
|
+
* @returns Detected correction or null if none found
|
|
581
|
+
*/
|
|
582
|
+
declare function detectUserCorrection(signals: CorrectionSignal): DetectedCorrection | null;
|
|
583
|
+
/** Edit history entry */
|
|
584
|
+
interface EditEntry {
|
|
585
|
+
file: string;
|
|
586
|
+
success: boolean;
|
|
587
|
+
timestamp: number;
|
|
588
|
+
}
|
|
589
|
+
/** Edit history for self-correction detection */
|
|
590
|
+
interface EditHistory {
|
|
591
|
+
edits: EditEntry[];
|
|
592
|
+
}
|
|
593
|
+
/** Detected self-correction */
|
|
594
|
+
interface DetectedSelfCorrection {
|
|
595
|
+
file: string;
|
|
596
|
+
trigger: string;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Detect self-correction patterns in edit history.
|
|
600
|
+
*
|
|
601
|
+
* Looks for edit→fail→re-edit patterns on the same file,
|
|
602
|
+
* which indicate Claude had to correct its own work.
|
|
603
|
+
*
|
|
604
|
+
* @param history - Edit history to analyze
|
|
605
|
+
* @returns Detected self-correction or null if none found
|
|
606
|
+
*/
|
|
607
|
+
declare function detectSelfCorrection(history: EditHistory): DetectedSelfCorrection | null;
|
|
608
|
+
/** Test result for failure detection */
|
|
609
|
+
interface TestResult {
|
|
610
|
+
passed: boolean;
|
|
611
|
+
output: string;
|
|
612
|
+
testFile: string;
|
|
613
|
+
}
|
|
614
|
+
/** Detected test failure */
|
|
615
|
+
interface DetectedTestFailure {
|
|
616
|
+
testFile: string;
|
|
617
|
+
errorOutput: string;
|
|
618
|
+
trigger: string;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Detect test failure patterns.
|
|
622
|
+
*
|
|
623
|
+
* When tests fail, this creates a potential learning opportunity
|
|
624
|
+
* if the failure is later fixed.
|
|
625
|
+
*
|
|
626
|
+
* @param testResult - Test result to analyze
|
|
627
|
+
* @returns Detected test failure or null if tests passed
|
|
628
|
+
*/
|
|
629
|
+
declare function detectTestFailure(testResult: TestResult): DetectedTestFailure | null;
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Session-start lesson retrieval
|
|
633
|
+
*
|
|
634
|
+
* Loads high-severity lessons at the start of a session.
|
|
635
|
+
* No vector search - just filter by severity and recency.
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
/** A full lesson with severity field present */
|
|
639
|
+
type FullLesson = Lesson & {
|
|
640
|
+
type: 'full';
|
|
641
|
+
severity: Severity;
|
|
642
|
+
};
|
|
643
|
+
/**
|
|
644
|
+
* Load high-severity lessons for session start.
|
|
645
|
+
*
|
|
646
|
+
* Returns confirmed, high-severity lessons sorted by recency.
|
|
647
|
+
* These are the most important lessons to surface at the start
|
|
648
|
+
* of a coding session.
|
|
649
|
+
*
|
|
650
|
+
* @param repoRoot - Repository root directory
|
|
651
|
+
* @param limit - Maximum number of lessons to return (default: 5)
|
|
652
|
+
* @returns Array of high-severity lessons, most recent first
|
|
653
|
+
*/
|
|
654
|
+
declare function loadSessionLessons(repoRoot: string, limit?: number): Promise<FullLesson[]>;
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Plan-time lesson retrieval
|
|
658
|
+
*
|
|
659
|
+
* Retrieves relevant lessons when planning an implementation.
|
|
660
|
+
* Uses vector search to find semantically similar lessons.
|
|
661
|
+
*/
|
|
662
|
+
|
|
663
|
+
/** Result of plan-time retrieval */
|
|
664
|
+
interface PlanRetrievalResult {
|
|
665
|
+
lessons: RankedLesson[];
|
|
666
|
+
message: string;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Retrieve relevant lessons for a plan.
|
|
670
|
+
*
|
|
671
|
+
* Uses vector search to find semantically similar lessons,
|
|
672
|
+
* then applies ranking boosts for severity, recency, and confirmation.
|
|
673
|
+
*
|
|
674
|
+
* Hard-fails if embeddings are unavailable (propagates error from embedText).
|
|
675
|
+
*
|
|
676
|
+
* @param repoRoot - Repository root directory
|
|
677
|
+
* @param planText - The plan text to search against
|
|
678
|
+
* @param limit - Maximum number of lessons to return (default: 5)
|
|
679
|
+
* @returns Ranked lessons and formatted message
|
|
680
|
+
*/
|
|
681
|
+
declare function retrieveForPlan(repoRoot: string, planText: string, limit?: number): Promise<PlanRetrievalResult>;
|
|
682
|
+
/**
|
|
683
|
+
* Format a "Lessons Check" message for display.
|
|
684
|
+
*
|
|
685
|
+
* This message is intended to be shown at plan-time to remind
|
|
686
|
+
* the developer of relevant lessons before implementation.
|
|
687
|
+
*
|
|
688
|
+
* @param lessons - Ranked lessons to include in the message
|
|
689
|
+
* @returns Formatted message string
|
|
690
|
+
*/
|
|
691
|
+
declare function formatLessonsCheck(lessons: ScoredLesson[]): string;
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Learning Agent - Repository-scoped learning system for Claude Code
|
|
695
|
+
*
|
|
696
|
+
* This package helps Claude Code learn from mistakes and avoid repeating them.
|
|
697
|
+
* It captures lessons during coding sessions and retrieves relevant lessons
|
|
698
|
+
* when planning new work.
|
|
699
|
+
*
|
|
700
|
+
* ## Quick Start
|
|
701
|
+
*
|
|
702
|
+
* ```typescript
|
|
703
|
+
* import { appendLesson, retrieveForPlan, loadSessionLessons } from 'learning-agent';
|
|
704
|
+
*
|
|
705
|
+
* // At session start, load high-severity lessons
|
|
706
|
+
* const criticalLessons = await loadSessionLessons(repoRoot);
|
|
707
|
+
*
|
|
708
|
+
* // When planning, retrieve relevant lessons
|
|
709
|
+
* const { lessons, message } = await retrieveForPlan(repoRoot, planText);
|
|
710
|
+
*
|
|
711
|
+
* // When capturing a lesson
|
|
712
|
+
* await appendLesson(repoRoot, lesson);
|
|
713
|
+
* ```
|
|
714
|
+
*
|
|
715
|
+
* ## Hook Integration
|
|
716
|
+
*
|
|
717
|
+
* Add to your `.claude/settings.json`:
|
|
718
|
+
*
|
|
719
|
+
* ```json
|
|
720
|
+
* {
|
|
721
|
+
* "hooks": {
|
|
722
|
+
* "session_start": "npx learning-agent load-session",
|
|
723
|
+
* "pre_tool": "npx learning-agent check-plan"
|
|
724
|
+
* }
|
|
725
|
+
* }
|
|
726
|
+
* ```
|
|
727
|
+
*
|
|
728
|
+
* ## Resource Management
|
|
729
|
+
*
|
|
730
|
+
* This library manages two heavyweight resources that require cleanup:
|
|
731
|
+
*
|
|
732
|
+
* ### SQLite Database
|
|
733
|
+
* - **Acquired:** Lazily on first database operation (search, rebuild, etc.)
|
|
734
|
+
* - **Memory:** Minimal (~few KB for connection, index cached by OS)
|
|
735
|
+
* - **Cleanup:** Call `closeDb()` before process exit
|
|
736
|
+
*
|
|
737
|
+
* ### Embedding Model
|
|
738
|
+
* - **Acquired:** Lazily on first embedding call (embedText, embedTexts, searchVector)
|
|
739
|
+
* - **Memory:** ~150MB RAM for the EmbeddingGemma model
|
|
740
|
+
* - **Cleanup:** Call `unloadEmbedding()` before process exit
|
|
741
|
+
*
|
|
742
|
+
* ### Recommended Cleanup Pattern
|
|
743
|
+
*
|
|
744
|
+
* ```typescript
|
|
745
|
+
* import { closeDb, unloadEmbedding } from 'learning-agent';
|
|
746
|
+
*
|
|
747
|
+
* // For CLI commands - use try/finally
|
|
748
|
+
* async function main() {
|
|
749
|
+
* try {
|
|
750
|
+
* // ... your code that uses learning-agent
|
|
751
|
+
* } finally {
|
|
752
|
+
* unloadEmbedding();
|
|
753
|
+
* closeDb();
|
|
754
|
+
* }
|
|
755
|
+
* }
|
|
756
|
+
*
|
|
757
|
+
* // For long-running processes - use shutdown handlers
|
|
758
|
+
* process.on('SIGTERM', () => {
|
|
759
|
+
* unloadEmbedding();
|
|
760
|
+
* closeDb();
|
|
761
|
+
* process.exit(0);
|
|
762
|
+
* });
|
|
763
|
+
* process.on('SIGINT', () => {
|
|
764
|
+
* unloadEmbedding();
|
|
765
|
+
* closeDb();
|
|
766
|
+
* process.exit(0);
|
|
767
|
+
* });
|
|
768
|
+
* ```
|
|
769
|
+
*
|
|
770
|
+
* **Note:** Failing to clean up will not corrupt data, but may cause:
|
|
771
|
+
* - Memory leaks in long-running processes
|
|
772
|
+
* - Unclean process exits (warnings in some environments)
|
|
773
|
+
*
|
|
774
|
+
* @see {@link closeDb} for database cleanup
|
|
775
|
+
* @see {@link unloadEmbedding} for embedding model cleanup
|
|
776
|
+
* @module learning-agent
|
|
777
|
+
*/
|
|
778
|
+
declare const VERSION = "0.1.0";
|
|
779
|
+
|
|
780
|
+
export { type ActionabilityResult, type Context, type CorrectionSignal, DB_PATH, type DetectedCorrection, type DetectedSelfCorrection, type DetectedTestFailure, type EditEntry, type EditHistory, LESSONS_PATH, type Lesson, LessonSchema, type LessonType, LessonTypeSchema, MODEL_FILENAME, MODEL_URI, type NoveltyOptions, type NoveltyResult, type ParseError, type PlanRetrievalResult, type ProposeResult, type RankedLesson, type ReadLessonsOptions, type ReadLessonsResult, type ScoredLesson, type SearchVectorOptions, type Severity, type Source, type SpecificityResult, type TestResult, type Tombstone, TombstoneSchema, VERSION, appendLesson, calculateScore, closeDb, confirmationBoost, cosineSimilarity, detectSelfCorrection, detectTestFailure, detectUserCorrection, embedText, embedTexts, formatLessonsCheck, generateId, getEmbedding, isActionable, isModelAvailable, isNovel, isSpecific, loadSessionLessons, rankLessons, readLessons, rebuildIndex, recencyBoost, resolveModel, retrieveForPlan, searchKeyword, searchVector, severityBoost, shouldPropose, unloadEmbedding };
|