@wrongstack/tools 0.306.2 → 0.306.4

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/index.js CHANGED
@@ -1205,8 +1205,9 @@ async function resolveRealInsideRoot(absPath, ctx) {
1205
1205
  }
1206
1206
  throw err;
1207
1207
  }
1208
- if (isInsideAny(real, realRoots)) {
1209
- return pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1208
+ const candidate = pendingTail.length > 0 ? path3.join(real, ...pendingTail) : real;
1209
+ if (isInsideAny(candidate, realRoots)) {
1210
+ return candidate;
1210
1211
  }
1211
1212
  throw new Error(
1212
1213
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
@@ -8095,7 +8096,7 @@ var auditTool = {
8095
8096
  "audit: `fix: true` is not supported \u2014 this tool is read-only (mutating: false). To remediate vulnerabilities, upgrade the affected packages with the `install` tool (or `language_package` for non-JS ecosystems)."
8096
8097
  );
8097
8098
  }
8098
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
8099
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
8099
8100
  const bridge = await tryLegacyPackageOperation("package-audit", {
8100
8101
  cwd,
8101
8102
  projectRoot: ctx.projectRoot,
@@ -11503,6 +11504,85 @@ function runSqliteWithRetry(fn) {
11503
11504
  throw lastError;
11504
11505
  }
11505
11506
 
11507
+ // src/codebase-index/vector-search.ts
11508
+ var RRF_K = 60;
11509
+ var VECTOR_DIMENSIONS = 384;
11510
+ var NGRAM_SIZE = 3;
11511
+ function embedText(text) {
11512
+ const vec = new Float32Array(VECTOR_DIMENSIONS);
11513
+ const normalized = text.toLowerCase().trim();
11514
+ if (normalized.length < NGRAM_SIZE) {
11515
+ const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
11516
+ for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
11517
+ const ngram = padded.slice(i, i + NGRAM_SIZE);
11518
+ const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
11519
+ vec[bucket] += 1;
11520
+ }
11521
+ } else {
11522
+ for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
11523
+ const ngram = normalized.slice(i, i + NGRAM_SIZE);
11524
+ const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
11525
+ vec[bucket] += 1;
11526
+ }
11527
+ }
11528
+ let norm = 0;
11529
+ for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
11530
+ norm += vec[i] * vec[i];
11531
+ }
11532
+ norm = Math.sqrt(norm);
11533
+ if (norm > 0) {
11534
+ for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
11535
+ vec[i] /= norm;
11536
+ }
11537
+ }
11538
+ return vec;
11539
+ }
11540
+ function hashNgram(str) {
11541
+ let hash = 2166136261;
11542
+ for (let i = 0; i < str.length; i++) {
11543
+ hash ^= str.charCodeAt(i);
11544
+ hash = Math.imul(hash, 16777619);
11545
+ }
11546
+ return hash >>> 0;
11547
+ }
11548
+ function cosineSimilarity(a, b) {
11549
+ let dot = 0;
11550
+ const len = Math.min(a.length, b.length);
11551
+ for (let i = 0; i < len; i++) {
11552
+ dot += a[i] * b[i];
11553
+ }
11554
+ return dot;
11555
+ }
11556
+ function encodeVector(vec) {
11557
+ return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
11558
+ }
11559
+ function decodeVector(buf) {
11560
+ const view = new DataView(
11561
+ buf.buffer,
11562
+ buf.byteOffset,
11563
+ buf.byteLength
11564
+ );
11565
+ const copy = new Float32Array(buf.byteLength / 4);
11566
+ for (let i = 0; i < copy.length; i++) {
11567
+ copy[i] = view.getFloat32(i * 4, true);
11568
+ }
11569
+ return copy;
11570
+ }
11571
+ function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
11572
+ const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
11573
+ const scored = [];
11574
+ for (const id of allIds) {
11575
+ const bm25Rank = bm25Ranks.get(id);
11576
+ const vecRank = vectorRanks.get(id);
11577
+ let score = 0;
11578
+ if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
11579
+ if (vecRank !== void 0) score += 1 / (k + vecRank);
11580
+ scored.push([id, score]);
11581
+ }
11582
+ scored.sort((a, b) => b[1] - a[1]);
11583
+ return scored;
11584
+ }
11585
+
11506
11586
  // src/codebase-index/writer-admin.ts
11507
11587
  import * as fs9 from "node:fs";
11508
11588
  import * as path14 from "node:path";
@@ -12864,90 +12944,19 @@ var StorePool = class {
12864
12944
  }
12865
12945
  };
12866
12946
 
12867
- // src/codebase-index/vector-search.ts
12868
- var RRF_K = 60;
12869
- var VECTOR_DIMENSIONS = 384;
12870
- var NGRAM_SIZE = 3;
12871
- function embedText(text) {
12872
- const vec = new Float32Array(VECTOR_DIMENSIONS);
12873
- const normalized = text.toLowerCase().trim();
12874
- if (normalized.length < NGRAM_SIZE) {
12875
- const padded = ` ${normalized} `.slice(0, Math.max(NGRAM_SIZE, normalized.length + 2));
12876
- for (let i = 0; i <= padded.length - NGRAM_SIZE; i++) {
12877
- const ngram = padded.slice(i, i + NGRAM_SIZE);
12878
- const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
12879
- vec[bucket] += 1;
12880
- }
12881
- } else {
12882
- for (let i = 0; i <= normalized.length - NGRAM_SIZE; i++) {
12883
- const ngram = normalized.slice(i, i + NGRAM_SIZE);
12884
- const bucket = hashNgram(ngram) % VECTOR_DIMENSIONS;
12885
- vec[bucket] += 1;
12886
- }
12887
- }
12888
- let norm = 0;
12889
- for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
12890
- norm += vec[i] * vec[i];
12891
- }
12892
- norm = Math.sqrt(norm);
12893
- if (norm > 0) {
12894
- for (let i = 0; i < VECTOR_DIMENSIONS; i++) {
12895
- vec[i] /= norm;
12896
- }
12897
- }
12898
- return vec;
12899
- }
12900
- function hashNgram(str) {
12901
- let hash = 2166136261;
12902
- for (let i = 0; i < str.length; i++) {
12903
- hash ^= str.charCodeAt(i);
12904
- hash = Math.imul(hash, 16777619);
12905
- }
12906
- return hash >>> 0;
12907
- }
12908
- function cosineSimilarity(a, b) {
12909
- let dot = 0;
12910
- const len = Math.min(a.length, b.length);
12911
- for (let i = 0; i < len; i++) {
12912
- dot += a[i] * b[i];
12913
- }
12914
- return dot;
12915
- }
12916
- function encodeVector(vec) {
12917
- return Buffer.from(vec.buffer, vec.byteOffset, vec.byteLength);
12918
- }
12919
- function decodeVector(buf) {
12920
- const view = new DataView(
12921
- buf.buffer,
12922
- buf.byteOffset,
12923
- buf.byteLength
12924
- );
12925
- const copy = new Float32Array(buf.byteLength / 4);
12926
- for (let i = 0; i < copy.length; i++) {
12927
- copy[i] = view.getFloat32(i * 4, true);
12928
- }
12929
- return copy;
12930
- }
12931
- function reciprocalRankFusion(bm25Ranks, vectorRanks, k = RRF_K) {
12932
- const allIds = /* @__PURE__ */ new Set([...bm25Ranks.keys(), ...vectorRanks.keys()]);
12933
- const scored = [];
12934
- for (const id of allIds) {
12935
- const bm25Rank = bm25Ranks.get(id);
12936
- const vecRank = vectorRanks.get(id);
12937
- let score = 0;
12938
- if (bm25Rank !== void 0) score += 1 / (k + bm25Rank);
12939
- if (vecRank !== void 0) score += 1 / (k + vecRank);
12940
- scored.push([id, score]);
12941
- }
12942
- scored.sort((a, b) => b[1] - a[1]);
12943
- return scored;
12944
- }
12945
-
12946
12947
  // src/codebase-index/writer.ts
12947
12948
  var DB_FILE2 = "index.db";
12948
12949
  var MAX_STATEMENT_CACHE = 128;
12949
12950
  var IndexStore = class _IndexStore {
12950
12951
  db;
12952
+ /**
12953
+ * True while an index run owns one outer SQLite transaction. Individual
12954
+ * writer methods normally protect themselves with BEGIN/COMMIT, but during
12955
+ * a refresh they join this transaction so readers observe either the last
12956
+ * completed index or the next completed index, never an in-between batch.
12957
+ */
12958
+ atomicIndexUpdateActive = false;
12959
+ writeSavepointSequence = 0;
12951
12960
  /** Absolute path to this project's index directory. */
12952
12961
  indexDir;
12953
12962
  /**
@@ -13028,6 +13037,51 @@ var IndexStore = class _IndexStore {
13028
13037
  runWithRetry(fn) {
13029
13038
  return runSqliteWithRetry(fn);
13030
13039
  }
13040
+ /** Run a complete index mutation as one WAL-visible publication. */
13041
+ async runAtomicIndexUpdate(job) {
13042
+ if (this.atomicIndexUpdateActive) return job();
13043
+ this.runWithRetry(() => this.db.exec("BEGIN IMMEDIATE"));
13044
+ this.atomicIndexUpdateActive = true;
13045
+ try {
13046
+ const result = await job();
13047
+ this.db.exec("COMMIT");
13048
+ return result;
13049
+ } catch (error) {
13050
+ try {
13051
+ this.db.exec("ROLLBACK");
13052
+ } catch {
13053
+ }
13054
+ throw error;
13055
+ } finally {
13056
+ this.atomicIndexUpdateActive = false;
13057
+ }
13058
+ }
13059
+ /**
13060
+ * Begin a method-local transaction. Inside an atomic index publication a
13061
+ * SAVEPOINT preserves the old per-batch rollback boundary, which is needed
13062
+ * when commitBatch falls back to per-file writes after one batch fails.
13063
+ */
13064
+ beginWriteTransaction() {
13065
+ if (this.atomicIndexUpdateActive) {
13066
+ const savepoint = `index_write_${++this.writeSavepointSequence}`;
13067
+ this.db.exec(`SAVEPOINT ${savepoint}`);
13068
+ return savepoint;
13069
+ }
13070
+ this.db.exec("BEGIN IMMEDIATE");
13071
+ return null;
13072
+ }
13073
+ commitWriteTransaction(savepoint) {
13074
+ if (savepoint) this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
13075
+ else this.db.exec("COMMIT");
13076
+ }
13077
+ rollbackWriteTransaction(savepoint) {
13078
+ if (savepoint) {
13079
+ this.db.exec(`ROLLBACK TO SAVEPOINT ${savepoint}`);
13080
+ this.db.exec(`RELEASE SAVEPOINT ${savepoint}`);
13081
+ } else {
13082
+ this.db.exec("ROLLBACK");
13083
+ }
13084
+ }
13031
13085
  /**
13032
13086
  * Mirror the in-process language→family map into SQLite.
13033
13087
  *
@@ -13262,7 +13316,7 @@ var IndexStore = class _IndexStore {
13262
13316
  insertSymbols(symbols) {
13263
13317
  this.invalidateBm25();
13264
13318
  return this.runWithRetry(() => {
13265
- this.db.exec("BEGIN IMMEDIATE");
13319
+ const ownsTransaction = this.beginWriteTransaction();
13266
13320
  try {
13267
13321
  let nextId = this.allocateSymbolIds(symbols.length);
13268
13322
  const result = [];
@@ -13289,7 +13343,9 @@ var IndexStore = class _IndexStore {
13289
13343
  }
13290
13344
  vectorRows.push({
13291
13345
  id,
13292
- vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
13346
+ vector: encodeVector(
13347
+ embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
13348
+ )
13293
13349
  });
13294
13350
  result.push({ ...s, id });
13295
13351
  }
@@ -13307,10 +13363,10 @@ var IndexStore = class _IndexStore {
13307
13363
  vectorRows
13308
13364
  );
13309
13365
  }
13310
- this.db.exec("COMMIT");
13366
+ this.commitWriteTransaction(ownsTransaction);
13311
13367
  return result;
13312
13368
  } catch (err) {
13313
- this.db.exec("ROLLBACK");
13369
+ this.rollbackWriteTransaction(ownsTransaction);
13314
13370
  throw err;
13315
13371
  }
13316
13372
  });
@@ -13318,7 +13374,7 @@ var IndexStore = class _IndexStore {
13318
13374
  deleteSymbolsForFile(file) {
13319
13375
  this.invalidateBm25();
13320
13376
  this.runWithRetry(() => {
13321
- this.db.exec("BEGIN IMMEDIATE");
13377
+ const ownsTransaction = this.beginWriteTransaction();
13322
13378
  try {
13323
13379
  const affectedNames = this.invalidateIncomingRefsForFiles([file]);
13324
13380
  if (this.ftsAvailable) {
@@ -13333,9 +13389,9 @@ var IndexStore = class _IndexStore {
13333
13389
  }
13334
13390
  this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
13335
13391
  this.resolveRefsForNamesUnsafe(affectedNames);
13336
- this.db.exec("COMMIT");
13392
+ this.commitWriteTransaction(ownsTransaction);
13337
13393
  } catch (error) {
13338
- this.db.exec("ROLLBACK");
13394
+ this.rollbackWriteTransaction(ownsTransaction);
13339
13395
  throw error;
13340
13396
  }
13341
13397
  });
@@ -13348,7 +13404,7 @@ var IndexStore = class _IndexStore {
13348
13404
  deleteFile(file) {
13349
13405
  this.invalidateBm25();
13350
13406
  this.runWithRetry(() => {
13351
- this.db.exec("BEGIN IMMEDIATE");
13407
+ const ownsTransaction = this.beginWriteTransaction();
13352
13408
  try {
13353
13409
  const affectedNames = this.invalidateIncomingRefsForFiles([file]);
13354
13410
  if (this.ftsAvailable) {
@@ -13367,9 +13423,9 @@ var IndexStore = class _IndexStore {
13367
13423
  this.stmt("DELETE FROM symbols WHERE file_fk = ?").run(file);
13368
13424
  this.stmt("DELETE FROM files WHERE file = ?").run(file);
13369
13425
  this.resolveRefsForNamesUnsafe(affectedNames);
13370
- this.db.exec("COMMIT");
13426
+ this.commitWriteTransaction(ownsTransaction);
13371
13427
  } catch (err) {
13372
- this.db.exec("ROLLBACK");
13428
+ this.rollbackWriteTransaction(ownsTransaction);
13373
13429
  throw err;
13374
13430
  }
13375
13431
  });
@@ -13756,7 +13812,7 @@ var IndexStore = class _IndexStore {
13756
13812
  clearAll() {
13757
13813
  this.invalidateBm25();
13758
13814
  this.runWithRetry(() => {
13759
- this.db.exec("BEGIN IMMEDIATE");
13815
+ const ownsTransaction = this.beginWriteTransaction();
13760
13816
  try {
13761
13817
  this.db.exec("DROP TABLE IF EXISTS refs");
13762
13818
  this.db.exec("DROP TABLE IF EXISTS symbols");
@@ -13764,15 +13820,15 @@ var IndexStore = class _IndexStore {
13764
13820
  this.db.exec("DROP TABLE IF EXISTS metadata");
13765
13821
  if (this.ftsAvailable) this.db.exec("DROP TABLE IF EXISTS symbols_fts");
13766
13822
  this.db.exec("DROP TABLE IF EXISTS symbol_vectors");
13767
- this.db.exec("COMMIT");
13768
13823
  this.stmtCache.clear();
13769
13824
  this.initSchema();
13770
13825
  this.stmt("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)").run(
13771
13826
  _IndexStore.NEXT_SYMBOL_ID_KEY,
13772
13827
  "1"
13773
13828
  );
13829
+ this.commitWriteTransaction(ownsTransaction);
13774
13830
  } catch (err) {
13775
- this.db.exec("ROLLBACK");
13831
+ this.rollbackWriteTransaction(ownsTransaction);
13776
13832
  throw err;
13777
13833
  }
13778
13834
  });
@@ -13834,7 +13890,7 @@ var IndexStore = class _IndexStore {
13834
13890
  }
13835
13891
  this.invalidateBm25();
13836
13892
  return this.runWithRetry(() => {
13837
- this.db.exec("BEGIN IMMEDIATE");
13893
+ const ownsTransaction = this.beginWriteTransaction();
13838
13894
  try {
13839
13895
  const affectedNames = /* @__PURE__ */ new Set();
13840
13896
  for (const entry of entries) {
@@ -13895,7 +13951,9 @@ var IndexStore = class _IndexStore {
13895
13951
  }
13896
13952
  vectorRows.push({
13897
13953
  id,
13898
- vector: encodeVector(embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment)))
13954
+ vector: encodeVector(
13955
+ embedText(s.text || buildIndexableText(s.name, s.signature, s.docComment))
13956
+ )
13899
13957
  });
13900
13958
  const inserted = { ...s, id };
13901
13959
  allInserted.push(inserted);
@@ -13940,10 +13998,10 @@ var IndexStore = class _IndexStore {
13940
13998
  );
13941
13999
  }
13942
14000
  this.resolveRefsForNamesUnsafe(affectedNames);
13943
- this.db.exec("COMMIT");
14001
+ this.commitWriteTransaction(ownsTransaction);
13944
14002
  return allInserted;
13945
14003
  } catch (err) {
13946
- this.db.exec("ROLLBACK");
14004
+ this.rollbackWriteTransaction(ownsTransaction);
13947
14005
  throw err;
13948
14006
  }
13949
14007
  });
@@ -14020,7 +14078,7 @@ var IndexStore = class _IndexStore {
14020
14078
  replaceEmptyFile(meta) {
14021
14079
  this.invalidateBm25();
14022
14080
  this.runWithRetry(() => {
14023
- this.db.exec("BEGIN IMMEDIATE");
14081
+ const ownsTransaction = this.beginWriteTransaction();
14024
14082
  try {
14025
14083
  const affectedNames = this.invalidateIncomingRefsForFiles([meta.file]);
14026
14084
  if (this.ftsAvailable) {
@@ -14055,9 +14113,9 @@ var IndexStore = class _IndexStore {
14055
14113
  meta.lastIndexed
14056
14114
  );
14057
14115
  this.resolveRefsForNamesUnsafe(affectedNames);
14058
- this.db.exec("COMMIT");
14116
+ this.commitWriteTransaction(ownsTransaction);
14059
14117
  } catch (err) {
14060
- this.db.exec("ROLLBACK");
14118
+ this.rollbackWriteTransaction(ownsTransaction);
14061
14119
  throw err;
14062
14120
  }
14063
14121
  });
@@ -15015,6 +15073,7 @@ import { Worker as Worker2 } from "node:worker_threads";
15015
15073
  // src/codebase-index/indexer.ts
15016
15074
  import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
15017
15075
  import { execFile } from "node:child_process";
15076
+ import { createHash as createHash5 } from "node:crypto";
15018
15077
  import * as fs18 from "node:fs/promises";
15019
15078
  import { availableParallelism } from "node:os";
15020
15079
  import * as path24 from "node:path";
@@ -15855,6 +15914,10 @@ var DEFAULT_IGNORE_FILES = /* @__PURE__ */ new Set(["package-lock.json", "pnpm-l
15855
15914
  var INDEXABLE_EXTENSION_SET = new Set(INDEXABLE_EXTENSIONS);
15856
15915
  var MAX_INDEX_FILE_BYTES = 5 * 1024 * 1024;
15857
15916
  var MAX_GIT_FILE_LIST_BYTES = 64 * 1024 * 1024;
15917
+ var GIT_SNAPSHOT_METADATA_KEY = "git_discovery_snapshot";
15918
+ var IndexSourceChangedError = class extends Error {
15919
+ name = "IndexSourceChangedError";
15920
+ };
15858
15921
  function isWithinProject(projectRoot, file) {
15859
15922
  const rel = path24.relative(projectRoot, file);
15860
15923
  return rel !== "" && !rel.startsWith(`..${path24.sep}`) && rel !== ".." && !path24.isAbsolute(rel);
@@ -15891,7 +15954,7 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
15891
15954
  if (normalizeComparablePath(topLevel) !== normalizeComparablePath(projectRoot)) return null;
15892
15955
  throwIfAborted(signal);
15893
15956
  const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
15894
- const [output, statusOutput] = await Promise.all([
15957
+ const [output, statusOutput, stagedOutput] = await Promise.all([
15895
15958
  gitOutput(projectRoot, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]),
15896
15959
  gitOutput(projectRoot, [
15897
15960
  "status",
@@ -15899,7 +15962,8 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
15899
15962
  "-z",
15900
15963
  "--untracked-files=all",
15901
15964
  "--ignored=no"
15902
- ])
15965
+ ]),
15966
+ gitOutput(projectRoot, ["ls-files", "--stage", "-z"])
15903
15967
  ]);
15904
15968
  throwIfAborted(signal);
15905
15969
  const dirty = /* @__PURE__ */ new Set();
@@ -15929,9 +15993,17 @@ async function findGitSourceFiles(projectRoot, ignore, signal) {
15929
15993
  const ext = path24.extname(relative13).toLowerCase();
15930
15994
  if (INDEXABLE_EXTENSION_SET.has(ext) || detectLang(full) !== null) files.push(full);
15931
15995
  }
15996
+ const snapshot = createHash5("sha256").update(stagedOutput).update("\0").update(statusOutput);
15997
+ const indexedFiles = new Set(files);
15998
+ for (const dirtyFile of [...dirty].sort()) {
15999
+ if (!indexedFiles.has(dirtyFile) || deleted.has(dirtyFile)) continue;
16000
+ snapshot.update("\0").update(dirtyFile).update("\0");
16001
+ snapshot.update(xxhash64String(await fs18.readFile(dirtyFile, "utf8")));
16002
+ }
15932
16003
  return {
15933
16004
  files,
15934
- trustedUnchanged: new Set(files.filter((file) => !dirty.has(file)))
16005
+ trustedUnchanged: new Set(files.filter((file) => !dirty.has(file))),
16006
+ snapshotKey: snapshot.digest("hex")
15935
16007
  };
15936
16008
  } catch {
15937
16009
  return null;
@@ -15944,7 +16016,8 @@ async function findSourceFiles(projectRoot, ignore, isGitIgnored, signal) {
15944
16016
  files: gitFiles.files,
15945
16017
  complete: true,
15946
16018
  errors: [],
15947
- trustedUnchanged: gitFiles.trustedUnchanged
16019
+ trustedUnchanged: gitFiles.trustedUnchanged,
16020
+ snapshotKey: gitFiles.snapshotKey
15948
16021
  };
15949
16022
  }
15950
16023
  const results = [];
@@ -16031,6 +16104,17 @@ async function resolveProjectRelations(store, projectRoot, opts) {
16031
16104
  }
16032
16105
  }
16033
16106
  async function runIndexerWithStore(store, opts) {
16107
+ let result;
16108
+ try {
16109
+ result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
16110
+ } catch (error) {
16111
+ if (!(error instanceof IndexSourceChangedError)) throw error;
16112
+ result = await store.runAtomicIndexUpdate(() => runIndexerAtomic(store, opts));
16113
+ }
16114
+ if (!opts.files) store.compactIfNeeded();
16115
+ return result;
16116
+ }
16117
+ async function runIndexerAtomic(store, opts) {
16034
16118
  const { projectRoot, langs, ignore = [], signal } = opts;
16035
16119
  const relationGraphVersion = "2";
16036
16120
  const refResolutionVersion = "2";
@@ -16050,6 +16134,7 @@ async function runIndexerWithStore(store, opts) {
16050
16134
  let discoveredFiles = null;
16051
16135
  let discoveryComplete = true;
16052
16136
  let trustedUnchanged;
16137
+ let discoverySnapshotKey;
16053
16138
  if (opts.files && opts.files.length > 0) {
16054
16139
  files = opts.files.map((f) => path24.resolve(projectRoot, f)).filter((f) => {
16055
16140
  if (!isWithinProject(projectRoot, f)) return false;
@@ -16063,6 +16148,7 @@ async function runIndexerWithStore(store, opts) {
16063
16148
  discoveryComplete = discovery.complete;
16064
16149
  discoveredFiles = new Set(files);
16065
16150
  trustedUnchanged = discovery.trustedUnchanged;
16151
+ discoverySnapshotKey = discovery.snapshotKey;
16066
16152
  }
16067
16153
  if (langs && langs.length > 0) {
16068
16154
  const langSet = new Set(langs);
@@ -16076,6 +16162,8 @@ async function runIndexerWithStore(store, opts) {
16076
16162
  if (!force) {
16077
16163
  for (const meta of store.getAllFileMetas()) existingMeta.set(meta.file, meta);
16078
16164
  }
16165
+ const snapshotTrusted = !force && discoverySnapshotKey !== void 0 && store.getMetadata(GIT_SNAPSHOT_METADATA_KEY) === discoverySnapshotKey;
16166
+ if (!snapshotTrusted) trustedUnchanged = void 0;
16079
16167
  const totalFilesForProgress = files.length;
16080
16168
  let filesPreSkipped = 0;
16081
16169
  if (!force && trustedUnchanged) {
@@ -16138,9 +16226,6 @@ async function runIndexerWithStore(store, opts) {
16138
16226
  };
16139
16227
  }
16140
16228
  const meta = existingMeta.get(file);
16141
- if (!force && meta && meta.mtimeMs === Math.floor(stat20.mtimeMs)) {
16142
- return { file, stat: stat20, lang, parsed: null, skippedMeta: meta };
16143
- }
16144
16229
  let content;
16145
16230
  try {
16146
16231
  content = await fs18.readFile(file, { encoding: "utf8", signal });
@@ -16369,9 +16454,18 @@ async function runIndexerWithStore(store, opts) {
16369
16454
  });
16370
16455
  store.setMetadata("ref_resolution_version", refResolutionVersion);
16371
16456
  store.setMetadata("relation_graph_version", relationGraphVersion);
16457
+ const completeProjectScope = !opts.files && (!langs || langs.length === 0) && (!opts.ignore || opts.ignore.length === 0);
16458
+ if (completeProjectScope && discoverySnapshotKey !== void 0) {
16459
+ const finalSnapshot = await findGitSourceFiles(projectRoot, ignore, signal);
16460
+ if (!finalSnapshot || finalSnapshot.snapshotKey !== discoverySnapshotKey) {
16461
+ throw new IndexSourceChangedError(
16462
+ "Project files changed during indexing; retrying before publishing the generation."
16463
+ );
16464
+ }
16465
+ store.setMetadata(GIT_SNAPSHOT_METADATA_KEY, errors.length === 0 ? discoverySnapshotKey : "");
16466
+ }
16372
16467
  if (!opts.files || filesIndexed >= 50) store.optimize();
16373
16468
  store.setLastIndexed(Date.now());
16374
- if (!opts.files) store.compactIfNeeded();
16375
16469
  const durationMs = Date.now() - startMs;
16376
16470
  return {
16377
16471
  filesIndexed,
@@ -16682,6 +16776,13 @@ function callIndexOp(op, args, opts) {
16682
16776
  });
16683
16777
  }
16684
16778
  async function callInline(op, args, opts) {
16779
+ if (op !== "index" && _indexing) {
16780
+ const error = new Error(
16781
+ "Codebase index refresh in progress; retry after the completed generation is published."
16782
+ );
16783
+ error.name = "IndexRefreshInProgressError";
16784
+ throw error;
16785
+ }
16685
16786
  const ac = new AbortController();
16686
16787
  const onOuterAbort = () => ac.abort(opts.signal?.reason ?? new Error("Indexing cancelled"));
16687
16788
  if (opts.signal?.aborted) onOuterAbort();
@@ -17026,12 +17127,12 @@ var codebaseSearchTool = {
17026
17127
  },
17027
17128
  async execute(input, ctx, execOpts) {
17028
17129
  const state = getIndexState();
17029
- if (state.indexing && !state.ready) {
17130
+ if (state.indexing) {
17030
17131
  return {
17031
17132
  results: [],
17032
17133
  total: 0,
17033
17134
  query: input.query,
17034
- indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
17135
+ indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
17035
17136
  };
17036
17137
  }
17037
17138
  if (state.lastError) {
@@ -17212,12 +17313,12 @@ var codebaseIncomingCallsTool = {
17212
17313
  },
17213
17314
  async execute(input, ctx) {
17214
17315
  const state = getIndexState();
17215
- if (state.indexing && !state.ready) {
17316
+ if (state.indexing) {
17216
17317
  return {
17217
17318
  symbol: input.symbol,
17218
17319
  calls: [],
17219
17320
  total: 0,
17220
- indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
17321
+ indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
17221
17322
  };
17222
17323
  }
17223
17324
  if (state.lastError) {
@@ -17234,16 +17335,14 @@ var codebaseIncomingCallsTool = {
17234
17335
  const transitive = input.transitive === true;
17235
17336
  let serviced;
17236
17337
  try {
17237
- serviced = await incomingCallsService2(
17238
- {
17239
- projectRoot: ctx.projectRoot,
17240
- indexDir: codebaseIndexDirOverride(ctx),
17241
- symbol: input.symbol,
17242
- file: input.file,
17243
- limit,
17244
- transitive
17245
- }
17246
- );
17338
+ serviced = await incomingCallsService2({
17339
+ projectRoot: ctx.projectRoot,
17340
+ indexDir: codebaseIndexDirOverride(ctx),
17341
+ symbol: input.symbol,
17342
+ file: input.file,
17343
+ limit,
17344
+ transitive
17345
+ });
17247
17346
  } catch (err) {
17248
17347
  return {
17249
17348
  symbol: input.symbol,
@@ -17282,10 +17381,14 @@ var codebaseIncomingCallsTool = {
17282
17381
  }
17283
17382
  const notes = [];
17284
17383
  if (totalMatches > limit) {
17285
- notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
17384
+ notes.push(
17385
+ `Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`
17386
+ );
17286
17387
  }
17287
17388
  if (ambiguous) {
17288
- notes.push(`Symbol "${input.symbol}" exists in multiple files. Results include callers of all same-named symbols. Use codebase-search to find the exact file and pass it as \`file\`.`);
17389
+ notes.push(
17390
+ `Symbol "${input.symbol}" exists in multiple files. Results include callers of all same-named symbols. Use codebase-search to find the exact file and pass it as \`file\`.`
17391
+ );
17289
17392
  }
17290
17393
  return {
17291
17394
  symbol: input.symbol,
@@ -17335,12 +17438,12 @@ var codebaseOutgoingCallsTool = {
17335
17438
  },
17336
17439
  async execute(input, ctx) {
17337
17440
  const state = getIndexState();
17338
- if (state.indexing && !state.ready) {
17441
+ if (state.indexing) {
17339
17442
  return {
17340
17443
  symbol: input.symbol,
17341
17444
  calls: [],
17342
17445
  total: 0,
17343
- indexStatus: `Indexing in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry in a moment.`
17446
+ indexStatus: `Index refresh in progress (${state.currentFile}/${state.totalFiles} files) \u2014 retry after the completed generation is published.`
17344
17447
  };
17345
17448
  }
17346
17449
  if (state.lastError) {
@@ -17357,16 +17460,14 @@ var codebaseOutgoingCallsTool = {
17357
17460
  const transitive = input.transitive === true;
17358
17461
  let serviced;
17359
17462
  try {
17360
- serviced = await outgoingCallsService2(
17361
- {
17362
- projectRoot: ctx.projectRoot,
17363
- indexDir: codebaseIndexDirOverride(ctx),
17364
- symbol: input.symbol,
17365
- file: input.file,
17366
- limit,
17367
- transitive
17368
- }
17369
- );
17463
+ serviced = await outgoingCallsService2({
17464
+ projectRoot: ctx.projectRoot,
17465
+ indexDir: codebaseIndexDirOverride(ctx),
17466
+ symbol: input.symbol,
17467
+ file: input.file,
17468
+ limit,
17469
+ transitive
17470
+ });
17370
17471
  } catch (err) {
17371
17472
  return {
17372
17473
  symbol: input.symbol,
@@ -17405,10 +17506,14 @@ var codebaseOutgoingCallsTool = {
17405
17506
  }
17406
17507
  const notes = [];
17407
17508
  if (totalMatches > limit) {
17408
- notes.push(`Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`);
17509
+ notes.push(
17510
+ `Results capped at ${limit} of ${totalMatches} call sites. Increase \`limit\` or use \`file\` to narrow.`
17511
+ );
17409
17512
  }
17410
17513
  if (unresolvedCount > 0) {
17411
- notes.push(`${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`);
17514
+ notes.push(
17515
+ `${unresolvedCount} unresolved reference(s) not shown \u2014 their targets could not be resolved during indexing.`
17516
+ );
17412
17517
  }
17413
17518
  return {
17414
17519
  symbol: input.symbol,
@@ -18505,7 +18610,7 @@ var documentTool = {
18505
18610
  }
18506
18611
  },
18507
18612
  async execute(input, ctx) {
18508
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
18613
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
18509
18614
  const style = input.style ?? "jsdoc";
18510
18615
  const results = [];
18511
18616
  let filesProcessed = 0;
@@ -18514,7 +18619,7 @@ var documentTool = {
18514
18619
  Array.isArray(input.files) ? input.files.join(",") : input.files,
18515
18620
  cwd,
18516
18621
  ctx
18517
- ) : input.path ? [safeResolve(input.path, ctx)] : [];
18622
+ ) : input.path ? [await safeResolveReal(input.path, ctx)] : [];
18518
18623
  for (const absPath of fileList) {
18519
18624
  try {
18520
18625
  const content = await fs23.readFile(absPath, "utf8");
@@ -21334,7 +21439,7 @@ var formatTool = {
21334
21439
  return final;
21335
21440
  },
21336
21441
  async *executeStream(input, ctx, opts) {
21337
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
21442
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
21338
21443
  const fixer = input.fixer ?? "auto";
21339
21444
  if (fixer === "auto" && !input.files) {
21340
21445
  const op = input.check ? "format-check" : "format-write";
@@ -22552,7 +22657,7 @@ var installTool = {
22552
22657
  return final;
22553
22658
  },
22554
22659
  async *executeStream(input, ctx, opts) {
22555
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
22660
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
22556
22661
  if (!input.global) {
22557
22662
  const pkgList2 = input.packages ? Array.isArray(input.packages) ? input.packages : input.packages.split(",").map((p) => p.trim()) : [];
22558
22663
  const bridge = await tryLegacyPackageOperation(
@@ -26157,7 +26262,7 @@ var lintTool = {
26157
26262
  return final;
26158
26263
  },
26159
26264
  async *executeStream(input, ctx, opts) {
26160
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
26265
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
26161
26266
  const linter = input.linter ?? "auto";
26162
26267
  if (linter === "auto" && !input.files) {
26163
26268
  const bridge = await tryLegacyCodeOperation("lint", {
@@ -26283,7 +26388,7 @@ var logsTool = {
26283
26388
  }
26284
26389
  },
26285
26390
  async execute(input, ctx, opts) {
26286
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
26391
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
26287
26392
  const lines = input.lines ?? 100;
26288
26393
  let filterRe = null;
26289
26394
  if (input.filter) {
@@ -26490,7 +26595,7 @@ var outdatedTool = {
26490
26595
  }
26491
26596
  },
26492
26597
  async execute(input, ctx, opts) {
26493
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
26598
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
26494
26599
  const manager = await detectPackageManager(cwd, ctx.projectRoot);
26495
26600
  if (manager === "npm") {
26496
26601
  try {
@@ -28204,7 +28309,7 @@ async function resolveFiles2(filesInput, ctx, extraGlob) {
28204
28309
  const parts = normalized.split(",").map((s) => s.trim()).filter(Boolean);
28205
28310
  const resolved = [];
28206
28311
  for (const p of parts) {
28207
- const absPath = safeResolve(p, ctx);
28312
+ const absPath = await safeResolveReal(p, ctx);
28208
28313
  if (extraGlob && !passesExtraGlob(extraGlob, path35.basename(absPath), absPath)) continue;
28209
28314
  const stat20 = await fs30.stat(absPath).catch(() => null);
28210
28315
  if (stat20?.isFile()) {
@@ -28442,7 +28547,7 @@ var scaffoldTool = {
28442
28547
  required: ["template", "name"]
28443
28548
  },
28444
28549
  async execute(input, ctx) {
28445
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
28550
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
28446
28551
  const name = input.name;
28447
28552
  const vars = { name, ...input.vars };
28448
28553
  const builtIn = BUILT_IN_TEMPLATES[input.template];
@@ -29530,7 +29635,7 @@ var testTool = {
29530
29635
  return final;
29531
29636
  },
29532
29637
  async *executeStream(input, ctx, opts) {
29533
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
29638
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
29534
29639
  const runner = input.runner ?? "auto";
29535
29640
  if (runner === "auto") {
29536
29641
  const bridge = await tryLegacyCodeOperation("test", {
@@ -30045,7 +30150,7 @@ var treeTool = {
30045
30150
  return final;
30046
30151
  },
30047
30152
  async *executeStream(input, ctx, opts) {
30048
- const basePath = input.path ? safeResolve(input.path, ctx) : ctx.cwd;
30153
+ const basePath = input.path ? await safeResolveReal(input.path, ctx) : ctx.cwd;
30049
30154
  const maxDepth = input.depth ?? 3;
30050
30155
  const showFiles = input.show_files ?? true;
30051
30156
  const showDirs = input.show_dirs ?? true;
@@ -30225,7 +30330,7 @@ var typecheckTool = {
30225
30330
  return final;
30226
30331
  },
30227
30332
  async *executeStream(input, ctx, opts) {
30228
- const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
30333
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
30229
30334
  const bridge = await tryLegacyCodeOperation("semantic", {
30230
30335
  cwd,
30231
30336
  projectRoot: ctx.projectRoot,
@@ -30262,7 +30367,7 @@ var typecheckTool = {
30262
30367
  cmdArgs = ["tsc", ...tscArgs];
30263
30368
  }
30264
30369
  } else {
30265
- const tsconfig = input.project ? safeResolve(input.project, ctx) : await findTsConfig(cwd);
30370
+ const tsconfig = input.project ? await safeResolveReal(input.project, ctx) : await findTsConfig(cwd);
30266
30371
  const tscArgs = ["--noEmit"];
30267
30372
  if (input.strict) tscArgs.push("--strict");
30268
30373
  if (tsconfig) tscArgs.push("--project", tsconfig);