@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.
- package/dist/auth-KQCJ43U2.js +118 -0
- package/dist/{chunk-Q4HIY43N.js → chunk-2VYFVYJL.js} +67 -24
- package/dist/{chunk-DBMUNBNB.js → chunk-5J6NLQUN.js} +149 -19
- package/dist/{chunk-4KRVDKGB.js → chunk-F7OXF7Z3.js} +1 -1
- package/dist/chunk-G6SVZ4Q5.js +122 -0
- package/dist/{chunk-NNUWU6CV.js → chunk-JGBXM5NC.js} +42 -0
- package/dist/{chunk-6JCMYYBT.js → chunk-PDR55ZNW.js} +247 -112
- package/dist/{chunk-247GVVKK.js → chunk-ZLYRPD7I.js} +18 -16
- package/dist/ci-QM57ZCBW.js +367 -0
- package/dist/{codemap-RRJIDBQ5.js → codemap-RKSD4MIE.js} +49 -17
- package/dist/{dist-LZKZFPVX.js → dist-CB5D5LMO.js} +6 -3
- package/dist/{dist-7LHZ65GC.js → dist-CS2VKNYS.js} +5 -4
- package/dist/{dist-R5F4MX3I.js → dist-GJYT2OQV.js} +11 -4
- package/dist/{dist-RJGCUS3L.js → dist-QAU3LGJN.js} +3 -1
- package/dist/{dist-W7K4WPAF.js → dist-UKMCJBB2.js} +42 -14
- package/dist/{dist-R5ZJ4LX5.js → dist-YA2BWZB2.js} +1 -1
- package/dist/{history-Q2LDADFW.js → history-NFNA4HE5.js} +13 -7
- package/dist/index.js +50 -21
- package/dist/{init-AY5C2ZAS.js → init-6CH4HV5T.js} +2 -2
- package/dist/{memory-J3G24QHS.js → memory-Y6OZTXJ2.js} +231 -22
- package/dist/{server-MOYPE4SM-N7SE2AN7.js → server-USLHY6GH-AEOJC5ST.js} +2 -2
- package/dist/skills/ulpi-generate-guardian/SKILL.md +246 -7
- package/dist/skills/ulpi-generate-guardian/references/framework-rules.md +161 -4
- package/dist/skills/ulpi-generate-guardian/references/language-rules.md +13 -18
- package/dist/{ui-L7UAWXDY.js → ui-OWXZ3YSR.js} +3 -3
- package/dist/ui.html +112 -112
- package/dist/{update-DJ227LL3.js → update-WUITQX4Z.js} +1 -1
- package/dist/{version-checker-M37KI7DY.js → version-checker-SMAYSN7Y.js} +1 -1
- package/package.json +31 -28
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
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
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
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 =
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
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
|
};
|