@ulpi/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/auth-PN7TMQHV-2W4ICG64.js +15 -0
  4. package/dist/chunk-247GVVKK.js +2259 -0
  5. package/dist/chunk-2CLNOKPA.js +793 -0
  6. package/dist/chunk-2HEE5OKX.js +79 -0
  7. package/dist/chunk-2MZER6ND.js +415 -0
  8. package/dist/chunk-3SBPZRB5.js +772 -0
  9. package/dist/chunk-4VNS5WPM.js +42 -0
  10. package/dist/chunk-6JCMYYBT.js +1546 -0
  11. package/dist/chunk-6OCEY7JY.js +422 -0
  12. package/dist/chunk-74WVVWJ4.js +375 -0
  13. package/dist/chunk-7AL4DOEJ.js +131 -0
  14. package/dist/chunk-7LXY5UVC.js +330 -0
  15. package/dist/chunk-DBMUNBNB.js +3048 -0
  16. package/dist/chunk-JWUUVXIV.js +13694 -0
  17. package/dist/chunk-KIKPIH6N.js +4048 -0
  18. package/dist/chunk-KLEASXUR.js +70 -0
  19. package/dist/chunk-MIAQVCFW.js +39 -0
  20. package/dist/chunk-NNUWU6CV.js +1610 -0
  21. package/dist/chunk-PKD4ASEM.js +115 -0
  22. package/dist/chunk-Q4HIY43N.js +4230 -0
  23. package/dist/chunk-QJ5GSMEC.js +146 -0
  24. package/dist/chunk-SIAQVRKG.js +2163 -0
  25. package/dist/chunk-SPOI23SB.js +197 -0
  26. package/dist/chunk-YM2HV4IA.js +505 -0
  27. package/dist/codemap-RRJIDBQ5.js +636 -0
  28. package/dist/config-EGAXXCGL.js +127 -0
  29. package/dist/dist-6G7JC2RA.js +90 -0
  30. package/dist/dist-7LHZ65GC.js +418 -0
  31. package/dist/dist-LZKZFPVX.js +140 -0
  32. package/dist/dist-R5F4MX3I.js +107 -0
  33. package/dist/dist-R5ZJ4LX5.js +56 -0
  34. package/dist/dist-RJGCUS3L.js +87 -0
  35. package/dist/dist-RKOGLK7R.js +151 -0
  36. package/dist/dist-W7K4WPAF.js +597 -0
  37. package/dist/export-import-4A5MWLIA.js +53 -0
  38. package/dist/history-ATTUKOHO.js +934 -0
  39. package/dist/index.js +2120 -0
  40. package/dist/init-AY5C2ZAS.js +393 -0
  41. package/dist/launchd-LF2QMSKZ.js +148 -0
  42. package/dist/log-TVTUXAYD.js +75 -0
  43. package/dist/mcp-installer-NQCGKQ23.js +124 -0
  44. package/dist/memory-J3G24QHS.js +406 -0
  45. package/dist/ollama-3XCUZMZT-FYKHW4TZ.js +7 -0
  46. package/dist/openai-E7G2YAHU-UYY4ZWON.js +8 -0
  47. package/dist/projects-ATHDD3D6.js +271 -0
  48. package/dist/review-ADUPV3PN.js +152 -0
  49. package/dist/rules-E427DKYJ.js +134 -0
  50. package/dist/server-MOYPE4SM-N7SE2AN7.js +18 -0
  51. package/dist/server-X5P6WH2M-7K2RY34N.js +11 -0
  52. package/dist/skills/ulpi-generate-guardian/SKILL.md +511 -0
  53. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +692 -0
  54. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +596 -0
  55. package/dist/skills-CX73O3IV.js +76 -0
  56. package/dist/status-4DFHDJMN.js +66 -0
  57. package/dist/templates/biome.yml +24 -0
  58. package/dist/templates/conventional-commits.yml +18 -0
  59. package/dist/templates/django.yml +30 -0
  60. package/dist/templates/docker.yml +30 -0
  61. package/dist/templates/eslint.yml +13 -0
  62. package/dist/templates/express.yml +20 -0
  63. package/dist/templates/fastapi.yml +23 -0
  64. package/dist/templates/git-flow.yml +26 -0
  65. package/dist/templates/github-flow.yml +27 -0
  66. package/dist/templates/go.yml +33 -0
  67. package/dist/templates/jest.yml +24 -0
  68. package/dist/templates/laravel.yml +30 -0
  69. package/dist/templates/monorepo.yml +26 -0
  70. package/dist/templates/nestjs.yml +21 -0
  71. package/dist/templates/nextjs.yml +31 -0
  72. package/dist/templates/nodejs.yml +33 -0
  73. package/dist/templates/npm.yml +15 -0
  74. package/dist/templates/php.yml +25 -0
  75. package/dist/templates/pnpm.yml +15 -0
  76. package/dist/templates/prettier.yml +23 -0
  77. package/dist/templates/prisma.yml +21 -0
  78. package/dist/templates/python.yml +33 -0
  79. package/dist/templates/quality-of-life.yml +111 -0
  80. package/dist/templates/ruby.yml +25 -0
  81. package/dist/templates/rust.yml +34 -0
  82. package/dist/templates/typescript.yml +14 -0
  83. package/dist/templates/vitest.yml +24 -0
  84. package/dist/templates/yarn.yml +15 -0
  85. package/dist/templates-U7T6MARD.js +156 -0
  86. package/dist/ui-L7UAWXDY.js +167 -0
  87. package/dist/ui.html +698 -0
  88. package/dist/ulpi-RMMCUAGP-JCJ273T6.js +161 -0
  89. package/dist/uninstall-6SW35IK4.js +25 -0
  90. package/dist/update-M2B4RLGH.js +61 -0
  91. package/dist/version-checker-ANCS3IHR.js +10 -0
  92. package/package.json +92 -0
@@ -0,0 +1,1546 @@
1
+ import {
2
+ createEmbedder
3
+ } from "./chunk-DBMUNBNB.js";
4
+ import {
5
+ readEvents
6
+ } from "./chunk-YM2HV4IA.js";
7
+ import {
8
+ ClassificationResultSchema,
9
+ MemoryConfigSchema
10
+ } from "./chunk-74WVVWJ4.js";
11
+ import {
12
+ getMemoryBranch,
13
+ memoryConfigFile,
14
+ memoryEntriesDir,
15
+ memoryHistoryDir,
16
+ memoryLanceDir,
17
+ memoryStatsFile,
18
+ memoryWatermarksDir,
19
+ projectMemoryDir
20
+ } from "./chunk-7LXY5UVC.js";
21
+
22
+ // ../../packages/memory-engine/dist/index.js
23
+ import * as fs from "fs";
24
+ import * as path from "path";
25
+ import * as fs2 from "fs";
26
+ import * as path2 from "path";
27
+ import { createHash } from "crypto";
28
+ import { connect } from "@lancedb/lancedb";
29
+ import * as fs3 from "fs";
30
+ import * as path3 from "path";
31
+ import { execFileSync, spawn } from "child_process";
32
+ import * as fs4 from "fs";
33
+ import * as os from "os";
34
+ import * as path4 from "path";
35
+ import * as fs5 from "fs";
36
+ import * as os2 from "os";
37
+ import * as path5 from "path";
38
+ import { createHash as createHash2 } from "crypto";
39
+ import * as fs7 from "fs";
40
+ import * as path7 from "path";
41
+ import * as fs6 from "fs";
42
+ import * as path6 from "path";
43
+ import * as fs8 from "fs";
44
+ import * as path8 from "path";
45
+ import { execFileSync as execFileSync2 } from "child_process";
46
+ import * as fs9 from "fs";
47
+ import * as os3 from "os";
48
+ import * as path9 from "path";
49
+ var DEFAULT_MEMORY_CONFIG = MemoryConfigSchema.parse({});
50
+ function loadMemoryConfig(projectDir) {
51
+ const configPath = memoryConfigFile(projectDir);
52
+ if (!fs.existsSync(configPath)) {
53
+ return DEFAULT_MEMORY_CONFIG;
54
+ }
55
+ try {
56
+ const raw = fs.readFileSync(configPath, "utf-8");
57
+ const parsed = JSON.parse(raw);
58
+ return MemoryConfigSchema.parse(parsed);
59
+ } catch {
60
+ return DEFAULT_MEMORY_CONFIG;
61
+ }
62
+ }
63
+ function saveMemoryConfig(projectDir, config) {
64
+ const configPath = memoryConfigFile(projectDir);
65
+ const dir = path.dirname(configPath);
66
+ fs.mkdirSync(dir, { recursive: true });
67
+ const tmpPath = configPath + ".tmp";
68
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
69
+ fs.renameSync(tmpPath, configPath);
70
+ }
71
+ function isMemoryEnabled(projectDir) {
72
+ const config = loadMemoryConfig(projectDir);
73
+ return config.enabled;
74
+ }
75
+ function generateMemoryId(type, content) {
76
+ return createHash("sha256").update(`${type}:${content}`).digest("hex").slice(0, 32);
77
+ }
78
+ function walkDirSize(dir) {
79
+ if (!fs2.existsSync(dir)) return 0;
80
+ let total = 0;
81
+ const entries = fs2.readdirSync(dir);
82
+ for (const entry of entries) {
83
+ const fullPath = path2.join(dir, entry);
84
+ try {
85
+ const stat = fs2.statSync(fullPath);
86
+ if (stat.isDirectory()) {
87
+ total += walkDirSize(fullPath);
88
+ } else {
89
+ total += stat.size;
90
+ }
91
+ } catch {
92
+ }
93
+ }
94
+ return total;
95
+ }
96
+ var MemoryVectorStore = class {
97
+ db = null;
98
+ table = null;
99
+ indexDir;
100
+ constructor(projectDir) {
101
+ this.indexDir = memoryLanceDir(projectDir);
102
+ }
103
+ async initialize() {
104
+ fs2.mkdirSync(this.indexDir, { recursive: true });
105
+ this.db = await connect(this.indexDir);
106
+ const tables = await this.db.tableNames();
107
+ if (tables.includes("memories")) {
108
+ this.table = await this.db.openTable("memories");
109
+ }
110
+ }
111
+ async upsertItems(items) {
112
+ if (items.length === 0) return;
113
+ const records = items.map((item) => ({
114
+ id: item.id,
115
+ vector: item.vector,
116
+ type: item.metadata.type,
117
+ importance: item.metadata.importance,
118
+ snippet: item.metadata.snippet,
119
+ createdAt: item.metadata.createdAt,
120
+ tags: item.metadata.tags
121
+ }));
122
+ if (!this.table) {
123
+ this.table = await this.db.createTable("memories", records);
124
+ } else {
125
+ await this.table.mergeInsert("id").whenMatchedUpdateAll().whenNotMatchedInsertAll().execute(records);
126
+ }
127
+ }
128
+ async removeItems(ids) {
129
+ if (!this.table || ids.length === 0) return;
130
+ const quoted = ids.map((id) => `'${id.replace(/'/g, "''")}'`).join(", ");
131
+ await this.table.delete(`id IN (${quoted})`);
132
+ }
133
+ async query(vector, topK = 10) {
134
+ if (!this.table) return [];
135
+ const results = await this.table.vectorSearch(vector).distanceType("cosine").limit(topK).toArray();
136
+ return results.map((r) => ({
137
+ id: r.id,
138
+ type: r.type,
139
+ score: 1 - (r._distance ?? 0),
140
+ snippet: r.snippet,
141
+ importance: r.importance
142
+ }));
143
+ }
144
+ async getItemCount() {
145
+ if (!this.table) return 0;
146
+ return this.table.countRows();
147
+ }
148
+ getIndexSizeBytes() {
149
+ return walkDirSize(this.indexDir);
150
+ }
151
+ };
152
+ function saveEntry(projectDir, entry) {
153
+ const dir = memoryEntriesDir(projectDir);
154
+ fs2.mkdirSync(dir, { recursive: true });
155
+ const filePath = path2.join(dir, `${entry.id}.json`);
156
+ const tmpPath = filePath + ".tmp";
157
+ fs2.writeFileSync(tmpPath, JSON.stringify(entry, null, 2) + "\n", "utf-8");
158
+ fs2.renameSync(tmpPath, filePath);
159
+ }
160
+ function loadEntry(projectDir, id) {
161
+ const filePath = path2.join(memoryEntriesDir(projectDir), `${id}.json`);
162
+ if (!fs2.existsSync(filePath)) return null;
163
+ try {
164
+ return JSON.parse(fs2.readFileSync(filePath, "utf-8"));
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ function removeEntry(projectDir, id) {
170
+ const filePath = path2.join(memoryEntriesDir(projectDir), `${id}.json`);
171
+ try {
172
+ fs2.unlinkSync(filePath);
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+ function listEntries(projectDir) {
179
+ const dir = memoryEntriesDir(projectDir);
180
+ if (!fs2.existsSync(dir)) return [];
181
+ const entries = [];
182
+ const files = fs2.readdirSync(dir).filter((f) => f.endsWith(".json"));
183
+ for (const file of files) {
184
+ try {
185
+ const raw = fs2.readFileSync(path2.join(dir, file), "utf-8");
186
+ entries.push(JSON.parse(raw));
187
+ } catch {
188
+ }
189
+ }
190
+ return entries;
191
+ }
192
+ function updateEntry(projectDir, id, updates) {
193
+ const entry = loadEntry(projectDir, id);
194
+ if (!entry) return null;
195
+ const updated = { ...entry, ...updates, id: entry.id };
196
+ saveEntry(projectDir, updated);
197
+ return updated;
198
+ }
199
+ var DEFAULT_PATTERNS = [
200
+ // API keys
201
+ /(?:api[_-]?key|apikey|secret[_-]?key)\s*[:=]\s*["']?[A-Za-z0-9_\-]{20,}["']?/gi,
202
+ // Bearer tokens
203
+ /Bearer\s+[A-Za-z0-9_\-.]{20,}/g,
204
+ // AWS keys
205
+ /AKIA[A-Z0-9]{16}/g,
206
+ // Generic secrets in env format
207
+ /(?:PASSWORD|SECRET|TOKEN|PRIVATE_KEY)\s*=\s*\S+/gi,
208
+ // Base64 encoded long values that look like secrets
209
+ /(?:eyJ[A-Za-z0-9_-]{50,})/g
210
+ ];
211
+ function redactContent(text, userPatterns = []) {
212
+ let result = text;
213
+ for (const pattern of DEFAULT_PATTERNS) {
214
+ result = result.replace(pattern, "[REDACTED]");
215
+ }
216
+ for (const patternStr of userPatterns) {
217
+ try {
218
+ const pattern = new RegExp(patternStr, "gi");
219
+ result = result.replace(pattern, "[REDACTED]");
220
+ } catch {
221
+ }
222
+ }
223
+ return result;
224
+ }
225
+ var MAX_TRANSCRIPT_SIZE = 10485760;
226
+ function sessionCaptureDir(projectDir, sessionId) {
227
+ return path3.join(memoryHistoryDir(projectDir), `sess_${sessionId}`);
228
+ }
229
+ function appendMemoryEvent(sessionId, event, projectDir) {
230
+ const dir = sessionCaptureDir(projectDir, sessionId);
231
+ fs3.mkdirSync(dir, { recursive: true });
232
+ const logPath = path3.join(dir, "events.jsonl");
233
+ fs3.appendFileSync(logPath, JSON.stringify(event) + "\n", "utf-8");
234
+ }
235
+ function toClassificationEvent(ev) {
236
+ return {
237
+ ts: ev.ts,
238
+ event: ev.event,
239
+ hookEvent: ev.hookEvent,
240
+ toolName: ev.toolName,
241
+ filePath: ev.filePath,
242
+ command: ev.command,
243
+ message: ev.message
244
+ };
245
+ }
246
+ function finalizeCapture(sessionId, state, projectDir) {
247
+ const config = loadMemoryConfig(projectDir);
248
+ const dir = sessionCaptureDir(projectDir, sessionId);
249
+ fs3.mkdirSync(dir, { recursive: true });
250
+ const redactPatterns = config.redactPatterns;
251
+ if (config.captureMode === "end_of_session") {
252
+ bulkCaptureFromSessionLog(sessionId, projectDir, redactPatterns);
253
+ }
254
+ let transcriptCaptured = false;
255
+ if (state.transcriptPath) {
256
+ transcriptCaptured = copyTranscript(
257
+ state.transcriptPath,
258
+ path3.join(dir, "transcript.jsonl"),
259
+ redactPatterns
260
+ );
261
+ }
262
+ const meta = {
263
+ sessionId,
264
+ projectDir,
265
+ startedAt: state.startedAt,
266
+ endedAt: (/* @__PURE__ */ new Date()).toISOString(),
267
+ branch: state.branch,
268
+ sessionName: state.sessionName,
269
+ filesRead: state.filesRead.length,
270
+ filesWritten: state.filesWritten.length,
271
+ commandsRun: state.commandsRun.length,
272
+ transcriptCaptured,
273
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
274
+ };
275
+ const metaPath = path3.join(dir, "meta.json");
276
+ fs3.writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\n", "utf-8");
277
+ }
278
+ function bulkCaptureFromSessionLog(sessionId, projectDir, redactPatterns) {
279
+ const events = readEvents(sessionId, projectDir);
280
+ if (events.length === 0) return;
281
+ const dir = sessionCaptureDir(projectDir, sessionId);
282
+ const logPath = path3.join(dir, "events.jsonl");
283
+ const lines = events.map((ev) => {
284
+ const ce = toClassificationEvent(ev);
285
+ if (ce.command && redactPatterns.length > 0) {
286
+ ce.command = redactContent(ce.command, redactPatterns);
287
+ }
288
+ if (ce.message && redactPatterns.length > 0) {
289
+ ce.message = redactContent(ce.message, redactPatterns);
290
+ }
291
+ return JSON.stringify(ce);
292
+ });
293
+ fs3.writeFileSync(logPath, lines.join("\n") + "\n", "utf-8");
294
+ }
295
+ function copyTranscript(sourcePath, destPath, redactPatterns) {
296
+ try {
297
+ const stat = fs3.statSync(sourcePath);
298
+ if (stat.size > MAX_TRANSCRIPT_SIZE) {
299
+ const fd = fs3.openSync(sourcePath, "r");
300
+ const buffer = Buffer.alloc(MAX_TRANSCRIPT_SIZE);
301
+ fs3.readSync(fd, buffer, 0, MAX_TRANSCRIPT_SIZE, 0);
302
+ fs3.closeSync(fd);
303
+ let content = buffer.toString("utf-8");
304
+ if (redactPatterns.length > 0) {
305
+ content = redactContent(content, redactPatterns);
306
+ }
307
+ fs3.writeFileSync(destPath, content, "utf-8");
308
+ } else {
309
+ let content = fs3.readFileSync(sourcePath, "utf-8");
310
+ if (redactPatterns.length > 0) {
311
+ content = redactContent(content, redactPatterns);
312
+ }
313
+ fs3.writeFileSync(destPath, content, "utf-8");
314
+ }
315
+ return true;
316
+ } catch {
317
+ return false;
318
+ }
319
+ }
320
+ function isSessionCaptured(projectDir, sessionId) {
321
+ const metaPath = path3.join(sessionCaptureDir(projectDir, sessionId), "meta.json");
322
+ return fs3.existsSync(metaPath);
323
+ }
324
+ function listCapturedSessions(projectDir) {
325
+ const dir = memoryHistoryDir(projectDir);
326
+ if (!fs3.existsSync(dir)) return [];
327
+ const sessions = [];
328
+ const dirs = fs3.readdirSync(dir).filter((d) => d.startsWith("sess_"));
329
+ for (const d of dirs) {
330
+ const metaPath = path3.join(dir, d, "meta.json");
331
+ try {
332
+ const raw = fs3.readFileSync(metaPath, "utf-8");
333
+ sessions.push(JSON.parse(raw));
334
+ } catch {
335
+ }
336
+ }
337
+ return sessions.sort((a, b) => a.capturedAt.localeCompare(b.capturedAt));
338
+ }
339
+ function readCapturedEvents(projectDir, sessionId) {
340
+ const logPath = path3.join(sessionCaptureDir(projectDir, sessionId), "events.jsonl");
341
+ try {
342
+ const raw = fs3.readFileSync(logPath, "utf-8");
343
+ const events = [];
344
+ for (const line of raw.split("\n")) {
345
+ const trimmed = line.trim();
346
+ if (!trimmed) continue;
347
+ try {
348
+ events.push(JSON.parse(trimmed));
349
+ } catch {
350
+ }
351
+ }
352
+ return events;
353
+ } catch {
354
+ return [];
355
+ }
356
+ }
357
+ function readCapturedTranscript(projectDir, sessionId, maxChars = 1e4) {
358
+ const transcriptPath = path3.join(
359
+ sessionCaptureDir(projectDir, sessionId),
360
+ "transcript.jsonl"
361
+ );
362
+ try {
363
+ const content = fs3.readFileSync(transcriptPath, "utf-8");
364
+ return content.length > maxChars ? content.slice(0, maxChars) : content;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+ function resolveClaudePath() {
370
+ try {
371
+ const result = execFileSync("which", ["claude"], { stdio: "pipe", timeout: 3e3 }).toString().trim();
372
+ if (result) return result;
373
+ } catch {
374
+ }
375
+ const home = os.homedir();
376
+ const candidates = [
377
+ path4.join(home, ".local", "bin", "claude"),
378
+ path4.join(home, ".claude", "bin", "claude"),
379
+ "/usr/local/bin/claude"
380
+ ];
381
+ for (const p of candidates) {
382
+ try {
383
+ if (fs4.existsSync(p)) return p;
384
+ } catch {
385
+ }
386
+ }
387
+ return null;
388
+ }
389
+ var cachedClaudePath;
390
+ function getClaudePath() {
391
+ if (cachedClaudePath === void 0) {
392
+ cachedClaudePath = resolveClaudePath();
393
+ }
394
+ return cachedClaudePath;
395
+ }
396
+ var TRIVIAL_COMMANDS = /* @__PURE__ */ new Set([
397
+ "ls",
398
+ "pwd",
399
+ "echo",
400
+ "cat",
401
+ "head",
402
+ "tail",
403
+ "which",
404
+ "cd",
405
+ "whoami",
406
+ "date",
407
+ "wc"
408
+ ]);
409
+ function filterSignificantEvents(events) {
410
+ return events.filter((ev) => {
411
+ if (ev.event === "tool_blocked" || ev.event === "permission_denied" || ev.event === "postcondition_failed") {
412
+ return true;
413
+ }
414
+ if (ev.event === "tool_used" && ev.toolName === "Read") {
415
+ return false;
416
+ }
417
+ if (ev.toolName === "Bash" && ev.command) {
418
+ const cmd = ev.command.trim().split(/\s+/)[0];
419
+ if (TRIVIAL_COMMANDS.has(cmd)) return false;
420
+ }
421
+ return true;
422
+ });
423
+ }
424
+ function buildClassificationWindows(sessionId, projectDir, events, config) {
425
+ if (events.length === 0) return [];
426
+ const windowSize = config.classifier.windowSize;
427
+ const step = windowSize;
428
+ const windows = [];
429
+ for (let i = 0; i < events.length; i += step) {
430
+ const windowEvents = events.slice(i, i + windowSize);
431
+ if (windowEvents.length === 0) break;
432
+ let transcriptExcerpt;
433
+ if (i === 0 && config.classifier.includeTranscript) {
434
+ transcriptExcerpt = readCapturedTranscript(
435
+ projectDir,
436
+ sessionId,
437
+ config.classifier.maxTranscriptChars
438
+ ) ?? void 0;
439
+ }
440
+ windows.push({
441
+ sessionId,
442
+ projectDir,
443
+ events: windowEvents,
444
+ transcriptExcerpt,
445
+ startTs: windowEvents[0].ts,
446
+ endTs: windowEvents[windowEvents.length - 1].ts
447
+ });
448
+ }
449
+ return windows;
450
+ }
451
+ var CLASSIFIER_SCHEMA = JSON.stringify({
452
+ type: "object",
453
+ properties: {
454
+ memories: {
455
+ type: "array",
456
+ items: {
457
+ type: "object",
458
+ properties: {
459
+ type: {
460
+ type: "string",
461
+ enum: ["DECISION", "PATTERN", "BUG_ROOT_CAUSE", "PREFERENCE", "CONSTRAINT", "CONTEXT", "LESSON", "RELATIONSHIP"]
462
+ },
463
+ summary: { type: "string", description: "Concise memory text (max 2000 chars)" },
464
+ detail: { type: "string", description: "Additional context (optional)" },
465
+ importance: { type: "string", enum: ["critical", "high", "medium", "low"] },
466
+ tags: { type: "array", items: { type: "string" }, description: "Categorization tags" },
467
+ relatedFiles: { type: "array", items: { type: "string" }, description: "Related file paths" }
468
+ },
469
+ required: ["type", "summary", "importance"]
470
+ }
471
+ }
472
+ },
473
+ required: ["memories"]
474
+ });
475
+ function buildPromptPreamble(maxMemories) {
476
+ return [
477
+ "You are extracting structured memories from an AI coding agent's session.",
478
+ 'A "memory" is knowledge worth retaining for future sessions.',
479
+ "",
480
+ `Extract 0-${maxMemories} memories from the following session events.`,
481
+ "Only extract genuinely useful, specific information. Skip generic observations.",
482
+ "",
483
+ "Memory types:",
484
+ "- DECISION: Architecture/design decisions with rationale",
485
+ "- PATTERN: Code patterns adopted or enforced in this project",
486
+ "- BUG_ROOT_CAUSE: Root causes of bugs (not symptoms)",
487
+ "- PREFERENCE: User preferences for code style, tools, workflow",
488
+ "- CONSTRAINT: Project constraints (performance budgets, compatibility)",
489
+ "- CONTEXT: Domain knowledge, terminology, relationships between concepts",
490
+ "- LESSON: Lessons learned from mistakes or difficulties",
491
+ "- RELATIONSHIP: How components/modules/files relate to each other",
492
+ ""
493
+ ];
494
+ }
495
+ function formatEventsSection(events) {
496
+ const lines = ["## Session Events"];
497
+ for (const ev of events) {
498
+ const parts = [ev.ts, ev.event, ev.hookEvent];
499
+ if (ev.toolName) parts.push(`tool=${ev.toolName}`);
500
+ if (ev.filePath) parts.push(`file=${ev.filePath}`);
501
+ if (ev.command) parts.push(`cmd=${ev.command.slice(0, 200)}`);
502
+ if (ev.message) parts.push(`msg=${ev.message.slice(0, 200)}`);
503
+ lines.push(parts.join(" | "));
504
+ }
505
+ lines.push("");
506
+ return lines;
507
+ }
508
+ function buildPromptGuidelines() {
509
+ return [
510
+ "## Guidelines",
511
+ '- Be specific: "User prefers const over let" not "User has coding preferences"',
512
+ "- Include file paths when relevant",
513
+ "- Set importance based on how impactful the memory is",
514
+ '- Use tags to categorize (e.g., ["typescript", "testing", "auth"])',
515
+ "- Skip routine operations (file reads, standard commands)",
516
+ "- Focus on decisions, learnings, and project-specific knowledge",
517
+ "",
518
+ "Fill in the structured output."
519
+ ];
520
+ }
521
+ function buildClassificationPrompt(window) {
522
+ const sections = [
523
+ ...buildPromptPreamble(10),
524
+ ...formatEventsSection(window.events)
525
+ ];
526
+ if (window.transcriptExcerpt) {
527
+ sections.push("## Transcript Excerpt");
528
+ sections.push(window.transcriptExcerpt);
529
+ sections.push("");
530
+ }
531
+ sections.push(...buildPromptGuidelines());
532
+ return sections.join("\n");
533
+ }
534
+ function buildSingleClassificationPrompt(events, transcriptExcerpt) {
535
+ const sections = [
536
+ ...buildPromptPreamble(20),
537
+ ...formatEventsSection(events)
538
+ ];
539
+ if (transcriptExcerpt) {
540
+ sections.push("## Transcript Excerpt");
541
+ sections.push(transcriptExcerpt);
542
+ sections.push("");
543
+ }
544
+ sections.push(...buildPromptGuidelines());
545
+ return sections.join("\n");
546
+ }
547
+ function invokeClassifier(prompt, model, timeout) {
548
+ const claudePath = getClaudePath();
549
+ if (!claudePath) {
550
+ throw new Error("Claude CLI not found \u2014 cannot classify memories");
551
+ }
552
+ return new Promise((resolve2, reject) => {
553
+ const proc = spawn(claudePath, [
554
+ "--print",
555
+ "--model",
556
+ model,
557
+ "--output-format",
558
+ "json",
559
+ "--json-schema",
560
+ CLASSIFIER_SCHEMA,
561
+ "--permission-mode",
562
+ "bypassPermissions"
563
+ ], {
564
+ stdio: ["pipe", "pipe", "pipe"]
565
+ });
566
+ let stdout = "";
567
+ let stderr = "";
568
+ proc.stdout.on("data", (data) => {
569
+ stdout += data.toString();
570
+ });
571
+ proc.stderr.on("data", (data) => {
572
+ stderr += data.toString();
573
+ });
574
+ proc.stdin.write(prompt);
575
+ proc.stdin.end();
576
+ const timer = setTimeout(() => {
577
+ proc.kill("SIGTERM");
578
+ reject(new Error("Classification timed out"));
579
+ }, timeout);
580
+ proc.on("close", (code) => {
581
+ clearTimeout(timer);
582
+ if (code !== 0) {
583
+ reject(new Error(`claude CLI exited with code ${code}: ${stderr.slice(0, 500)}`));
584
+ } else {
585
+ try {
586
+ resolve2(parseClassifierOutput(stdout));
587
+ } catch (err) {
588
+ reject(err);
589
+ }
590
+ }
591
+ });
592
+ proc.on("error", (err) => {
593
+ clearTimeout(timer);
594
+ reject(new Error(`Failed to run claude CLI: ${err.message}`));
595
+ });
596
+ });
597
+ }
598
+ function classifyWindow(window, model, timeout) {
599
+ const prompt = buildClassificationPrompt(window);
600
+ return invokeClassifier(prompt, model, timeout);
601
+ }
602
+ function classifySinglePrompt(events, transcriptExcerpt, model, timeout) {
603
+ const prompt = buildSingleClassificationPrompt(events, transcriptExcerpt);
604
+ return invokeClassifier(prompt, model, timeout);
605
+ }
606
+ function parseClassifierOutput(response) {
607
+ try {
608
+ const envelope = JSON.parse(response.trim());
609
+ if (envelope.structured_output && typeof envelope.structured_output === "object") {
610
+ return ClassificationResultSchema.parse(envelope.structured_output);
611
+ }
612
+ if (typeof envelope.result === "string" && envelope.result.trim().startsWith("{")) {
613
+ return ClassificationResultSchema.parse(JSON.parse(envelope.result));
614
+ }
615
+ if (Array.isArray(envelope.memories)) {
616
+ return ClassificationResultSchema.parse(envelope);
617
+ }
618
+ } catch {
619
+ }
620
+ const jsonMatch = response.match(/```json\s*\n?([\s\S]*?)\n?\s*```/);
621
+ if (jsonMatch) {
622
+ try {
623
+ return ClassificationResultSchema.parse(JSON.parse(jsonMatch[1]));
624
+ } catch {
625
+ }
626
+ }
627
+ try {
628
+ return ClassificationResultSchema.parse(JSON.parse(response.trim()));
629
+ } catch {
630
+ }
631
+ return { memories: [] };
632
+ }
633
+ async function deduplicateMemories(projectDir, candidates, embeddings, store, threshold) {
634
+ const result = {
635
+ unique: [],
636
+ duplicates: [],
637
+ reinforced: []
638
+ };
639
+ for (let i = 0; i < candidates.length; i++) {
640
+ const candidate = candidates[i];
641
+ const vector = embeddings[i];
642
+ const hits = await store.query(vector, 1);
643
+ if (hits.length > 0 && hits[0].score >= threshold) {
644
+ const existingId = hits[0].id;
645
+ result.duplicates.push({
646
+ candidate,
647
+ existingId,
648
+ similarity: hits[0].score
649
+ });
650
+ updateEntry(projectDir, existingId, {
651
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
652
+ accessCount: (loadAccessCount(projectDir, existingId) ?? 0) + 1
653
+ });
654
+ result.reinforced.push(existingId);
655
+ } else {
656
+ result.unique.push(candidate);
657
+ }
658
+ }
659
+ return result;
660
+ }
661
+ function loadAccessCount(projectDir, id) {
662
+ const entries = listEntries(projectDir);
663
+ const entry = entries.find((e) => e.id === id);
664
+ return entry?.accessCount ?? null;
665
+ }
666
+ var DEFAULT_IMPORTANCE_WEIGHTS = {
667
+ critical: 1,
668
+ high: 0.8,
669
+ medium: 0.6,
670
+ low: 0.4
671
+ };
672
+ function computeDecayFactor(importance, createdAt) {
673
+ if (importance === "critical") return 1;
674
+ const ageMs = Date.now() - new Date(createdAt).getTime();
675
+ const ageDays = ageMs / (1e3 * 60 * 60 * 24);
676
+ const halfLife = {
677
+ high: 180,
678
+ // 6 months
679
+ medium: 90,
680
+ // 3 months
681
+ low: 30
682
+ // 1 month
683
+ };
684
+ const hl = halfLife[importance] ?? 90;
685
+ const decay = Math.pow(0.5, ageDays / hl);
686
+ return Math.max(0.1, decay);
687
+ }
688
+ function applyRanking(results, config) {
689
+ const weights = config?.importanceWeights ?? DEFAULT_IMPORTANCE_WEIGHTS;
690
+ const boostPerHit = config?.accessBoostPerHit ?? 0.05;
691
+ const boostCap = config?.accessBoostCap ?? 0.2;
692
+ for (const result of results) {
693
+ const entry = result.entry;
694
+ const importance = entry.importance;
695
+ const importanceWeight = weights[importance] ?? 0.6;
696
+ const decayFactor = computeDecayFactor(importance, entry.createdAt);
697
+ const accessBoost = Math.min(entry.accessCount * boostPerHit, boostCap);
698
+ result.finalScore = result.score * importanceWeight * decayFactor * (1 + accessBoost);
699
+ }
700
+ results.sort((a, b) => b.finalScore - a.finalScore);
701
+ return results;
702
+ }
703
+ var STALE_TIMEOUT_MS = 6e4;
704
+ var ACQUIRE_TIMEOUT_MS = 1e4;
705
+ var POLL_INTERVAL_MS = 500;
706
+ function sanitizeSlug(projectDir) {
707
+ const hash = createHash2("sha256").update(path5.resolve(projectDir)).digest("hex").slice(0, 16);
708
+ const name = path5.basename(projectDir).replace(/[^a-z0-9-]/gi, "-").toLowerCase();
709
+ return `${name}-${hash}`;
710
+ }
711
+ function lockFilePath(projectDir) {
712
+ const slug = sanitizeSlug(projectDir);
713
+ return path5.join(os2.tmpdir(), `ulpi-memory-${slug}.lock`);
714
+ }
715
+ function isProcessAlive(pid) {
716
+ try {
717
+ process.kill(pid, 0);
718
+ return true;
719
+ } catch {
720
+ return false;
721
+ }
722
+ }
723
+ function isLockStale(lockPath) {
724
+ try {
725
+ const content = fs5.readFileSync(lockPath, "utf-8");
726
+ const data = JSON.parse(content);
727
+ if (!isProcessAlive(data.pid)) return true;
728
+ const lockTime = new Date(data.timestamp).getTime();
729
+ if (Date.now() - lockTime > STALE_TIMEOUT_MS) return true;
730
+ return false;
731
+ } catch {
732
+ return true;
733
+ }
734
+ }
735
+ function acquireMemoryLock(projectDir) {
736
+ const lockPath = lockFilePath(projectDir);
737
+ const startTime = Date.now();
738
+ while (true) {
739
+ if (fs5.existsSync(lockPath)) {
740
+ if (isLockStale(lockPath)) {
741
+ try {
742
+ fs5.unlinkSync(lockPath);
743
+ } catch {
744
+ }
745
+ } else {
746
+ if (Date.now() - startTime > ACQUIRE_TIMEOUT_MS) {
747
+ throw new Error("Timeout acquiring memory lock");
748
+ }
749
+ const waitUntil = Date.now() + POLL_INTERVAL_MS;
750
+ while (Date.now() < waitUntil) {
751
+ }
752
+ continue;
753
+ }
754
+ }
755
+ const lockData = {
756
+ pid: process.pid,
757
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
758
+ };
759
+ try {
760
+ fs5.writeFileSync(lockPath, JSON.stringify(lockData), { flag: "wx" });
761
+ return;
762
+ } catch {
763
+ if (Date.now() - startTime > ACQUIRE_TIMEOUT_MS) {
764
+ throw new Error("Timeout acquiring memory lock");
765
+ }
766
+ const waitUntil = Date.now() + POLL_INTERVAL_MS;
767
+ while (Date.now() < waitUntil) {
768
+ }
769
+ }
770
+ }
771
+ }
772
+ function releaseMemoryLock(projectDir) {
773
+ const lockPath = lockFilePath(projectDir);
774
+ try {
775
+ const content = fs5.readFileSync(lockPath, "utf-8");
776
+ const data = JSON.parse(content);
777
+ if (data.pid === process.pid) {
778
+ fs5.unlinkSync(lockPath);
779
+ }
780
+ } catch {
781
+ }
782
+ }
783
+ function isMemoryLocked(projectDir) {
784
+ const lockPath = lockFilePath(projectDir);
785
+ if (!fs5.existsSync(lockPath)) return false;
786
+ return !isLockStale(lockPath);
787
+ }
788
+ async function searchMemory(projectDir, options) {
789
+ const startMs = Date.now();
790
+ const config = loadMemoryConfig(projectDir);
791
+ const limit = options.limit ?? 10;
792
+ const embedder = await createEmbedder(config.embedding);
793
+ const [queryVector] = await embedder.embed([options.query]);
794
+ acquireMemoryLock(projectDir);
795
+ let results;
796
+ let trimmed;
797
+ try {
798
+ const store = new MemoryVectorStore(projectDir);
799
+ await store.initialize();
800
+ const candidateCount = Math.min(limit * 3, 150);
801
+ const vectorResults = await store.query(queryVector, candidateCount);
802
+ results = [];
803
+ for (const vr of vectorResults) {
804
+ const entry = loadEntry(projectDir, vr.id);
805
+ if (!entry) continue;
806
+ if (entry.supersededBy && !options.includeSuperseded) continue;
807
+ if (options.types && options.types.length > 0) {
808
+ if (!options.types.includes(entry.type)) continue;
809
+ }
810
+ if (options.importance && options.importance.length > 0) {
811
+ if (!options.importance.includes(entry.importance)) continue;
812
+ }
813
+ if (options.tags && options.tags.length > 0) {
814
+ const hasTag = options.tags.some((t) => entry.tags.includes(t));
815
+ if (!hasTag) continue;
816
+ }
817
+ if (options.since && entry.createdAt < options.since) continue;
818
+ results.push({
819
+ entry,
820
+ score: vr.score,
821
+ finalScore: vr.score
822
+ // Will be updated by ranking
823
+ });
824
+ }
825
+ applyRanking(results, config.ranking);
826
+ trimmed = results.slice(0, limit);
827
+ for (const result of trimmed) {
828
+ try {
829
+ updateEntry(projectDir, result.entry.id, {
830
+ accessCount: result.entry.accessCount + 1,
831
+ lastAccessedAt: (/* @__PURE__ */ new Date()).toISOString()
832
+ });
833
+ } catch {
834
+ }
835
+ }
836
+ } finally {
837
+ releaseMemoryLock(projectDir);
838
+ }
839
+ return {
840
+ query: options.query,
841
+ durationMs: Date.now() - startMs,
842
+ results: trimmed
843
+ };
844
+ }
845
+ function getTopMemories(projectDir, limit) {
846
+ const config = loadMemoryConfig(projectDir);
847
+ const maxResults = limit ?? config.surfaceLimit;
848
+ const entries = listEntries(projectDir).filter((e) => !e.supersededBy);
849
+ if (entries.length === 0) return [];
850
+ const scored = entries.map((entry) => {
851
+ let score = 0;
852
+ const importanceScores = {
853
+ critical: 1,
854
+ high: 0.8,
855
+ medium: 0.5,
856
+ low: 0.2
857
+ };
858
+ score += importanceScores[entry.importance] ?? 0.5;
859
+ if (entry.lastAccessedAt) {
860
+ const daysSinceAccess = (Date.now() - new Date(entry.lastAccessedAt).getTime()) / (1e3 * 60 * 60 * 24);
861
+ if (daysSinceAccess < 7) score += 0.3;
862
+ else if (daysSinceAccess < 30) score += 0.1;
863
+ }
864
+ score += Math.min(entry.accessCount * 0.02, 0.2);
865
+ return { entry, score };
866
+ });
867
+ scored.sort((a, b) => b.score - a.score);
868
+ return scored.slice(0, maxResults).map((s) => s.entry);
869
+ }
870
+ function formatMemoriesForAgent(memories) {
871
+ if (memories.length === 0) return "";
872
+ const lines = [];
873
+ const width = 56;
874
+ lines.push(`\u256D${"\u2500".repeat(3)} Agent Memory ${"\u2500".repeat(width - 17)}\u256E`);
875
+ lines.push(`\u2502 ${memories.length} memories loaded for this codebase${" ".repeat(width - 37 - String(memories.length).length)}\u2502`);
876
+ lines.push(`\u251C${"\u2500".repeat(width)}\u2524`);
877
+ for (const mem of memories) {
878
+ const typeLabel = mem.type.replace(/_/g, " ");
879
+ const summary = mem.summary.length > width - typeLabel.length - 5 ? mem.summary.slice(0, width - typeLabel.length - 8) + "..." : mem.summary;
880
+ const line = `${typeLabel}: ${summary}`;
881
+ const padded = line.length > width - 4 ? line.slice(0, width - 4) : line + " ".repeat(width - 4 - line.length);
882
+ lines.push(`\u2502 ${padded} \u2502`);
883
+ }
884
+ lines.push(`\u2570${"\u2500".repeat(width)}\u256F`);
885
+ return lines.join("\n");
886
+ }
887
+ function watermarkPath(projectDir, sessionId) {
888
+ return path6.join(memoryWatermarksDir(projectDir), `${sessionId}.json`);
889
+ }
890
+ function loadWatermark(projectDir, sessionId) {
891
+ const filePath = watermarkPath(projectDir, sessionId);
892
+ if (!fs6.existsSync(filePath)) return null;
893
+ try {
894
+ return JSON.parse(fs6.readFileSync(filePath, "utf-8"));
895
+ } catch {
896
+ return null;
897
+ }
898
+ }
899
+ function saveWatermark(projectDir, watermark) {
900
+ const dir = memoryWatermarksDir(projectDir);
901
+ fs6.mkdirSync(dir, { recursive: true });
902
+ const filePath = watermarkPath(projectDir, watermark.sessionId);
903
+ const tmpPath = filePath + ".tmp";
904
+ fs6.writeFileSync(tmpPath, JSON.stringify(watermark, null, 2) + "\n", "utf-8");
905
+ fs6.renameSync(tmpPath, filePath);
906
+ }
907
+ function listWatermarks(projectDir) {
908
+ const dir = memoryWatermarksDir(projectDir);
909
+ if (!fs6.existsSync(dir)) return [];
910
+ const watermarks = [];
911
+ const files = fs6.readdirSync(dir).filter((f) => f.endsWith(".json"));
912
+ for (const file of files) {
913
+ try {
914
+ const raw = fs6.readFileSync(path6.join(dir, file), "utf-8");
915
+ watermarks.push(JSON.parse(raw));
916
+ } catch {
917
+ }
918
+ }
919
+ return watermarks;
920
+ }
921
+ function isMemoryInitialized(projectDir) {
922
+ return fs7.existsSync(projectMemoryDir(projectDir));
923
+ }
924
+ async function getMemoryStats(projectDir) {
925
+ if (!isMemoryInitialized(projectDir)) {
926
+ return {
927
+ totalMemories: 0,
928
+ memoriesByType: {},
929
+ memoriesByImportance: {},
930
+ totalAccesses: 0,
931
+ sessionsCaptured: 0,
932
+ sessionsClassified: 0,
933
+ indexSizeBytes: 0,
934
+ lastClassifiedAt: null,
935
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
936
+ initialized: false
937
+ };
938
+ }
939
+ const entries = listEntries(projectDir);
940
+ const sessions = listCapturedSessions(projectDir);
941
+ const watermarks = listWatermarks(projectDir);
942
+ const byType = {};
943
+ const byImportance = {};
944
+ let totalAccesses = 0;
945
+ for (const entry of entries) {
946
+ byType[entry.type] = (byType[entry.type] ?? 0) + 1;
947
+ byImportance[entry.importance] = (byImportance[entry.importance] ?? 0) + 1;
948
+ totalAccesses += entry.accessCount;
949
+ }
950
+ const store = new MemoryVectorStore(projectDir);
951
+ const indexSizeBytes = store.getIndexSizeBytes();
952
+ const classified = watermarks.filter((w) => w.status === "complete");
953
+ let lastClassifiedAt = null;
954
+ for (const w of classified) {
955
+ if (!lastClassifiedAt || w.updatedAt > lastClassifiedAt) {
956
+ lastClassifiedAt = w.updatedAt;
957
+ }
958
+ }
959
+ return {
960
+ totalMemories: entries.length,
961
+ memoriesByType: byType,
962
+ memoriesByImportance: byImportance,
963
+ totalAccesses,
964
+ sessionsCaptured: sessions.length,
965
+ sessionsClassified: classified.length,
966
+ indexSizeBytes,
967
+ lastClassifiedAt,
968
+ lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
969
+ initialized: true
970
+ };
971
+ }
972
+ function writeMemoryStats(projectDir, stats) {
973
+ const statsPath = memoryStatsFile(projectDir);
974
+ const tmpPath = statsPath + ".tmp";
975
+ fs7.mkdirSync(path7.dirname(statsPath), { recursive: true });
976
+ fs7.writeFileSync(tmpPath, JSON.stringify(stats, null, 2) + "\n", "utf-8");
977
+ fs7.renameSync(tmpPath, statsPath);
978
+ }
979
+ function loadSessionMeta(projectDir, sessionId) {
980
+ const metaPath = path8.join(sessionCaptureDir(projectDir, sessionId), "meta.json");
981
+ try {
982
+ return JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
983
+ } catch {
984
+ return null;
985
+ }
986
+ }
987
+ function isSessionSignificant(meta, filteredEvents, minEvents) {
988
+ const hasWrites = meta ? meta.filesWritten > 0 : true;
989
+ const hasEnoughEvents = filteredEvents.length >= minEvents;
990
+ const hasErrorEvents = filteredEvents.some(
991
+ (ev) => ev.event === "tool_blocked" || ev.event === "permission_denied" || ev.event === "postcondition_failed"
992
+ );
993
+ return hasWrites || hasEnoughEvents || hasErrorEvents;
994
+ }
995
+ async function classifySession(projectDir, sessionId, onProgress) {
996
+ const startMs = Date.now();
997
+ const config = loadMemoryConfig(projectDir);
998
+ onProgress?.({
999
+ phase: "reading",
1000
+ current: 0,
1001
+ total: 0,
1002
+ message: "Reading and filtering events..."
1003
+ });
1004
+ const allEvents = readCapturedEvents(projectDir, sessionId);
1005
+ if (allEvents.length === 0) {
1006
+ return {
1007
+ sessionId,
1008
+ windowsProcessed: 0,
1009
+ memoriesExtracted: 0,
1010
+ memoriesStored: 0,
1011
+ durationMs: Date.now() - startMs
1012
+ };
1013
+ }
1014
+ const watermark = loadWatermark(projectDir, sessionId);
1015
+ const startIdx = watermark?.lastProcessedEventIndex ?? 0;
1016
+ if (startIdx >= allEvents.length) {
1017
+ return {
1018
+ sessionId,
1019
+ windowsProcessed: 0,
1020
+ memoriesExtracted: 0,
1021
+ memoriesStored: 0,
1022
+ durationMs: Date.now() - startMs
1023
+ };
1024
+ }
1025
+ const unprocessedEvents = allEvents.slice(startIdx);
1026
+ const filteredEvents = config.classifier.filterNoiseEvents ? filterSignificantEvents(unprocessedEvents) : unprocessedEvents;
1027
+ const meta = loadSessionMeta(projectDir, sessionId);
1028
+ if (!isSessionSignificant(meta, filteredEvents, config.classifier.minSignificantEvents)) {
1029
+ saveWatermark(projectDir, {
1030
+ sessionId,
1031
+ lastProcessedEventIndex: allEvents.length,
1032
+ lastProcessedTs: allEvents[allEvents.length - 1].ts,
1033
+ status: "complete",
1034
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1035
+ });
1036
+ return {
1037
+ sessionId,
1038
+ windowsProcessed: 0,
1039
+ memoriesExtracted: 0,
1040
+ memoriesStored: 0,
1041
+ durationMs: Date.now() - startMs
1042
+ };
1043
+ }
1044
+ const allMemories = [];
1045
+ let windowsProcessed = 0;
1046
+ if (filteredEvents.length <= config.classifier.maxSinglePromptEvents) {
1047
+ onProgress?.({
1048
+ phase: "classifying",
1049
+ current: 1,
1050
+ total: 1,
1051
+ message: `Classifying ${filteredEvents.length} events in single prompt...`
1052
+ });
1053
+ let transcriptExcerpt;
1054
+ if (config.classifier.includeTranscript) {
1055
+ transcriptExcerpt = readCapturedTranscript(
1056
+ projectDir,
1057
+ sessionId,
1058
+ config.classifier.maxTranscriptChars
1059
+ ) ?? void 0;
1060
+ }
1061
+ try {
1062
+ const result = await classifySinglePrompt(
1063
+ filteredEvents,
1064
+ transcriptExcerpt,
1065
+ config.classifier.model,
1066
+ config.classifier.timeout
1067
+ );
1068
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1069
+ for (const mem of result.memories) {
1070
+ allMemories.push(toMemoryEntry(mem, sessionId, now));
1071
+ }
1072
+ } catch {
1073
+ }
1074
+ windowsProcessed = 1;
1075
+ } else {
1076
+ const windows = buildClassificationWindows(
1077
+ sessionId,
1078
+ projectDir,
1079
+ filteredEvents,
1080
+ config
1081
+ );
1082
+ if (windows.length === 0) {
1083
+ saveWatermark(projectDir, {
1084
+ sessionId,
1085
+ lastProcessedEventIndex: allEvents.length,
1086
+ lastProcessedTs: allEvents[allEvents.length - 1].ts,
1087
+ status: "complete",
1088
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1089
+ });
1090
+ return {
1091
+ sessionId,
1092
+ windowsProcessed: 0,
1093
+ memoriesExtracted: 0,
1094
+ memoriesStored: 0,
1095
+ durationMs: Date.now() - startMs
1096
+ };
1097
+ }
1098
+ let windowIdx = 0;
1099
+ for (const window of windows) {
1100
+ windowIdx++;
1101
+ onProgress?.({
1102
+ phase: "classifying",
1103
+ current: windowIdx,
1104
+ total: windows.length,
1105
+ message: `Classifying window ${windowIdx}/${windows.length}...`
1106
+ });
1107
+ try {
1108
+ const result = await classifyWindow(
1109
+ window,
1110
+ config.classifier.model,
1111
+ config.classifier.timeout
1112
+ );
1113
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1114
+ for (const mem of result.memories) {
1115
+ allMemories.push(toMemoryEntry(mem, sessionId, now));
1116
+ }
1117
+ } catch {
1118
+ continue;
1119
+ }
1120
+ }
1121
+ windowsProcessed = windows.length;
1122
+ }
1123
+ const lastEvent = allEvents[allEvents.length - 1];
1124
+ if (allMemories.length === 0) {
1125
+ saveWatermark(projectDir, {
1126
+ sessionId,
1127
+ lastProcessedEventIndex: allEvents.length,
1128
+ lastProcessedTs: lastEvent.ts,
1129
+ status: "complete",
1130
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1131
+ });
1132
+ return {
1133
+ sessionId,
1134
+ windowsProcessed,
1135
+ memoriesExtracted: 0,
1136
+ memoriesStored: 0,
1137
+ durationMs: Date.now() - startMs
1138
+ };
1139
+ }
1140
+ onProgress?.({
1141
+ phase: "embedding",
1142
+ current: 0,
1143
+ total: allMemories.length,
1144
+ message: `Embedding ${allMemories.length} memories...`
1145
+ });
1146
+ const embedder = await createEmbedder(config.embedding);
1147
+ const texts = allMemories.map((m) => m.summary);
1148
+ const embeddings = await embedWithBatch(embedder, texts);
1149
+ onProgress?.({
1150
+ phase: "storing",
1151
+ current: 0,
1152
+ total: allMemories.length,
1153
+ message: "Deduplicating and storing..."
1154
+ });
1155
+ acquireMemoryLock(projectDir);
1156
+ try {
1157
+ const store = new MemoryVectorStore(projectDir);
1158
+ await store.initialize();
1159
+ const dedupResult = await deduplicateMemories(
1160
+ projectDir,
1161
+ allMemories,
1162
+ embeddings,
1163
+ store,
1164
+ config.retention.deduplicationThreshold
1165
+ );
1166
+ if (dedupResult.unique.length > 0) {
1167
+ const vectorItems = [];
1168
+ for (let i = 0; i < allMemories.length; i++) {
1169
+ const mem = allMemories[i];
1170
+ if (!dedupResult.unique.includes(mem)) continue;
1171
+ saveEntry(projectDir, mem);
1172
+ vectorItems.push({
1173
+ id: mem.id,
1174
+ vector: embeddings[i],
1175
+ metadata: {
1176
+ id: mem.id,
1177
+ type: mem.type,
1178
+ importance: mem.importance,
1179
+ snippet: mem.summary.slice(0, 200),
1180
+ createdAt: mem.createdAt,
1181
+ tags: mem.tags.join(",")
1182
+ }
1183
+ });
1184
+ }
1185
+ await store.upsertItems(vectorItems);
1186
+ }
1187
+ saveWatermark(projectDir, {
1188
+ sessionId,
1189
+ lastProcessedEventIndex: allEvents.length,
1190
+ lastProcessedTs: lastEvent.ts,
1191
+ status: "complete",
1192
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1193
+ });
1194
+ onProgress?.({
1195
+ phase: "finalizing",
1196
+ current: dedupResult.unique.length,
1197
+ total: allMemories.length,
1198
+ message: `Stored ${dedupResult.unique.length} new memories (${dedupResult.duplicates.length} duplicates)`
1199
+ });
1200
+ return {
1201
+ sessionId,
1202
+ windowsProcessed,
1203
+ memoriesExtracted: allMemories.length,
1204
+ memoriesStored: dedupResult.unique.length,
1205
+ durationMs: Date.now() - startMs
1206
+ };
1207
+ } finally {
1208
+ releaseMemoryLock(projectDir);
1209
+ }
1210
+ }
1211
+ function toMemoryEntry(mem, sessionId, now) {
1212
+ return {
1213
+ id: generateMemoryId(mem.type, mem.summary),
1214
+ type: mem.type,
1215
+ summary: mem.summary,
1216
+ detail: mem.detail ?? null,
1217
+ importance: mem.importance,
1218
+ tags: mem.tags,
1219
+ relatedFiles: mem.relatedFiles,
1220
+ source: {
1221
+ type: "classifier",
1222
+ sessionId
1223
+ },
1224
+ createdAt: now,
1225
+ updatedAt: now,
1226
+ accessCount: 0
1227
+ };
1228
+ }
1229
+ async function reindexMemories(projectDir, onProgress) {
1230
+ const startMs = Date.now();
1231
+ const config = loadMemoryConfig(projectDir);
1232
+ const entries = listEntries(projectDir);
1233
+ if (entries.length === 0) {
1234
+ return { reindexed: 0, durationMs: Date.now() - startMs };
1235
+ }
1236
+ onProgress?.({
1237
+ phase: "embedding",
1238
+ current: 0,
1239
+ total: entries.length,
1240
+ message: `Re-embedding ${entries.length} memories...`
1241
+ });
1242
+ const embedder = await createEmbedder(config.embedding);
1243
+ const texts = entries.map((m) => m.summary);
1244
+ const embeddings = await embedWithBatch(embedder, texts);
1245
+ onProgress?.({
1246
+ phase: "storing",
1247
+ current: 0,
1248
+ total: entries.length,
1249
+ message: "Rebuilding vector index..."
1250
+ });
1251
+ acquireMemoryLock(projectDir);
1252
+ try {
1253
+ const lancePath = memoryLanceDir(projectDir);
1254
+ if (fs8.existsSync(lancePath)) {
1255
+ fs8.rmSync(lancePath, { recursive: true });
1256
+ }
1257
+ const store = new MemoryVectorStore(projectDir);
1258
+ await store.initialize();
1259
+ const vectorItems = entries.map((entry, i) => ({
1260
+ id: entry.id,
1261
+ vector: embeddings[i],
1262
+ metadata: {
1263
+ id: entry.id,
1264
+ type: entry.type,
1265
+ importance: entry.importance,
1266
+ snippet: entry.summary.slice(0, 200),
1267
+ createdAt: entry.createdAt,
1268
+ tags: entry.tags.join(",")
1269
+ }
1270
+ }));
1271
+ await store.upsertItems(vectorItems);
1272
+ onProgress?.({
1273
+ phase: "finalizing",
1274
+ current: entries.length,
1275
+ total: entries.length,
1276
+ message: `Re-indexed ${entries.length} memories`
1277
+ });
1278
+ return { reindexed: entries.length, durationMs: Date.now() - startMs };
1279
+ } finally {
1280
+ releaseMemoryLock(projectDir);
1281
+ }
1282
+ }
1283
+ async function embedWithBatch(embedder, texts) {
1284
+ if (texts.length === 0) return [];
1285
+ if (embedder.supportsBatch && embedder.submitBatch && embedder.pollBatch) {
1286
+ const batchId = await embedder.submitBatch(texts);
1287
+ const POLL_MS = 2e3;
1288
+ while (true) {
1289
+ const status = await embedder.pollBatch(batchId);
1290
+ if (status.status === "completed" && status.results) {
1291
+ return status.results;
1292
+ }
1293
+ if (status.status === "failed") {
1294
+ throw new Error(`Batch embedding failed (batch ${batchId})`);
1295
+ }
1296
+ await new Promise((resolve2) => setTimeout(resolve2, POLL_MS));
1297
+ }
1298
+ }
1299
+ return embedder.embed(texts);
1300
+ }
1301
+ async function rememberMemory(projectDir, entry) {
1302
+ const config = loadMemoryConfig(projectDir);
1303
+ const embedder = await createEmbedder(config.embedding);
1304
+ const [vector] = await embedder.embed([entry.summary]);
1305
+ acquireMemoryLock(projectDir);
1306
+ try {
1307
+ const store = new MemoryVectorStore(projectDir);
1308
+ await store.initialize();
1309
+ saveEntry(projectDir, entry);
1310
+ await store.upsertItems([{
1311
+ id: entry.id,
1312
+ vector,
1313
+ metadata: {
1314
+ id: entry.id,
1315
+ type: entry.type,
1316
+ importance: entry.importance,
1317
+ snippet: entry.summary.slice(0, 200),
1318
+ createdAt: entry.createdAt,
1319
+ tags: entry.tags.join(",")
1320
+ }
1321
+ }]);
1322
+ } finally {
1323
+ releaseMemoryLock(projectDir);
1324
+ }
1325
+ }
1326
+ function memoryBranchExists(projectDir) {
1327
+ const branch = getMemoryBranch();
1328
+ try {
1329
+ execFileSync2("git", ["rev-parse", "--verify", `refs/heads/${branch}`], {
1330
+ cwd: projectDir,
1331
+ stdio: "pipe",
1332
+ timeout: 5e3
1333
+ });
1334
+ return true;
1335
+ } catch {
1336
+ return false;
1337
+ }
1338
+ }
1339
+ function initMemoryBranch(projectDir) {
1340
+ const branch = getMemoryBranch();
1341
+ if (memoryBranchExists(projectDir)) return;
1342
+ const tmpDir = fs9.mkdtempSync(path9.join(os3.tmpdir(), "ulpi-memory-"));
1343
+ try {
1344
+ execFileSync2("git", ["worktree", "add", "--detach", tmpDir], {
1345
+ cwd: projectDir,
1346
+ stdio: "pipe",
1347
+ timeout: 1e4
1348
+ });
1349
+ execFileSync2("git", ["checkout", "--orphan", branch], {
1350
+ cwd: tmpDir,
1351
+ stdio: "pipe",
1352
+ timeout: 5e3
1353
+ });
1354
+ execFileSync2("git", ["rm", "-rf", "--cached", "."], {
1355
+ cwd: tmpDir,
1356
+ stdio: "pipe",
1357
+ timeout: 5e3
1358
+ });
1359
+ fs9.writeFileSync(
1360
+ path9.join(tmpDir, "README.md"),
1361
+ "# ULPI Agent Memory\n\nThis branch stores agent memory data.\n"
1362
+ );
1363
+ execFileSync2("git", ["add", "README.md"], {
1364
+ cwd: tmpDir,
1365
+ stdio: "pipe",
1366
+ timeout: 5e3
1367
+ });
1368
+ execFileSync2("git", ["commit", "-m", "init: agent memory branch"], {
1369
+ cwd: tmpDir,
1370
+ stdio: "pipe",
1371
+ timeout: 1e4
1372
+ });
1373
+ } finally {
1374
+ try {
1375
+ execFileSync2("git", ["worktree", "remove", "--force", tmpDir], {
1376
+ cwd: projectDir,
1377
+ stdio: "pipe",
1378
+ timeout: 1e4
1379
+ });
1380
+ } catch {
1381
+ try {
1382
+ fs9.rmSync(tmpDir, { recursive: true, force: true });
1383
+ } catch {
1384
+ }
1385
+ }
1386
+ }
1387
+ }
1388
+ function exportMemories(projectDir) {
1389
+ const branch = getMemoryBranch();
1390
+ if (!memoryBranchExists(projectDir)) {
1391
+ initMemoryBranch(projectDir);
1392
+ }
1393
+ const entries = listEntries(projectDir);
1394
+ const config = loadMemoryConfig(projectDir);
1395
+ const exportData = {
1396
+ version: 1,
1397
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1398
+ projectDir,
1399
+ memories: entries,
1400
+ config
1401
+ };
1402
+ const tmpDir = fs9.mkdtempSync(path9.join(os3.tmpdir(), "ulpi-memory-export-"));
1403
+ try {
1404
+ execFileSync2("git", ["worktree", "add", tmpDir, branch], {
1405
+ cwd: projectDir,
1406
+ stdio: "pipe",
1407
+ timeout: 1e4
1408
+ });
1409
+ fs9.writeFileSync(
1410
+ path9.join(tmpDir, "memories.json"),
1411
+ JSON.stringify(exportData, null, 2) + "\n"
1412
+ );
1413
+ execFileSync2("git", ["add", "memories.json"], {
1414
+ cwd: tmpDir,
1415
+ stdio: "pipe",
1416
+ timeout: 5e3
1417
+ });
1418
+ try {
1419
+ execFileSync2("git", ["diff", "--cached", "--quiet"], {
1420
+ cwd: tmpDir,
1421
+ stdio: "pipe",
1422
+ timeout: 5e3
1423
+ });
1424
+ const sha2 = execFileSync2("git", ["rev-parse", "HEAD"], {
1425
+ cwd: tmpDir,
1426
+ encoding: "utf-8",
1427
+ timeout: 5e3
1428
+ }).trim();
1429
+ return { branchName: branch, commitSha: sha2, memoriesExported: entries.length };
1430
+ } catch {
1431
+ }
1432
+ execFileSync2("git", [
1433
+ "commit",
1434
+ "-m",
1435
+ `memory: export ${entries.length} memories`
1436
+ ], {
1437
+ cwd: tmpDir,
1438
+ stdio: "pipe",
1439
+ timeout: 1e4
1440
+ });
1441
+ const sha = execFileSync2("git", ["rev-parse", "HEAD"], {
1442
+ cwd: tmpDir,
1443
+ encoding: "utf-8",
1444
+ timeout: 5e3
1445
+ }).trim();
1446
+ return { branchName: branch, commitSha: sha, memoriesExported: entries.length };
1447
+ } finally {
1448
+ try {
1449
+ execFileSync2("git", ["worktree", "remove", "--force", tmpDir], {
1450
+ cwd: projectDir,
1451
+ stdio: "pipe",
1452
+ timeout: 1e4
1453
+ });
1454
+ } catch {
1455
+ try {
1456
+ fs9.rmSync(tmpDir, { recursive: true, force: true });
1457
+ } catch {
1458
+ }
1459
+ }
1460
+ }
1461
+ }
1462
+ function importMemories(projectDir) {
1463
+ const branch = getMemoryBranch();
1464
+ if (!memoryBranchExists(projectDir)) {
1465
+ return { success: false, memoriesImported: 0, message: "Memory branch does not exist" };
1466
+ }
1467
+ try {
1468
+ const raw = execFileSync2(
1469
+ "git",
1470
+ ["show", `${branch}:memories.json`],
1471
+ { cwd: projectDir, encoding: "utf-8", timeout: 1e4 }
1472
+ );
1473
+ const exportData = JSON.parse(raw);
1474
+ if (!exportData.memories || !Array.isArray(exportData.memories)) {
1475
+ return { success: false, memoriesImported: 0, message: "Invalid export data" };
1476
+ }
1477
+ const entriesDir = memoryEntriesDir(projectDir);
1478
+ fs9.mkdirSync(entriesDir, { recursive: true });
1479
+ let imported = 0;
1480
+ for (const entry of exportData.memories) {
1481
+ const filePath = path9.join(entriesDir, `${entry.id}.json`);
1482
+ if (!fs9.existsSync(filePath)) {
1483
+ fs9.writeFileSync(filePath, JSON.stringify(entry, null, 2) + "\n", "utf-8");
1484
+ imported++;
1485
+ }
1486
+ }
1487
+ return {
1488
+ success: true,
1489
+ memoriesImported: imported,
1490
+ message: `Imported ${imported} new memories (${exportData.memories.length - imported} already exist)`
1491
+ };
1492
+ } catch (err) {
1493
+ const message = err instanceof Error ? err.message : String(err);
1494
+ return { success: false, memoriesImported: 0, message };
1495
+ }
1496
+ }
1497
+
1498
+ export {
1499
+ DEFAULT_MEMORY_CONFIG,
1500
+ loadMemoryConfig,
1501
+ saveMemoryConfig,
1502
+ isMemoryEnabled,
1503
+ generateMemoryId,
1504
+ MemoryVectorStore,
1505
+ saveEntry,
1506
+ loadEntry,
1507
+ removeEntry,
1508
+ listEntries,
1509
+ updateEntry,
1510
+ redactContent,
1511
+ appendMemoryEvent,
1512
+ toClassificationEvent,
1513
+ finalizeCapture,
1514
+ isSessionCaptured,
1515
+ listCapturedSessions,
1516
+ readCapturedEvents,
1517
+ readCapturedTranscript,
1518
+ filterSignificantEvents,
1519
+ buildClassificationWindows,
1520
+ buildClassificationPrompt,
1521
+ buildSingleClassificationPrompt,
1522
+ classifyWindow,
1523
+ classifySinglePrompt,
1524
+ parseClassifierOutput,
1525
+ deduplicateMemories,
1526
+ applyRanking,
1527
+ acquireMemoryLock,
1528
+ releaseMemoryLock,
1529
+ isMemoryLocked,
1530
+ searchMemory,
1531
+ getTopMemories,
1532
+ formatMemoriesForAgent,
1533
+ loadWatermark,
1534
+ saveWatermark,
1535
+ listWatermarks,
1536
+ isMemoryInitialized,
1537
+ getMemoryStats,
1538
+ writeMemoryStats,
1539
+ classifySession,
1540
+ reindexMemories,
1541
+ rememberMemory,
1542
+ memoryBranchExists,
1543
+ initMemoryBranch,
1544
+ exportMemories,
1545
+ importMemories
1546
+ };