chat-recall 0.7.1 → 0.7.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/cli.js +97 -65
- package/dist/code-index-cursor.d.ts +20 -3
- package/dist/code-index-cursor.d.ts.map +1 -1
- package/dist/code-index-cursor.js +53 -8
- package/dist/code-index-cursor.js.map +1 -1
- package/dist/mcp-relay.js +1 -1
- package/dist/mcp.js +96 -65
- package/dist/scan-worker.js +78 -52
- package/dist/sync-client.js +3 -3
- package/dist/sync-client.js.map +1 -1
- package/dist/watch.js +167 -93
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7736,23 +7736,8 @@ var init_opencode = __esm({
|
|
|
7736
7736
|
const projectPath = row.project_path || row.directory || "";
|
|
7737
7737
|
if (filter && !projectPath.toLowerCase().includes(filter))
|
|
7738
7738
|
continue;
|
|
7739
|
-
const
|
|
7740
|
-
|
|
7741
|
-
FROM part p JOIN message m ON m.id = p.message_id
|
|
7742
|
-
WHERE p.session_id = ?
|
|
7743
|
-
AND p.data LIKE '%"type":"text"%'
|
|
7744
|
-
AND m.data LIKE '%"role":"user"%'
|
|
7745
|
-
ORDER BY p.time_created ASC LIMIT 1
|
|
7746
|
-
`).get(row.id);
|
|
7747
|
-
let firstPrompt = "";
|
|
7748
|
-
if (firstUser) {
|
|
7749
|
-
try {
|
|
7750
|
-
const parsed = JSON.parse(firstUser.data);
|
|
7751
|
-
firstPrompt = flatString(String(parsed?.text || "").slice(0, 200));
|
|
7752
|
-
} catch {
|
|
7753
|
-
}
|
|
7754
|
-
}
|
|
7755
|
-
const messageCount = db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
|
|
7739
|
+
const firstPrompt = opts.previews === false ? "" : this.firstUserPrompt(db, row.id);
|
|
7740
|
+
const messageCount = opts.previews === false ? 0 : db.prepare("SELECT COUNT(*) as c FROM message WHERE session_id = ?").get(row.id)?.c ?? 0;
|
|
7756
7741
|
const created = new Date(row.time_created || row.time_updated).toISOString();
|
|
7757
7742
|
const modified = new Date(row.time_updated).toISOString();
|
|
7758
7743
|
out.push({
|
|
@@ -7776,6 +7761,45 @@ var init_opencode = __esm({
|
|
|
7776
7761
|
db.close();
|
|
7777
7762
|
}
|
|
7778
7763
|
}
|
|
7764
|
+
/**
|
|
7765
|
+
* The first user text part of a session.
|
|
7766
|
+
*
|
|
7767
|
+
* Walks the session's user messages oldest first through the
|
|
7768
|
+
* (session_id, time_created, id) index and stops at the first one with a
|
|
7769
|
+
* text part. The role and the text are read by SQLite: a first message can
|
|
7770
|
+
* carry whole diffs in `summary` (107 MB on one row), and parsing that in JS
|
|
7771
|
+
* to read one field took the process to 510 MB. The earlier query filtered
|
|
7772
|
+
* every part and message with LIKE and then sorted, so it read all of a
|
|
7773
|
+
* session's data: 423 MB for a 236-session listing.
|
|
7774
|
+
*/
|
|
7775
|
+
firstUserPrompt(db, sessionId) {
|
|
7776
|
+
try {
|
|
7777
|
+
const has = (table, col) => !!db.prepare(`SELECT 1 FROM pragma_table_info(?) WHERE name = ?`).get(table, col);
|
|
7778
|
+
const msgOrder = has("message", "time_created") ? "time_created, id" : "rowid";
|
|
7779
|
+
const partOrder = has("part", "time_created") ? "time_created, id" : "rowid";
|
|
7780
|
+
const nextUser = db.prepare(`SELECT id, ${msgOrder === "rowid" ? "rowid AS k1, 0 AS k2" : "time_created AS k1, id AS k2"} FROM message
|
|
7781
|
+
WHERE session_id = ? AND (${msgOrder === "rowid" ? "rowid, 0" : "time_created, id"}) > (?, ?)
|
|
7782
|
+
AND json_extract(data, '$.role') = 'user'
|
|
7783
|
+
ORDER BY ${msgOrder} LIMIT 1`);
|
|
7784
|
+
const firstText = db.prepare(`SELECT substr(json_extract(data, '$.text'), 1, 400) AS text FROM part
|
|
7785
|
+
WHERE message_id = ? AND json_extract(data, '$.type') = 'text'
|
|
7786
|
+
AND length(trim(json_extract(data, '$.text'))) > 0
|
|
7787
|
+
ORDER BY ${partOrder} LIMIT 1`);
|
|
7788
|
+
let k1 = Number.MIN_SAFE_INTEGER, k2 = msgOrder === "rowid" ? 0 : "";
|
|
7789
|
+
for (; ; ) {
|
|
7790
|
+
const m = nextUser.get(sessionId, k1, k2);
|
|
7791
|
+
if (!m)
|
|
7792
|
+
return "";
|
|
7793
|
+
k1 = m.k1;
|
|
7794
|
+
k2 = m.k2;
|
|
7795
|
+
const t = firstText.get(m.id);
|
|
7796
|
+
if (t?.text)
|
|
7797
|
+
return flatString(t.text.slice(0, 200));
|
|
7798
|
+
}
|
|
7799
|
+
} catch {
|
|
7800
|
+
return "";
|
|
7801
|
+
}
|
|
7802
|
+
}
|
|
7779
7803
|
/** OpenCode auto-generates a session title (model-written, distinct from the
|
|
7780
7804
|
* first prompt — e.g. "Replacing Claude with Gemini CLI"). It's a plain
|
|
7781
7805
|
* column on the session row. */
|
|
@@ -8409,28 +8433,29 @@ var init_codex2 = __esm({
|
|
|
8409
8433
|
continue;
|
|
8410
8434
|
let firstPrompt = "";
|
|
8411
8435
|
let messageCount = 0;
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
firstPrompt
|
|
8436
|
+
if (opts.previews !== false)
|
|
8437
|
+
try {
|
|
8438
|
+
const lines = readFileSync3(filePath, "utf-8").split("\n");
|
|
8439
|
+
for (const line of lines) {
|
|
8440
|
+
if (!line.trim())
|
|
8441
|
+
continue;
|
|
8442
|
+
let ev;
|
|
8443
|
+
try {
|
|
8444
|
+
ev = JSON.parse(line);
|
|
8445
|
+
} catch {
|
|
8446
|
+
continue;
|
|
8447
|
+
}
|
|
8448
|
+
if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
|
|
8449
|
+
messageCount++;
|
|
8450
|
+
if (!firstPrompt && ev.payload.message) {
|
|
8451
|
+
firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
|
|
8452
|
+
}
|
|
8453
|
+
} else if (ev.type === "response_item" && ev.payload?.type === "message") {
|
|
8454
|
+
messageCount++;
|
|
8427
8455
|
}
|
|
8428
|
-
} else if (ev.type === "response_item" && ev.payload?.type === "message") {
|
|
8429
|
-
messageCount++;
|
|
8430
8456
|
}
|
|
8457
|
+
} catch {
|
|
8431
8458
|
}
|
|
8432
|
-
} catch {
|
|
8433
|
-
}
|
|
8434
8459
|
const prior = seen.get(rawId);
|
|
8435
8460
|
if (prior) {
|
|
8436
8461
|
if (stat3.mtimeMs > prior.mtime) {
|
|
@@ -9120,21 +9145,22 @@ var init_agy = __esm({
|
|
|
9120
9145
|
const projectDir = projectPath ? basename8(projectPath) : "";
|
|
9121
9146
|
let firstPrompt = "";
|
|
9122
9147
|
let messageCount = 0;
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
const
|
|
9128
|
-
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9148
|
+
if (opts.previews !== false)
|
|
9149
|
+
try {
|
|
9150
|
+
const lines = readFileSync4(filePath, "utf-8").split("\n").filter(Boolean);
|
|
9151
|
+
messageCount = lines.length;
|
|
9152
|
+
for (const line of lines) {
|
|
9153
|
+
const obj = JSON.parse(line);
|
|
9154
|
+
if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
|
|
9155
|
+
const content = obj.content;
|
|
9156
|
+
const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
|
|
9157
|
+
const rawText = match ? match[1].trim() : content.trim();
|
|
9158
|
+
firstPrompt = flatString(rawText.slice(0, 200));
|
|
9159
|
+
break;
|
|
9160
|
+
}
|
|
9134
9161
|
}
|
|
9162
|
+
} catch {
|
|
9135
9163
|
}
|
|
9136
|
-
} catch {
|
|
9137
|
-
}
|
|
9138
9164
|
if (seenIds.has(rawId))
|
|
9139
9165
|
continue;
|
|
9140
9166
|
seenIds.add(rawId);
|
|
@@ -11409,12 +11435,12 @@ var init_claude2 = __esm({
|
|
|
11409
11435
|
const seen = /* @__PURE__ */ new Set();
|
|
11410
11436
|
const out = [];
|
|
11411
11437
|
for (const root of claudeProjectDirs()) {
|
|
11412
|
-
this.collectSessionsFromRoot(root, cutoff, filter, seen, out);
|
|
11438
|
+
this.collectSessionsFromRoot(root, cutoff, filter, seen, out, opts.previews !== false);
|
|
11413
11439
|
}
|
|
11414
11440
|
out.sort((a, b) => b.mtime - a.mtime);
|
|
11415
11441
|
return opts.limit ? out.slice(0, opts.limit) : out;
|
|
11416
11442
|
}
|
|
11417
|
-
collectSessionsFromRoot(root, cutoff, filter, seen, out) {
|
|
11443
|
+
collectSessionsFromRoot(root, cutoff, filter, seen, out, previews) {
|
|
11418
11444
|
if (!existsSync15(root))
|
|
11419
11445
|
return;
|
|
11420
11446
|
for (const proj of readdirSync11(root, { withFileTypes: true })) {
|
|
@@ -11473,7 +11499,7 @@ var init_claude2 = __esm({
|
|
|
11473
11499
|
}
|
|
11474
11500
|
if (stat3.mtimeMs < cutoff)
|
|
11475
11501
|
continue;
|
|
11476
|
-
const firstPrompt = extractFirstUserPromptSync(fullPath, { maxLength: 200 });
|
|
11502
|
+
const firstPrompt = previews ? extractFirstUserPromptSync(fullPath, { maxLength: 200 }) : "";
|
|
11477
11503
|
out.push({
|
|
11478
11504
|
toolId: "claude",
|
|
11479
11505
|
rawId: sessionId,
|
|
@@ -11893,6 +11919,7 @@ function loadProjectsConfig() {
|
|
|
11893
11919
|
}
|
|
11894
11920
|
function resetProjectResolverCache() {
|
|
11895
11921
|
resolveCache.clear();
|
|
11922
|
+
inputCache.clear();
|
|
11896
11923
|
gitCache.clear();
|
|
11897
11924
|
workspaceCache.clear();
|
|
11898
11925
|
wsIdCache.clear();
|
|
@@ -11909,12 +11936,13 @@ function resolveProjectId(inputPath) {
|
|
|
11909
11936
|
if (!isAbsolute(inputPath)) {
|
|
11910
11937
|
return { id: `path:${inputPath}`, displayName: inputPath, source: "path" };
|
|
11911
11938
|
}
|
|
11939
|
+
const known = inputCache.get(inputPath);
|
|
11940
|
+
if (known)
|
|
11941
|
+
return known;
|
|
11912
11942
|
const realPath = safeRealpath(inputPath);
|
|
11913
|
-
const
|
|
11914
|
-
if (cached)
|
|
11915
|
-
return cached;
|
|
11916
|
-
const result = doResolve(realPath);
|
|
11943
|
+
const result = resolveCache.get(realPath) ?? doResolve(realPath);
|
|
11917
11944
|
resolveCache.set(realPath, result);
|
|
11945
|
+
inputCache.set(inputPath, result);
|
|
11918
11946
|
return result;
|
|
11919
11947
|
}
|
|
11920
11948
|
function resolveWorkspaceId(inputPath) {
|
|
@@ -12215,7 +12243,7 @@ function globToRegex(glob) {
|
|
|
12215
12243
|
function escapeRe(s) {
|
|
12216
12244
|
return s.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
12217
12245
|
}
|
|
12218
|
-
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
12246
|
+
var DEFAULT_AUTO_WORKSPACE_MIN_REPOS, BOT_WORKTREE_MARKERS, resolveCache, inputCache, gitCache, workspaceCache, wsIdCache, cachedConfig, GIT_TIMEOUT_MS, AUTO_WORKSPACE_MAX_DEPTH;
|
|
12219
12247
|
var init_project_resolver = __esm({
|
|
12220
12248
|
"../engine/dist/core/project-resolver.js"() {
|
|
12221
12249
|
"use strict";
|
|
@@ -12223,6 +12251,7 @@ var init_project_resolver = __esm({
|
|
|
12223
12251
|
DEFAULT_AUTO_WORKSPACE_MIN_REPOS = 3;
|
|
12224
12252
|
BOT_WORKTREE_MARKERS = [".claude-pr-bot"];
|
|
12225
12253
|
resolveCache = /* @__PURE__ */ new Map();
|
|
12254
|
+
inputCache = /* @__PURE__ */ new Map();
|
|
12226
12255
|
gitCache = /* @__PURE__ */ new Map();
|
|
12227
12256
|
workspaceCache = /* @__PURE__ */ new Map();
|
|
12228
12257
|
wsIdCache = /* @__PURE__ */ new Map();
|
|
@@ -18512,7 +18541,7 @@ var init_http = __esm({
|
|
|
18512
18541
|
CLI_VERSION = JSON.parse(readFileSync31(new URL("../package.json", import.meta.url), "utf-8")).version || CLI_VERSION;
|
|
18513
18542
|
} catch {
|
|
18514
18543
|
try {
|
|
18515
|
-
if (true) CLI_VERSION = "0.7.
|
|
18544
|
+
if (true) CLI_VERSION = "0.7.3";
|
|
18516
18545
|
} catch {
|
|
18517
18546
|
}
|
|
18518
18547
|
}
|
|
@@ -19011,7 +19040,7 @@ var init_telemetry = __esm({
|
|
|
19011
19040
|
init_sync_client();
|
|
19012
19041
|
cliVersion2 = () => {
|
|
19013
19042
|
try {
|
|
19014
|
-
return true ? "0.7.
|
|
19043
|
+
return true ? "0.7.3" : "dev";
|
|
19015
19044
|
} catch {
|
|
19016
19045
|
return "dev";
|
|
19017
19046
|
}
|
|
@@ -20046,7 +20075,7 @@ async function reconcileFields(opts = {}) {
|
|
|
20046
20075
|
let cached = null;
|
|
20047
20076
|
const fullRefs = () => cached ??= listAvailableBackends().flatMap((b) => {
|
|
20048
20077
|
try {
|
|
20049
|
-
return b.listSessions({});
|
|
20078
|
+
return b.listSessions({ previews: false });
|
|
20050
20079
|
} catch {
|
|
20051
20080
|
return [];
|
|
20052
20081
|
}
|
|
@@ -20209,7 +20238,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20209
20238
|
const listSinceMs = changedWalk ? Math.max(0, (opts.sinceMs ?? 0) - CHANGED_WALK_OVERLAP_MS) : opts.useLedger ? void 0 : opts.sinceMs;
|
|
20210
20239
|
const refs = listAvailableBackends().flatMap((b) => {
|
|
20211
20240
|
try {
|
|
20212
|
-
return b.listSessions({ sinceMs: listSinceMs });
|
|
20241
|
+
return b.listSessions({ sinceMs: listSinceMs, previews: false });
|
|
20213
20242
|
} catch {
|
|
20214
20243
|
return [];
|
|
20215
20244
|
}
|
|
@@ -20524,7 +20553,7 @@ async function syncToTargetInScope(cred, opts = {}) {
|
|
|
20524
20553
|
cred,
|
|
20525
20554
|
() => listAvailableBackends().flatMap((b) => {
|
|
20526
20555
|
try {
|
|
20527
|
-
return b.listSessions({});
|
|
20556
|
+
return b.listSessions({ previews: false });
|
|
20528
20557
|
} catch {
|
|
20529
20558
|
return [];
|
|
20530
20559
|
}
|
|
@@ -21291,7 +21320,7 @@ var init_sync_client = __esm({
|
|
|
21291
21320
|
ownVersion = JSON.parse(readFileSync35(join42(dir, "../package.json"), "utf-8")).version || ownVersion;
|
|
21292
21321
|
} catch {
|
|
21293
21322
|
try {
|
|
21294
|
-
if (true) ownVersion = "0.7.
|
|
21323
|
+
if (true) ownVersion = "0.7.3";
|
|
21295
21324
|
} catch {
|
|
21296
21325
|
}
|
|
21297
21326
|
}
|
|
@@ -24082,7 +24111,10 @@ async function resolveCodeindexBin(autoInstall = true) {
|
|
|
24082
24111
|
}
|
|
24083
24112
|
function runMcp(binPath, workspace, calls, timeoutMs = 18e4) {
|
|
24084
24113
|
return new Promise((resolve3, reject) => {
|
|
24085
|
-
const p = spawn3(binPath, ["--mcp", "--workspace", workspace], {
|
|
24114
|
+
const p = spawn3(binPath, ["--mcp", "--workspace", workspace], {
|
|
24115
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
24116
|
+
env: { ...process.env, CODEINDEX_NO_DAEMON: "1" }
|
|
24117
|
+
});
|
|
24086
24118
|
const res = /* @__PURE__ */ new Map();
|
|
24087
24119
|
let pending = "";
|
|
24088
24120
|
let failed = false;
|
|
@@ -25422,7 +25454,7 @@ var init_intent_drain = __esm({
|
|
|
25422
25454
|
ownVersion2 = JSON.parse(readFileSync45(join56(dir, "../package.json"), "utf-8")).version || ownVersion2;
|
|
25423
25455
|
} catch {
|
|
25424
25456
|
try {
|
|
25425
|
-
if (true) ownVersion2 = "0.7.
|
|
25457
|
+
if (true) ownVersion2 = "0.7.3";
|
|
25426
25458
|
} catch {
|
|
25427
25459
|
}
|
|
25428
25460
|
}
|
|
@@ -1,8 +1,25 @@
|
|
|
1
|
-
/**
|
|
2
|
-
export
|
|
1
|
+
/** One completed index: when, and the workspace fingerprint it saw. */
|
|
2
|
+
export interface CursorEntry {
|
|
3
|
+
at: number;
|
|
4
|
+
fp?: string;
|
|
5
|
+
}
|
|
6
|
+
/** workspace path → the last COMPLETED index. */
|
|
7
|
+
export type CodeIndexCursor = Record<string, CursorEntry>;
|
|
3
8
|
export declare function readCursor(): CodeIndexCursor;
|
|
4
9
|
/** Record a COMPLETED index. Never called for a failure — see the header. */
|
|
5
|
-
export declare function noteIndexed(workspace: string, now?: number): void;
|
|
10
|
+
export declare function noteIndexed(workspace: string, now?: number, fp?: string | null): void;
|
|
11
|
+
/**
|
|
12
|
+
* What the code in a git workspace is right now: HEAD, plus each changed or
|
|
13
|
+
* untracked file with its size and mtime, plus the collector's own version so
|
|
14
|
+
* a new result format rescans everything. Null when the workspace is not a git
|
|
15
|
+
* repository or git does not answer; such a workspace is scanned every pass.
|
|
16
|
+
*/
|
|
17
|
+
export declare function workspaceFingerprint(workspace: string, collectorVersion: number): string | null;
|
|
18
|
+
/** An unchanged workspace is still sent again after this long. The collector
|
|
19
|
+
* cannot see the server's copy, and a server that lost it gets it back. */
|
|
20
|
+
export declare const UNCHANGED_RESEND_MS: number;
|
|
21
|
+
/** True when the last completed index saw this same fingerprint recently. */
|
|
22
|
+
export declare function isUnchangedSinceIndexed(workspace: string, fp: string | null, cursor: CodeIndexCursor, now?: number): boolean;
|
|
6
23
|
/**
|
|
7
24
|
* Order workspaces least-recently-indexed first, so a sweep that keeps getting
|
|
8
25
|
* interrupted still eventually covers everything.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-index-cursor.d.ts","sourceRoot":"","sources":["../src/code-index-cursor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"code-index-cursor.d.ts","sourceRoot":"","sources":["../src/code-index-cursor.ts"],"names":[],"mappings":"AAkCA,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAAG,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE;AAExD,iDAAiD;AACjD,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAE1D,wBAAgB,UAAU,IAAI,eAAe,CAiB5C;AAUD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAIzF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkB/F;AAED;4EAC4E;AAC5E,eAAO,MAAM,mBAAmB,QAAuB,CAAC;AAExD,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,GAAG,SAAa,GAAG,OAAO,CAGhI;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,MAAM,kBAAe,GAAG,MAAM,EAAE,CAKtF;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAQjD"}
|
|
@@ -16,13 +16,17 @@
|
|
|
16
16
|
* which is what this cursor provides.
|
|
17
17
|
*
|
|
18
18
|
* ── Shape ────────────────────────────────────────────────────────────────
|
|
19
|
-
* A tiny JSON map of workspace path → last successful index
|
|
19
|
+
* A tiny JSON map of workspace path → last successful index (its time and the
|
|
20
|
+
* workspace fingerprint it saw, so a pass skips code that did not change).
|
|
21
|
+
* Best-effort in both
|
|
20
22
|
* directions: a missing or corrupt file just means "nothing indexed yet", and a
|
|
21
23
|
* write that fails is ignored. It records COMPLETIONS only, so a workspace that
|
|
22
24
|
* crashes the indexer is retried rather than being marked done — the opposite
|
|
23
25
|
* choice would silently drop a repo forever.
|
|
24
26
|
*/
|
|
25
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
27
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
|
|
28
|
+
import { execFileSync } from 'child_process';
|
|
29
|
+
import { createHash } from 'crypto';
|
|
26
30
|
import { join, dirname } from 'path';
|
|
27
31
|
import { getDataDir } from '@chat-recall/engine/core/paths.js';
|
|
28
32
|
const cursorPath = () => join(getDataDir(), 'code-index-cursor.json');
|
|
@@ -32,8 +36,13 @@ export function readCursor() {
|
|
|
32
36
|
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
33
37
|
const out = {};
|
|
34
38
|
for (const [k, v] of Object.entries(raw)) {
|
|
39
|
+
// A bare number is the file an earlier version wrote: a time, no fingerprint.
|
|
35
40
|
if (typeof v === 'number' && Number.isFinite(v))
|
|
36
|
-
out[k] = v;
|
|
41
|
+
out[k] = { at: v };
|
|
42
|
+
else if (v && typeof v === 'object' && typeof v.at === 'number' && Number.isFinite(v.at)) {
|
|
43
|
+
const fp = v.fp;
|
|
44
|
+
out[k] = typeof fp === 'string' ? { at: v.at, fp } : { at: v.at };
|
|
45
|
+
}
|
|
37
46
|
}
|
|
38
47
|
return out;
|
|
39
48
|
}
|
|
@@ -50,13 +59,49 @@ function writeCursor(c) {
|
|
|
50
59
|
catch { /* housekeeping must never fail a sweep */ }
|
|
51
60
|
}
|
|
52
61
|
/** Record a COMPLETED index. Never called for a failure — see the header. */
|
|
53
|
-
export function noteIndexed(workspace, now = Date.now()) {
|
|
62
|
+
export function noteIndexed(workspace, now = Date.now(), fp) {
|
|
54
63
|
const c = readCursor();
|
|
55
|
-
c[workspace] = now;
|
|
56
|
-
// Forget paths that are no longer candidates, so a machine that has moved on
|
|
57
|
-
// does not carry a growing map of dead repos forever.
|
|
64
|
+
c[workspace] = fp ? { at: now, fp } : { at: now };
|
|
58
65
|
writeCursor(c);
|
|
59
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* What the code in a git workspace is right now: HEAD, plus each changed or
|
|
69
|
+
* untracked file with its size and mtime, plus the collector's own version so
|
|
70
|
+
* a new result format rescans everything. Null when the workspace is not a git
|
|
71
|
+
* repository or git does not answer; such a workspace is scanned every pass.
|
|
72
|
+
*/
|
|
73
|
+
export function workspaceFingerprint(workspace, collectorVersion) {
|
|
74
|
+
try {
|
|
75
|
+
const git = (args) => execFileSync('git', ['-C', workspace, ...args], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 16 * 1024 * 1024, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
76
|
+
const head = git(['rev-parse', 'HEAD']).trim();
|
|
77
|
+
const status = git(['status', '--porcelain=v1', '-z', '--untracked-files=all']);
|
|
78
|
+
const h = createHash('sha256').update(`v${collectorVersion}\0${head}\0`);
|
|
79
|
+
for (const entry of status.split('\0')) {
|
|
80
|
+
if (entry.length < 4)
|
|
81
|
+
continue;
|
|
82
|
+
const rel = entry.slice(3);
|
|
83
|
+
let stamp = 'gone';
|
|
84
|
+
try {
|
|
85
|
+
const s = statSync(join(workspace, rel));
|
|
86
|
+
stamp = `${s.size}:${Math.floor(s.mtimeMs)}`;
|
|
87
|
+
}
|
|
88
|
+
catch { /* deleted */ }
|
|
89
|
+
h.update(`${entry.slice(0, 2)}\0${rel}\0${stamp}\0`);
|
|
90
|
+
}
|
|
91
|
+
return h.digest('hex');
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** An unchanged workspace is still sent again after this long. The collector
|
|
98
|
+
* cannot see the server's copy, and a server that lost it gets it back. */
|
|
99
|
+
export const UNCHANGED_RESEND_MS = 7 * 24 * 3600 * 1000;
|
|
100
|
+
/** True when the last completed index saw this same fingerprint recently. */
|
|
101
|
+
export function isUnchangedSinceIndexed(workspace, fp, cursor, now = Date.now()) {
|
|
102
|
+
const e = cursor[workspace];
|
|
103
|
+
return !!fp && !!e && e.fp === fp && now - e.at < UNCHANGED_RESEND_MS;
|
|
104
|
+
}
|
|
60
105
|
/**
|
|
61
106
|
* Order workspaces least-recently-indexed first, so a sweep that keeps getting
|
|
62
107
|
* interrupted still eventually covers everything.
|
|
@@ -68,7 +113,7 @@ export function noteIndexed(workspace, now = Date.now()) {
|
|
|
68
113
|
*/
|
|
69
114
|
export function orderByStaleness(workspaces, cursor = readCursor()) {
|
|
70
115
|
return workspaces
|
|
71
|
-
.map((w, i) => ({ w, i, at: cursor[w] ?? -1 }))
|
|
116
|
+
.map((w, i) => ({ w, i, at: cursor[w]?.at ?? -1 }))
|
|
72
117
|
.sort((a, b) => (a.at - b.at) || (a.i - b.i))
|
|
73
118
|
.map((e) => e.w);
|
|
74
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-index-cursor.js","sourceRoot":"","sources":["../src/code-index-cursor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"code-index-cursor.js","sourceRoot":"","sources":["../src/code-index-cursor.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp-relay.js
CHANGED
|
@@ -192,7 +192,7 @@ function bridge(input, output, sock, exit, opts = {}) {
|
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// src/mcp-relay.ts
|
|
195
|
-
var VERSION = true ? "0.7.
|
|
195
|
+
var VERSION = true ? "0.7.3" : "0.0.0";
|
|
196
196
|
var SERVER_URL = new URL("./mcp.js", import.meta.url);
|
|
197
197
|
var SERVER_PATH = fileURLToPath(SERVER_URL);
|
|
198
198
|
var START_TIMEOUT_MS = 15e3;
|