@ulpi/cli 0.1.1 → 0.1.3

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 (30) hide show
  1. package/dist/auth-KQCJ43U2.js +118 -0
  2. package/dist/{chunk-Q4HIY43N.js → chunk-2VYFVYJL.js} +67 -24
  3. package/dist/{chunk-DBMUNBNB.js → chunk-5J6NLQUN.js} +149 -19
  4. package/dist/{chunk-4KRVDKGB.js → chunk-F7OXF7Z3.js} +1 -1
  5. package/dist/chunk-G6SVZ4Q5.js +122 -0
  6. package/dist/{chunk-NNUWU6CV.js → chunk-JGBXM5NC.js} +42 -0
  7. package/dist/{chunk-6JCMYYBT.js → chunk-PDR55ZNW.js} +247 -112
  8. package/dist/{chunk-247GVVKK.js → chunk-ZLYRPD7I.js} +18 -16
  9. package/dist/ci-QM57ZCBW.js +367 -0
  10. package/dist/{codemap-RRJIDBQ5.js → codemap-RKSD4MIE.js} +49 -17
  11. package/dist/{dist-LZKZFPVX.js → dist-CB5D5LMO.js} +6 -3
  12. package/dist/{dist-7LHZ65GC.js → dist-CS2VKNYS.js} +5 -4
  13. package/dist/{dist-R5F4MX3I.js → dist-GJYT2OQV.js} +11 -4
  14. package/dist/{dist-RJGCUS3L.js → dist-QAU3LGJN.js} +3 -1
  15. package/dist/{dist-W7K4WPAF.js → dist-UKMCJBB2.js} +42 -14
  16. package/dist/{dist-R5ZJ4LX5.js → dist-YA2BWZB2.js} +1 -1
  17. package/dist/{history-Q2LDADFW.js → history-NFNA4HE5.js} +13 -7
  18. package/dist/index.js +50 -21
  19. package/dist/{init-AY5C2ZAS.js → init-6CH4HV5T.js} +2 -2
  20. package/dist/{memory-J3G24QHS.js → memory-Y6OZTXJ2.js} +231 -22
  21. package/dist/{server-MOYPE4SM-N7SE2AN7.js → server-USLHY6GH-AEOJC5ST.js} +2 -2
  22. package/dist/skills/ulpi-generate-guardian/SKILL.md +246 -7
  23. package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +161 -4
  24. package/dist/skills/ulpi-generate-guardian/references/language-rules.md +13 -18
  25. package/dist/{ui-L7UAWXDY.js → ui-OWXZ3YSR.js} +3 -3
  26. package/dist/ui.html +112 -112
  27. package/dist/{update-DJ227LL3.js → update-WUITQX4Z.js} +1 -1
  28. package/dist/{version-checker-M37KI7DY.js → version-checker-SMAYSN7Y.js} +1 -1
  29. package/package.json +31 -28
  30. package/LICENSE +0 -21
@@ -0,0 +1,122 @@
1
+ // ../../packages/ci-engine/dist/index.js
2
+ import * as fs4 from "fs";
3
+ import * as path3 from "path";
4
+ import { execFileSync as execFileSync2 } from "child_process";
5
+ function buildPrompt(config, context) {
6
+ const sections = [];
7
+ sections.push("# Task");
8
+ sections.push("");
9
+ sections.push(config.instruction);
10
+ sections.push("");
11
+ if (context.prTitle || context.prBody) {
12
+ sections.push("# Pull Request Context");
13
+ sections.push("");
14
+ if (context.prTitle) sections.push(`**Title:** ${context.prTitle}`);
15
+ if (context.prBody) {
16
+ sections.push("");
17
+ sections.push(context.prBody);
18
+ }
19
+ sections.push("");
20
+ }
21
+ sections.push("# Branch Info");
22
+ sections.push("");
23
+ sections.push(`- **Working branch:** ${config.ref}`);
24
+ sections.push(`- **Base branch:** ${config.baseBranch}`);
25
+ sections.push(`- **Repository:** ${config.repoFullName}`);
26
+ sections.push("");
27
+ if (config.jiraContext || context.jiraContext) {
28
+ sections.push("# Jira Context");
29
+ sections.push("");
30
+ sections.push(config.jiraContext ?? context.jiraContext ?? "");
31
+ sections.push("");
32
+ }
33
+ sections.push("# Important Instructions");
34
+ sections.push("");
35
+ sections.push(
36
+ "- You are running in CI mode inside a worker container."
37
+ );
38
+ sections.push(
39
+ "- Work on the existing branch \u2014 do NOT create new branches."
40
+ );
41
+ sections.push(
42
+ "- Commit your changes with clear, descriptive commit messages."
43
+ );
44
+ sections.push("- Run tests if a test runner is configured.");
45
+ sections.push(
46
+ "- Do NOT push to the remote \u2014 the orchestrator will handle pushing."
47
+ );
48
+ sections.push("");
49
+ return sections.join("\n");
50
+ }
51
+ function extractCredentials(claudeConfigDir) {
52
+ const configDir = claudeConfigDir ?? path3.join(process.env.HOME ?? "", ".claude");
53
+ if (!fs4.existsSync(configDir)) {
54
+ throw new Error(
55
+ `Claude config directory not found: ${configDir}`
56
+ );
57
+ }
58
+ const tarOutput = execFileSync2(
59
+ "tar",
60
+ ["-czf", "-", "-C", configDir, "."],
61
+ {
62
+ encoding: "buffer",
63
+ timeout: 3e4,
64
+ maxBuffer: 50 * 1024 * 1024
65
+ // 50MB limit
66
+ }
67
+ );
68
+ return tarOutput.toString("base64");
69
+ }
70
+ function writeCredentials(blob, targetDir) {
71
+ fs4.mkdirSync(targetDir, { recursive: true });
72
+ const tarBuffer = Buffer.from(blob, "base64");
73
+ execFileSync2("tar", ["-xzf", "-", "-C", targetDir], {
74
+ input: tarBuffer,
75
+ timeout: 3e4
76
+ });
77
+ }
78
+ function validateCredentials(blob) {
79
+ const tmpDir = fs4.mkdtempSync("/tmp/ulpi-claude-check-");
80
+ try {
81
+ writeCredentials(blob, tmpDir);
82
+ const files = fs4.readdirSync(tmpDir);
83
+ return files.length > 0;
84
+ } catch {
85
+ return false;
86
+ } finally {
87
+ try {
88
+ fs4.rmSync(tmpDir, { recursive: true, force: true });
89
+ } catch {
90
+ }
91
+ }
92
+ }
93
+ function getCredentialExpiry(blob) {
94
+ const tmpDir = fs4.mkdtempSync("/tmp/ulpi-claude-expiry-");
95
+ try {
96
+ writeCredentials(blob, tmpDir);
97
+ const authFile = path3.join(tmpDir, ".credentials.json");
98
+ if (fs4.existsSync(authFile)) {
99
+ try {
100
+ const data = JSON.parse(
101
+ fs4.readFileSync(authFile, "utf-8")
102
+ );
103
+ if (data.expiresAt) return data.expiresAt;
104
+ if (data.expires_at) return data.expires_at;
105
+ } catch {
106
+ }
107
+ }
108
+ return void 0;
109
+ } finally {
110
+ try {
111
+ fs4.rmSync(tmpDir, { recursive: true, force: true });
112
+ } catch {
113
+ }
114
+ }
115
+ }
116
+
117
+ export {
118
+ buildPrompt,
119
+ extractCredentials,
120
+ validateCredentials,
121
+ getCredentialExpiry
122
+ };
@@ -345,6 +345,47 @@ function listRecentCommits(projectDir, maxCount = 20) {
345
345
  return [];
346
346
  }
347
347
  }
348
+ function listBranchOnlyCommits(projectDir, maxCount = 50) {
349
+ try {
350
+ let currentBranch;
351
+ try {
352
+ currentBranch = gitExec(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
353
+ } catch {
354
+ return listRecentCommits(projectDir, maxCount);
355
+ }
356
+ let defaultBranch = "main";
357
+ try {
358
+ const ref = gitExec(projectDir, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
359
+ defaultBranch = ref.replace("refs/remotes/origin/", "");
360
+ } catch {
361
+ try {
362
+ gitExec(projectDir, ["rev-parse", "--verify", "refs/heads/main"]);
363
+ defaultBranch = "main";
364
+ } catch {
365
+ try {
366
+ gitExec(projectDir, ["rev-parse", "--verify", "refs/heads/master"]);
367
+ defaultBranch = "master";
368
+ } catch {
369
+ return listRecentCommits(projectDir, maxCount);
370
+ }
371
+ }
372
+ }
373
+ if (currentBranch === defaultBranch) {
374
+ return listRecentCommits(projectDir, maxCount);
375
+ }
376
+ const raw = gitExec(projectDir, [
377
+ "rev-list",
378
+ "--reverse",
379
+ `--max-count=${maxCount}`,
380
+ "HEAD",
381
+ "--not",
382
+ defaultBranch
383
+ ]);
384
+ return raw ? raw.split("\n").filter(Boolean) : [];
385
+ } catch {
386
+ return listRecentCommits(projectDir, maxCount);
387
+ }
388
+ }
348
389
  function readFromBranch(projectDir, filePath, branchName) {
349
390
  branchName ??= getHistoryBranch();
350
391
  validateBranchPath(filePath);
@@ -1575,6 +1616,7 @@ export {
1575
1616
  getCurrentHead,
1576
1617
  listCommitsBetween,
1577
1618
  listRecentCommits,
1619
+ listBranchOnlyCommits,
1578
1620
  readFromBranch,
1579
1621
  listBranchDir,
1580
1622
  getWorktreeId,
@@ -1,6 +1,15 @@
1
1
  import {
2
- createEmbedder
3
- } from "./chunk-DBMUNBNB.js";
2
+ createEmbedder,
3
+ loadCodemapIgnore,
4
+ matchesDenyPattern
5
+ } from "./chunk-5J6NLQUN.js";
6
+ import {
7
+ commitInWorktree,
8
+ copyAndStage,
9
+ historyBranchExists,
10
+ withWorktree,
11
+ writeAndStage
12
+ } from "./chunk-JGBXM5NC.js";
4
13
  import {
5
14
  readEvents
6
15
  } from "./chunk-YM2HV4IA.js";
@@ -46,6 +55,8 @@ import { execFileSync as execFileSync2 } from "child_process";
46
55
  import * as fs9 from "fs";
47
56
  import * as os3 from "os";
48
57
  import * as path9 from "path";
58
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, existsSync as existsSync10 } from "fs";
59
+ import { join as join8 } from "path";
49
60
  var DEFAULT_MEMORY_CONFIG = MemoryConfigSchema.parse({});
50
61
  function loadMemoryConfig(projectDir) {
51
62
  const configPath = memoryConfigFile(projectDir);
@@ -248,9 +259,7 @@ function finalizeCapture(sessionId, state, projectDir) {
248
259
  const dir = sessionCaptureDir(projectDir, sessionId);
249
260
  fs3.mkdirSync(dir, { recursive: true });
250
261
  const redactPatterns = config.redactPatterns;
251
- if (config.captureMode === "end_of_session") {
252
- bulkCaptureFromSessionLog(sessionId, projectDir, redactPatterns);
253
- }
262
+ bulkCaptureFromSessionLog(sessionId, projectDir, redactPatterns);
254
263
  let transcriptCaptured = false;
255
264
  if (state.transcriptPath) {
256
265
  transcriptCaptured = copyTranscript(
@@ -406,11 +415,16 @@ var TRIVIAL_COMMANDS = /* @__PURE__ */ new Set([
406
415
  "date",
407
416
  "wc"
408
417
  ]);
409
- function filterSignificantEvents(events) {
418
+ function filterSignificantEvents(events, projectDir) {
419
+ const ignorePatterns = projectDir ? loadCodemapIgnore(projectDir) : [];
410
420
  return events.filter((ev) => {
411
421
  if (ev.event === "tool_blocked" || ev.event === "permission_denied" || ev.event === "postcondition_failed") {
412
422
  return true;
413
423
  }
424
+ if (ev.filePath && ignorePatterns.length > 0 && projectDir) {
425
+ const rel = ev.filePath.startsWith(projectDir) ? ev.filePath.slice(projectDir.length + 1) : ev.filePath;
426
+ if (matchesDenyPattern(rel, ignorePatterns)) return false;
427
+ }
414
428
  if (ev.event === "tool_used" && ev.toolName === "Read") {
415
429
  return false;
416
430
  }
@@ -550,6 +564,8 @@ function invokeClassifier(prompt, model, timeout) {
550
564
  throw new Error("Claude CLI not found \u2014 cannot classify memories");
551
565
  }
552
566
  return new Promise((resolve2, reject) => {
567
+ const env = { ...process.env };
568
+ delete env.CLAUDECODE;
553
569
  const proc = spawn(claudePath, [
554
570
  "--print",
555
571
  "--model",
@@ -561,7 +577,8 @@ function invokeClassifier(prompt, model, timeout) {
561
577
  "--permission-mode",
562
578
  "bypassPermissions"
563
579
  ], {
564
- stdio: ["pipe", "pipe", "pipe"]
580
+ stdio: ["pipe", "pipe", "pipe"],
581
+ env
565
582
  });
566
583
  let stdout = "";
567
584
  let stderr = "";
@@ -1023,7 +1040,7 @@ async function classifySession(projectDir, sessionId, onProgress) {
1023
1040
  };
1024
1041
  }
1025
1042
  const unprocessedEvents = allEvents.slice(startIdx);
1026
- const filteredEvents = config.classifier.filterNoiseEvents ? filterSignificantEvents(unprocessedEvents) : unprocessedEvents;
1043
+ const filteredEvents = config.classifier.filterNoiseEvents ? filterSignificantEvents(unprocessedEvents, projectDir) : unprocessedEvents;
1027
1044
  const meta = loadSessionMeta(projectDir, sessionId);
1028
1045
  if (!isSessionSignificant(meta, filteredEvents, config.classifier.minSignificantEvents)) {
1029
1046
  saveWatermark(projectDir, {
@@ -1324,17 +1341,7 @@ async function rememberMemory(projectDir, entry) {
1324
1341
  }
1325
1342
  }
1326
1343
  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
- }
1344
+ return historyBranchExists(projectDir, getMemoryBranch());
1338
1345
  }
1339
1346
  function initMemoryBranch(projectDir) {
1340
1347
  const branch = getMemoryBranch();
@@ -1385,113 +1392,238 @@ function initMemoryBranch(projectDir) {
1385
1392
  }
1386
1393
  }
1387
1394
  }
1388
- function exportMemories(projectDir) {
1389
- const branch = getMemoryBranch();
1395
+ async function exportMemories(projectDir) {
1396
+ const branchName = getMemoryBranch();
1390
1397
  if (!memoryBranchExists(projectDir)) {
1391
1398
  initMemoryBranch(projectDir);
1392
1399
  }
1393
1400
  const entries = listEntries(projectDir);
1394
1401
  const config = loadMemoryConfig(projectDir);
1395
- const exportData = {
1396
- version: 1,
1397
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1398
- projectDir,
1399
- memories: entries,
1400
- config
1402
+ const lanceDir = memoryLanceDir(projectDir);
1403
+ const statsFile = memoryStatsFile(projectDir);
1404
+ let filesExported = 0;
1405
+ let totalSizeBytes = 0;
1406
+ const commitSha = await withWorktree(projectDir, branchName, (worktreeDir) => {
1407
+ const exportData = {
1408
+ version: 1,
1409
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1410
+ projectDir,
1411
+ memories: entries,
1412
+ config
1413
+ };
1414
+ const memoriesJson = JSON.stringify(exportData, null, 2) + "\n";
1415
+ writeAndStage(worktreeDir, "memories.json", memoriesJson);
1416
+ totalSizeBytes += Buffer.byteLength(memoriesJson, "utf-8");
1417
+ filesExported++;
1418
+ const configPath = memoryConfigFile(projectDir);
1419
+ if (fs9.existsSync(configPath)) {
1420
+ const content = fs9.readFileSync(configPath, "utf-8");
1421
+ writeAndStage(worktreeDir, "config.json", content);
1422
+ totalSizeBytes += Buffer.byteLength(content, "utf-8");
1423
+ filesExported++;
1424
+ }
1425
+ if (fs9.existsSync(statsFile)) {
1426
+ const content = fs9.readFileSync(statsFile, "utf-8");
1427
+ writeAndStage(worktreeDir, "stats.json", content);
1428
+ totalSizeBytes += Buffer.byteLength(content, "utf-8");
1429
+ filesExported++;
1430
+ }
1431
+ if (fs9.existsSync(lanceDir)) {
1432
+ const result = copyDirRecursive(lanceDir, worktreeDir, "index/lance");
1433
+ filesExported += result.fileCount;
1434
+ totalSizeBytes += result.totalBytes;
1435
+ }
1436
+ const exportMeta = {
1437
+ exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
1438
+ indexVersion: 1,
1439
+ vectorStoreFormatVersion: 1,
1440
+ engine: "lancedb"
1441
+ };
1442
+ writeAndStage(worktreeDir, "export-meta.json", JSON.stringify(exportMeta, null, 2) + "\n");
1443
+ filesExported++;
1444
+ const readme = [
1445
+ "# ULPI Agent Memory",
1446
+ "",
1447
+ "This branch stores agent memory data for this repository.",
1448
+ "It is maintained automatically by [ULPI](https://github.com/ulpi-io/ulpi).",
1449
+ "",
1450
+ "## Contents",
1451
+ "",
1452
+ "- `memories.json` \u2014 Memory entries + config snapshot",
1453
+ "- `config.json` \u2014 Memory configuration",
1454
+ "- `stats.json` \u2014 Memory statistics",
1455
+ "- `index/lance/` \u2014 LanceDB vector index",
1456
+ "- `export-meta.json` \u2014 Export metadata",
1457
+ "",
1458
+ `_Exported: ${exportMeta.exportedAt} \u2014 ${entries.length} memories_`
1459
+ ].join("\n") + "\n";
1460
+ writeAndStage(worktreeDir, "README.md", readme);
1461
+ filesExported++;
1462
+ return commitInWorktree(worktreeDir, `memory: export ${entries.length} memories (${filesExported} files)`);
1463
+ });
1464
+ const commitMessage = `memory: export ${entries.length} memories (${filesExported} files)`;
1465
+ const tree = execFileSync2("git", ["rev-parse", `${commitSha}^{tree}`], {
1466
+ cwd: projectDir,
1467
+ encoding: "utf-8",
1468
+ timeout: 5e3
1469
+ }).trim();
1470
+ const orphanSha = execFileSync2("git", ["commit-tree", tree, "-m", commitMessage], {
1471
+ cwd: projectDir,
1472
+ encoding: "utf-8",
1473
+ timeout: 5e3
1474
+ }).trim();
1475
+ execFileSync2("git", ["update-ref", `refs/heads/${branchName}`, orphanSha], {
1476
+ cwd: projectDir,
1477
+ timeout: 5e3
1478
+ });
1479
+ return {
1480
+ branchName,
1481
+ commitSha: orphanSha,
1482
+ memoriesExported: entries.length,
1483
+ filesExported,
1484
+ totalSizeBytes
1401
1485
  };
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 {
1486
+ }
1487
+ function copyDirRecursive(srcDir, worktreeDir, prefix) {
1488
+ let fileCount = 0;
1489
+ let totalBytes = 0;
1490
+ const dirEntries = fs9.readdirSync(srcDir);
1491
+ for (const entry of dirEntries) {
1492
+ const srcPath = path9.join(srcDir, entry);
1493
+ const destRelative = `${prefix}/${entry}`;
1494
+ const stat = fs9.statSync(srcPath);
1495
+ if (stat.isDirectory()) {
1496
+ const sub = copyDirRecursive(srcPath, worktreeDir, destRelative);
1497
+ fileCount += sub.fileCount;
1498
+ totalBytes += sub.totalBytes;
1499
+ } else if (stat.isFile()) {
1500
+ copyAndStage(worktreeDir, destRelative, srcPath);
1501
+ totalBytes += stat.size;
1502
+ fileCount++;
1431
1503
  }
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 {
1504
+ }
1505
+ return { fileCount, totalBytes };
1506
+ }
1507
+ async function importMemories(projectDir) {
1508
+ const branchName = getMemoryBranch();
1509
+ if (!memoryBranchExists(projectDir)) {
1510
+ return { success: false, memoriesImported: 0, filesImported: 0, totalSizeBytes: 0, message: "Memory branch does not exist" };
1511
+ }
1512
+ const memDir = projectMemoryDir(projectDir);
1513
+ let memoriesImported = 0;
1514
+ let filesImported = 0;
1515
+ let totalSizeBytes = 0;
1516
+ await withWorktree(projectDir, branchName, (worktreeDir) => {
1517
+ const memoriesPath = path9.join(worktreeDir, "memories.json");
1518
+ if (fs9.existsSync(memoriesPath)) {
1519
+ const raw = fs9.readFileSync(memoriesPath, "utf-8");
1520
+ const exportData = JSON.parse(raw);
1521
+ totalSizeBytes += Buffer.byteLength(raw, "utf-8");
1522
+ filesImported++;
1523
+ if (exportData.memories && Array.isArray(exportData.memories)) {
1524
+ const entriesDir = memoryEntriesDir(projectDir);
1525
+ fs9.mkdirSync(entriesDir, { recursive: true });
1526
+ for (const entry of exportData.memories) {
1527
+ const filePath = path9.join(entriesDir, `${entry.id}.json`);
1528
+ if (!fs9.existsSync(filePath)) {
1529
+ fs9.writeFileSync(filePath, JSON.stringify(entry, null, 2) + "\n", "utf-8");
1530
+ memoriesImported++;
1531
+ }
1532
+ }
1458
1533
  }
1459
1534
  }
1535
+ const configSrc = path9.join(worktreeDir, "config.json");
1536
+ const configDest = memoryConfigFile(projectDir);
1537
+ if (fs9.existsSync(configSrc) && !fs9.existsSync(configDest)) {
1538
+ fs9.mkdirSync(path9.dirname(configDest), { recursive: true });
1539
+ const content = fs9.readFileSync(configSrc);
1540
+ fs9.writeFileSync(configDest, content);
1541
+ totalSizeBytes += content.length;
1542
+ filesImported++;
1543
+ }
1544
+ const statsSrc = path9.join(worktreeDir, "stats.json");
1545
+ const statsDest = memoryStatsFile(projectDir);
1546
+ if (fs9.existsSync(statsSrc) && !fs9.existsSync(statsDest)) {
1547
+ const content = fs9.readFileSync(statsSrc);
1548
+ fs9.writeFileSync(statsDest, content);
1549
+ totalSizeBytes += content.length;
1550
+ filesImported++;
1551
+ }
1552
+ const lanceSrc = path9.join(worktreeDir, "index", "lance");
1553
+ const lanceDest = memoryLanceDir(projectDir);
1554
+ if (fs9.existsSync(lanceSrc)) {
1555
+ fs9.mkdirSync(lanceDest, { recursive: true });
1556
+ const result = copyDirRecursiveImport(lanceSrc, lanceDest);
1557
+ filesImported += result.fileCount;
1558
+ totalSizeBytes += result.totalBytes;
1559
+ }
1560
+ });
1561
+ return {
1562
+ success: true,
1563
+ memoriesImported,
1564
+ filesImported,
1565
+ totalSizeBytes,
1566
+ message: memoriesImported > 0 ? `Imported ${memoriesImported} new memories + vector index (${filesImported} files)` : `Vector index restored (${filesImported} files, entries already exist)`
1567
+ };
1568
+ }
1569
+ function copyDirRecursiveImport(srcDir, destDir) {
1570
+ let fileCount = 0;
1571
+ let totalBytes = 0;
1572
+ fs9.mkdirSync(destDir, { recursive: true });
1573
+ const dirEntries = fs9.readdirSync(srcDir);
1574
+ for (const entry of dirEntries) {
1575
+ const srcPath = path9.join(srcDir, entry);
1576
+ const destPath = path9.join(destDir, entry);
1577
+ const stat = fs9.statSync(srcPath);
1578
+ if (stat.isDirectory()) {
1579
+ const sub = copyDirRecursiveImport(srcPath, destPath);
1580
+ fileCount += sub.fileCount;
1581
+ totalBytes += sub.totalBytes;
1582
+ } else if (stat.isFile()) {
1583
+ fs9.copyFileSync(srcPath, destPath);
1584
+ totalBytes += stat.size;
1585
+ fileCount++;
1586
+ }
1460
1587
  }
1588
+ return { fileCount, totalBytes };
1461
1589
  }
1462
- function importMemories(projectDir) {
1463
- const branch = getMemoryBranch();
1464
- if (!memoryBranchExists(projectDir)) {
1465
- return { success: false, memoriesImported: 0, message: "Memory branch does not exist" };
1590
+ function progressFile(projectDir) {
1591
+ return join8(projectMemoryDir(projectDir), "classify-progress.json");
1592
+ }
1593
+ var STALE_THRESHOLD_MS = 2 * 60 * 1e3;
1594
+ function writeClassifyBatchProgress(projectDir, progress) {
1595
+ try {
1596
+ const p = { ...progress, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1597
+ writeFileSync8(progressFile(projectDir), JSON.stringify(p, null, 2));
1598
+ } catch {
1466
1599
  }
1600
+ }
1601
+ function readClassifyBatchProgress(projectDir) {
1602
+ const file = progressFile(projectDir);
1603
+ if (!existsSync10(file)) return null;
1467
1604
  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++;
1605
+ const raw = readFileSync8(file, "utf-8");
1606
+ const data = JSON.parse(raw);
1607
+ if (data.status === "classifying" && data.updatedAt) {
1608
+ const age = Date.now() - new Date(data.updatedAt).getTime();
1609
+ if (age > STALE_THRESHOLD_MS) {
1610
+ return {
1611
+ ...data,
1612
+ status: "error",
1613
+ error: "Classification process appears to have stopped unexpectedly."
1614
+ };
1485
1615
  }
1486
1616
  }
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 };
1617
+ return data;
1618
+ } catch {
1619
+ return null;
1620
+ }
1621
+ }
1622
+ function clearClassifyBatchProgress(projectDir) {
1623
+ try {
1624
+ const file = progressFile(projectDir);
1625
+ if (existsSync10(file)) unlinkSync3(file);
1626
+ } catch {
1495
1627
  }
1496
1628
  }
1497
1629
 
@@ -1542,5 +1674,8 @@ export {
1542
1674
  memoryBranchExists,
1543
1675
  initMemoryBranch,
1544
1676
  exportMemories,
1545
- importMemories
1677
+ importMemories,
1678
+ writeClassifyBatchProgress,
1679
+ readClassifyBatchProgress,
1680
+ clearClassifyBatchProgress
1546
1681
  };