chat-recall 0.7.1 → 0.7.2

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 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 firstUser = db.prepare(`
7740
- SELECT p.data
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
- try {
8413
- const lines = readFileSync3(filePath, "utf-8").split("\n");
8414
- for (const line of lines) {
8415
- if (!line.trim())
8416
- continue;
8417
- let ev;
8418
- try {
8419
- ev = JSON.parse(line);
8420
- } catch {
8421
- continue;
8422
- }
8423
- if (ev.type === "event_msg" && ev.payload?.type === "user_message") {
8424
- messageCount++;
8425
- if (!firstPrompt && ev.payload.message) {
8426
- firstPrompt = flatString(String(ev.payload.message).slice(0, 200));
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
- try {
9124
- const lines = readFileSync4(filePath, "utf-8").split("\n").filter(Boolean);
9125
- messageCount = lines.length;
9126
- for (const line of lines) {
9127
- const obj = JSON.parse(line);
9128
- if (obj.source === "USER_EXPLICIT" && obj.type === "USER_INPUT" && typeof obj.content === "string") {
9129
- const content = obj.content;
9130
- const match = content.match(/<USER_REQUEST>([\s\S]*?)<\/USER_REQUEST>/);
9131
- const rawText = match ? match[1].trim() : content.trim();
9132
- firstPrompt = flatString(rawText.slice(0, 200));
9133
- break;
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 cached = resolveCache.get(realPath);
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.1";
18544
+ if (true) CLI_VERSION = "0.7.2";
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.1" : "dev";
19043
+ return true ? "0.7.2" : "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.1";
21323
+ if (true) ownVersion = "0.7.2";
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], { stdio: ["pipe", "pipe", "ignore"] });
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.1";
25457
+ if (true) ownVersion2 = "0.7.2";
25426
25458
  } catch {
25427
25459
  }
25428
25460
  }
@@ -1,8 +1,25 @@
1
- /** workspace path → epoch ms of the last COMPLETED index. */
2
- export type CodeIndexCursor = Record<string, number>;
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":"AA8BA,6DAA6D;AAC7D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAErD,wBAAgB,UAAU,IAAI,eAAe,CAY5C;AAUD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,SAAa,GAAG,IAAI,CAMrE;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"}
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. Best-effort in both
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":"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"}
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.1" : "0.0.0";
195
+ var VERSION = true ? "0.7.2" : "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;