github-router 0.3.219 → 0.3.229

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.
@@ -13335,40 +13335,47 @@ function deriveDefinitionName(node) {
13335
13335
  return null;
13336
13336
  }
13337
13337
  /**
13338
- * Collect EVERY definition node from the parse tree top-level AND
13339
- * nested (class methods, methods' inner functions, nested classes, …) —
13340
- * so the outline is a COMPLETE structural map the model can rely on to
13341
- * decide what to read. Recurses through non-definition wrappers (TS
13342
- * `export_statement`, Python `decorated_definition`, C++
13343
- * `template_declaration`, …) at the same depth, and INTO each definition
13344
- * at depth+1 to surface its members.
13345
- *
13346
- * `defTypes` is the language's definition-node-type set. Each node yields
13347
- * one entry; the `name` is derived per `deriveDefinitionName` (a node
13348
- * with no recoverable name is skipped, but the walk still descends into
13349
- * it so its named members aren't lost). Bounded at `MAX_OUTLINE_ENTRIES`.
13350
- */
13338
+ * Collect declarations that help a model navigate a file: top-level symbols
13339
+ * and members of class-like containers. Function-local variables and nested
13340
+ * helpers are deliberately omitted because they inflate the summary without
13341
+ * identifying useful read targets. Non-definition wrappers (exports,
13342
+ * decorators, templates) preserve the surrounding depth.
13343
+ */
13344
+ const OUTLINE_MEMBER_CONTAINERS = new Set([
13345
+ "class_declaration",
13346
+ "class_definition",
13347
+ "class_specifier",
13348
+ "interface_declaration",
13349
+ "annotation_type_declaration",
13350
+ "impl_item",
13351
+ "trait_item",
13352
+ "struct_item",
13353
+ "namespace_definition",
13354
+ "module"
13355
+ ]);
13351
13356
  function collectDefinitions(root, defTypes, signal) {
13352
13357
  const out = [];
13353
- const visit = (node, depth) => {
13358
+ const visit = (node, depth, includeDefinitions) => {
13354
13359
  if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
13355
13360
  for (const child of node.namedChildren) {
13356
13361
  if (signal?.aborted || out.length >= MAX_OUTLINE_ENTRIES) return;
13357
13362
  if (defTypes.has(child.type)) {
13358
- const name = deriveDefinitionName(child);
13359
- if (name !== null) out.push({
13360
- kind: child.type,
13361
- name,
13362
- line: child.startPosition.row + 1,
13363
- depth
13364
- });
13365
- visit(child, depth + 1);
13363
+ if (includeDefinitions) {
13364
+ const name = deriveDefinitionName(child);
13365
+ if (name !== null) out.push({
13366
+ kind: child.type,
13367
+ name,
13368
+ line: child.startPosition.row + 1,
13369
+ depth
13370
+ });
13371
+ }
13372
+ if (includeDefinitions && OUTLINE_MEMBER_CONTAINERS.has(child.type)) visit(child, depth + 1, true);
13366
13373
  continue;
13367
13374
  }
13368
- visit(child, depth);
13375
+ visit(child, depth, includeDefinitions);
13369
13376
  }
13370
13377
  };
13371
- visit(root, 0);
13378
+ visit(root, 0, true);
13372
13379
  return out;
13373
13380
  }
13374
13381
  /**
@@ -13512,10 +13519,10 @@ function confirmDefinitionSites(tree, source, language, hits, signal) {
13512
13519
  return confirmed;
13513
13520
  }
13514
13521
  /**
13515
- * Full structural outline of a single file — EVERY definition, top-level
13516
- * AND nested (functions, classes, methods, nested functions, interfaces,
13517
- * type aliases, enums, including exported / decorated / templated
13518
- * wrappers). Each entry carries a `depth` (0 = top-level). Reuses the
13522
+ * Navigational structural outline of a single file: top-level declarations
13523
+ * plus class-like members (methods, fields, interface members), excluding
13524
+ * function-local variables and nested implementation helpers. Each entry
13525
+ * carries a `depth` (0 = top-level). Reuses the
13519
13526
  * shared grammar bundle and the same `Parser` the structural pass uses
13520
13527
  * — no second `Parser.init()`.
13521
13528
  *
@@ -13680,6 +13687,14 @@ var TreeSitterPool = class {
13680
13687
  spawned: this.workersSpawned
13681
13688
  };
13682
13689
  }
13690
+ /**
13691
+ * Launch-time latency optimization: ready ONE worker and its grammar bundle.
13692
+ * A single worker keeps startup CPU/memory bounded while removing the first
13693
+ * query's initialization wait; normal demand still grows the pool lazily.
13694
+ */
13695
+ async warm() {
13696
+ return await this.ensureWorkers(1) > 0;
13697
+ }
13683
13698
  queue = [];
13684
13699
  inflight = /* @__PURE__ */ new Map();
13685
13700
  constructor() {
@@ -13695,18 +13710,19 @@ var TreeSitterPool = class {
13695
13710
  * callers onto one in-flight ensure so 8 simultaneous searches don't each
13696
13711
  * spawn a fresh batch. Returns the live worker count (0 → caller must fall
13697
13712
  * back to the in-process path). */
13698
- ensureWorkers() {
13713
+ ensureWorkers(target = this.size) {
13699
13714
  if (this.unavailable || this.shuttingDown) return Promise.resolve(0);
13715
+ const desired = Math.max(1, Math.min(target, this.size));
13700
13716
  const liveNow = this.workers.filter((w) => w.ready && w.loaded.size > 0).length;
13701
- if (liveNow >= this.size) return Promise.resolve(liveNow);
13702
- if (this.ensuring) return this.ensuring;
13703
- this.ensuring = this.doEnsureWorkers().finally(() => {
13717
+ if (liveNow >= desired) return Promise.resolve(liveNow);
13718
+ if (this.ensuring) return liveNow > 0 ? Promise.resolve(liveNow) : this.ensuring;
13719
+ this.ensuring = this.doEnsureWorkers(desired).finally(() => {
13704
13720
  this.ensuring = null;
13705
13721
  });
13706
- return this.ensuring;
13722
+ return liveNow > 0 ? Promise.resolve(liveNow) : this.ensuring;
13707
13723
  }
13708
- async doEnsureWorkers() {
13709
- const need = this.size - this.workers.length;
13724
+ async doEnsureWorkers(target) {
13725
+ const need = target - this.workers.length;
13710
13726
  const spawns = [];
13711
13727
  for (let i = 0; i < need; i++) spawns.push(this.spawnWorker());
13712
13728
  const spawned = await Promise.all(spawns);
@@ -13927,13 +13943,23 @@ var TreeSitterPool = class {
13927
13943
  budgetHit: false
13928
13944
  };
13929
13945
  opts.signal.addEventListener("abort", onAbort, { once: true });
13930
- const budgetTimer = setTimeout(() => {
13931
- budgetHit = true;
13932
- stop();
13933
- }, opts.budgetMs);
13934
- budgetTimer.unref?.();
13946
+ let budgetTimer;
13935
13947
  try {
13936
13948
  if (await this.ensureWorkers() === 0) return null;
13949
+ if (opts.signal.aborted) return {
13950
+ byFile,
13951
+ budgetHit: false
13952
+ };
13953
+ if (opts.budgetMs <= 0) {
13954
+ budgetHit = true;
13955
+ stop();
13956
+ } else {
13957
+ budgetTimer = setTimeout(() => {
13958
+ budgetHit = true;
13959
+ stop();
13960
+ }, opts.budgetMs);
13961
+ budgetTimer.unref?.();
13962
+ }
13937
13963
  const dispatchFile = async (job, retried) => {
13938
13964
  if (stopped) return;
13939
13965
  const id = this.nextJobId++;
@@ -13964,7 +13990,7 @@ var TreeSitterPool = class {
13964
13990
  };
13965
13991
  await Promise.all(jobs.map((job) => dispatchFile(job, false)));
13966
13992
  } finally {
13967
- clearTimeout(budgetTimer);
13993
+ if (budgetTimer) clearTimeout(budgetTimer);
13968
13994
  opts.signal.removeEventListener("abort", onAbort);
13969
13995
  }
13970
13996
  const stillLive = this.workers.some((w) => w.ready && w.loaded.size > 0);
@@ -14010,6 +14036,7 @@ function resolveWorkerPath() {
14010
14036
  let _pool = null;
14011
14037
  let _shutdownRegistered = false;
14012
14038
  let _testCrashOnceArmed = false;
14039
+ let _warmOverride;
14013
14040
  /**
14014
14041
  * The pool is ON by default for real (non-CI) runs and OFF under CI.
14015
14042
  *
@@ -14055,6 +14082,24 @@ function getTreeSitterPool() {
14055
14082
  }
14056
14083
  return _pool;
14057
14084
  }
14085
+ /**
14086
+ * Best-effort launch warm-up. Returns immediately when disabled/unavailable and
14087
+ * never rejects; lazy initialization remains the fallback after any failure.
14088
+ */
14089
+ async function warmTreeSitterPool() {
14090
+ if (process.env.GH_ROUTER_DISABLE_TS_POOL_WARMUP === "1") return;
14091
+ const pool = getTreeSitterPool();
14092
+ if (!pool) return;
14093
+ try {
14094
+ if (await (_warmOverride?.(pool) ?? pool.warm())) return;
14095
+ } catch (err) {
14096
+ consola.debug(`[code_search] tree-sitter pool warm-up failed; using lazy initialization: ${err.message}`);
14097
+ }
14098
+ if (_pool === pool) {
14099
+ pool.shutdown();
14100
+ _pool = null;
14101
+ }
14102
+ }
14058
14103
 
14059
14104
  //#endregion
14060
14105
  //#region src/lib/worker-agent/paths.ts
@@ -14257,9 +14302,9 @@ const WALL_TIME_MS = 3e4;
14257
14302
  * Structural-pass settings. The wall-clock budget is checked between
14258
14303
  * files (NOT mid-parse — tree-sitter doesn't surface a usable cancel
14259
14304
  * hook in the web-tree-sitter binding we're on), so a single
14260
- * pathological file can overrun by one file's parse-time. In practice
14261
- * a single source file parses in well under 50ms; 200ms gives us
14262
- * comfortable headroom for ~5-10 files even on cold cache.
14305
+ * pathological file can overrun by one file's parse-time. Worker/grammar
14306
+ * initialization is completed before this timer starts; the 200ms budget
14307
+ * therefore measures parsing only, on cold and warm queries alike.
14263
14308
  */
14264
14309
  const STRUCTURAL_BUDGET_MS = 200;
14265
14310
  let _structuralBudgetTestOverride = null;
@@ -14854,7 +14899,7 @@ async function runStructuralPassPooled(opts) {
14854
14899
  }
14855
14900
  return {
14856
14901
  confirmedHitIndexes,
14857
- fallback: run.budgetHit ? `structural budget exceeded after parsing ${run.byFile.size}/${opts.cap} hits; retry with structural: "topN" or narrow your query` : null,
14902
+ fallback: run.budgetHit ? `structural budget exceeded after parsing ${run.byFile.size}/${jobs.length} files; retry with structural: "topN" or narrow your query` : null,
14858
14903
  outlinesByFile
14859
14904
  };
14860
14905
  }
@@ -15184,26 +15229,23 @@ function dropAstGrepSecrets(env) {
15184
15229
  return env;
15185
15230
  }
15186
15231
  /**
15187
- * Resolve the ast-grep binary. Checks the toolbelt bin dir (where the
15188
- * proxy materializes `sg` + `ast-grep`) AND the system PATH, trying `sg`
15189
- * first then `ast-grep`. Returns an ABSOLUTE path or `null` when neither
15190
- * is found. `resolveExecutable` honors PATHEXT on Windows and excludes
15191
- * the cwd (no planted-`sg.exe` vector). The toolbelt dir is searched by
15192
- * prepending it to a PATH copy so the same resolver handles both sources.
15232
+ * Resolve the ast-grep binary. Checks the known router-owned toolbelt paths
15233
+ * directly before consulting PATH for the unambiguous `ast-grep` name.
15234
+ * Returns an ABSOLUTE path or `null` when neither source has it.
15193
15235
  */
15194
15236
  function resolveAstGrep() {
15195
15237
  const toolbeltDir = PATHS.TOOLBELT_BIN_DIR;
15196
- const sgInToolbelt = resolveExecutable("sg", { env: {
15197
- ...process.env,
15198
- PATH: toolbeltDir
15199
- } });
15200
- if (sgInToolbelt) return sgInToolbelt;
15201
- const astGrep = resolveExecutable("ast-grep", { env: {
15202
- ...process.env,
15203
- PATH: `${toolbeltDir}${path.delimiter}${pathEnvValue()}`
15204
- } });
15205
- if (astGrep) return astGrep;
15206
- return null;
15238
+ const toolbeltNames = process.platform === "win32" ? [
15239
+ "sg.exe",
15240
+ "ast-grep.exe",
15241
+ "sg",
15242
+ "ast-grep"
15243
+ ] : ["sg", "ast-grep"];
15244
+ for (const name of toolbeltNames) {
15245
+ const candidate = path.resolve(toolbeltDir, name);
15246
+ if (existsSync(candidate)) return candidate;
15247
+ }
15248
+ return resolveExecutable("ast-grep", { env: process.env });
15207
15249
  }
15208
15250
  /**
15209
15251
  * Test-only override for the ast-grep resolver. `undefined` = use the real
@@ -15217,11 +15259,6 @@ let _astGrepResolverOverride;
15217
15259
  function resolveAstGrepForRun() {
15218
15260
  return (_astGrepResolverOverride ?? resolveAstGrep)();
15219
15261
  }
15220
- /** Read PATH case-insensitively from the live env. */
15221
- function pathEnvValue() {
15222
- for (const key of Object.keys(process.env)) if (key.toLowerCase() === "path") return process.env[key] ?? "";
15223
- return "";
15224
- }
15225
15262
  /**
15226
15263
  * Run ast-grep with `pattern` over `workspaceCanonical` and return its
15227
15264
  * matches in `RawHit` shape (relativized, 1-indexed). Read-only,
@@ -15243,7 +15280,7 @@ async function runAstGrep(opts) {
15243
15280
  const binary = resolveAstGrepForRun();
15244
15281
  if (!binary) return {
15245
15282
  hits: [],
15246
- notice: "ast_pattern requires ast-grep (sg), which isn't available here; the model can run ast-grep directly or omit ast_pattern"
15283
+ notice: `ast_pattern requires ast-grep, but no executable was found at ${PATHS.TOOLBELT_BIN_DIR} (sg/ast-grep) or on PATH (ast-grep); install ast-grep or omit ast_pattern`
15247
15284
  };
15248
15285
  if (!opts.lang || !/^[A-Za-z0-9_+-]{1,20}$/.test(opts.lang)) return {
15249
15286
  hits: [],
@@ -15825,424 +15862,112 @@ function modelDirName() {
15825
15862
  const MODEL_ID = "LateOn-Code-edge";
15826
15863
 
15827
15864
  //#endregion
15828
- //#region src/lib/colbert/index-store.ts
15829
- const GIT_TIMEOUT_MS = 4e3;
15830
- /** Grace window after a `building` write before a workspace with no live
15831
- * build PID is declared `crashed` — covers the cross-process window where
15832
- * one proxy wrote `building` but hasn't yet recorded the colgrep child PID. */
15833
- const BUILD_SPAWN_GRACE_MS = 3e4;
15834
- /**
15835
- * Hash a workspace path the same way the metadata sidecar is keyed.
15836
- * NOTE: this is the ROUTER-OWNED meta key, independent of colgrep's
15837
- * internal xxh3 physical-dir key (we never need to predict colgrep's
15838
- * key because we pass the workspace as colgrep's PATH arg and let it
15839
- * route). A stable sha256-prefix of the canonical path is sufficient.
15840
- */
15841
- function metaHashForWorkspace(workspace) {
15842
- const canonical = process$1.platform === "win32" ? nodePath.resolve(workspace).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(workspace);
15843
- let h = 2166136261;
15844
- for (let i = 0; i < canonical.length; i++) {
15845
- h ^= canonical.charCodeAt(i);
15846
- h = Math.imul(h, 16777619);
15847
- }
15848
- return (h >>> 0).toString(16).padStart(8, "0");
15849
- }
15850
- function metaPath(workspace) {
15851
- return nodePath.join(PATHS.COLBERT_META_DIR, `${metaHashForWorkspace(workspace)}.json`);
15852
- }
15853
- /** Read the sidecar metadata for a workspace (null if none yet). */
15854
- async function readColbertMeta(workspace) {
15855
- try {
15856
- const raw = await fs.readFile(metaPath(workspace), "utf8");
15857
- const parsed = JSON.parse(raw);
15858
- if (parsed && typeof parsed === "object" && typeof parsed.status === "string") return parsed;
15859
- return null;
15860
- } catch {
15861
- return null;
15862
- }
15863
- }
15864
- /**
15865
- * Per-workspace write serializer. `runInit` issues a pre-spawn
15866
- * `building` write, an `onSpawn` write that patches in the colgrep child
15867
- * PID, and a final `ready`/`failed` write. Chaining them per workspace
15868
- * guarantees the final write lands AFTER the (fire-and-forget) onSpawn
15869
- * write, so a `ready` result is never clobbered back to `building` by a
15870
- * late atomic-rename.
15871
- */
15872
- const _metaWriteChains = /* @__PURE__ */ new Map();
15873
- /** Atomically write the sidecar metadata for a workspace (serialized). */
15874
- async function writeColbertMeta(meta) {
15875
- const key = metaHashForWorkspace(meta.workspace);
15876
- const next = (_metaWriteChains.get(key) ?? Promise.resolve()).then(() => writeColbertMetaUnchained(meta));
15877
- _metaWriteChains.set(key, next.then(() => void 0, () => void 0));
15878
- return next;
15879
- }
15880
- async function writeColbertMetaUnchained(meta) {
15881
- await fs.mkdir(PATHS.COLBERT_META_DIR, { recursive: true });
15882
- const dest = metaPath(meta.workspace);
15883
- const tmp = `${dest}.${process$1.pid}.${Math.random().toString(16).slice(2, 10)}.tmp`;
15884
- try {
15885
- await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
15886
- await fs.rename(tmp, dest);
15887
- } catch (err) {
15888
- await fs.rm(tmp, { force: true }).catch(() => {});
15889
- throw err;
15890
- }
15865
+ //#region src/lib/toolbelt/extract.ts
15866
+ function baseName(p) {
15867
+ const norm = p.replace(/\\/g, "/");
15868
+ const idx = norm.lastIndexOf("/");
15869
+ return idx === -1 ? norm : norm.slice(idx + 1);
15891
15870
  }
15892
15871
  /**
15893
- * Whether a COMPLETED colgrep index exists on disk for this workspace.
15894
- * The preflight uses this to distinguish `building`/`absent` (no
15895
- * completed index → don't spawn a foreground colgrep) from a real
15896
- * index. We scan COLGREP_DATA_DIR for any per-project dir containing a
15897
- * `project.json` whose canonical path matches this workspace AND an
15898
- * `index/metadata.json` marker.
15872
+ * Extract the first regular-file member whose basename equals
15873
+ * `wantBasename` from an **xz-compressed tarball** (`.tar.xz`).
15874
+ *
15875
+ * Node's `zlib` has no xz/lzma decoder and the project carries no xz
15876
+ * dependency, so this shells out to the system `tar` (universally
15877
+ * present on macOS/Linux, which is the ONLY place a `.tar.xz` is ever
15878
+ * fetched — the colgrep Windows asset is a `.zip` handled by
15879
+ * `extractZipMember`). The xz path therefore never runs on the Windows
15880
+ * primary deployment target.
15881
+ *
15882
+ * Safety: the archive is extracted into a fresh, caller-provided temp
15883
+ * dir (NOT the cwd) and we read back ONLY the named regular-file member.
15884
+ * `tar` is invoked with `shell:false` (argv array, no metacharacter
15885
+ * surface) and `--no-same-owner` so a hostile archive can't request a
15886
+ * uid/gid change. The colgrep tarball nests its binary one dir deep
15887
+ * (`colgrep-<triple>/colgrep`), so we search recursively for the
15888
+ * basename rather than assuming a flat layout, and never follow
15889
+ * symlinks during the walk (closes the escape-the-extract-dir vector).
15890
+ *
15891
+ * Returns the member bytes, or null if the member is absent or `tar`
15892
+ * fails. The provisioner treats null as "skip / mismatch".
15899
15893
  */
15900
- async function completedIndexOnDisk(workspace) {
15901
- const indicesDir = PATHS.COLBERT_INDICES_DIR;
15902
- let names;
15894
+ async function extractTarXzMember(buf, wantBasename, tmpDir) {
15895
+ const { spawn: spawn$1 } = await import("node:child_process");
15896
+ const fs$2 = await import("node:fs/promises");
15897
+ const path$1 = await import("node:path");
15898
+ const archivePath = path$1.join(tmpDir, "archive.tar.xz");
15899
+ const extractDir = path$1.join(tmpDir, "x");
15903
15900
  try {
15904
- names = await fs.readdir(indicesDir);
15901
+ await fs$2.mkdir(extractDir, { recursive: true });
15902
+ await fs$2.writeFile(archivePath, buf);
15905
15903
  } catch {
15906
- return false;
15904
+ return null;
15907
15905
  }
15908
- const wantCanonical = await realpathForCompare(workspace);
15909
- for (const name of names) {
15910
- if (name === ".gh-router-meta") continue;
15911
- const projJson = nodePath.join(indicesDir, name, "project.json");
15912
- let proj;
15906
+ if (!await new Promise((resolve) => {
15907
+ let child;
15913
15908
  try {
15914
- proj = JSON.parse(await fs.readFile(projJson, "utf8"));
15909
+ child = spawn$1("tar", [
15910
+ "-xJf",
15911
+ archivePath,
15912
+ "-C",
15913
+ extractDir,
15914
+ "--no-same-owner"
15915
+ ], {
15916
+ stdio: "ignore",
15917
+ windowsHide: true
15918
+ });
15915
15919
  } catch {
15916
- continue;
15920
+ resolve(false);
15921
+ return;
15917
15922
  }
15918
- const projPath = proj.path ?? proj.project_path;
15919
- if (!projPath) continue;
15920
- if (await realpathForCompare(projPath) !== wantCanonical) continue;
15921
- if (existsSync(nodePath.join(indicesDir, name, "index", "metadata.json"))) return true;
15922
- if (existsSync(nodePath.join(indicesDir, name, "index"))) try {
15923
- if ((await fs.readdir(nodePath.join(indicesDir, name, "index"))).length > 0) return true;
15924
- } catch {}
15925
- }
15926
- return false;
15927
- }
15928
- function canonicalForCompare(p) {
15929
- return process$1.platform === "win32" ? nodePath.resolve(p).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(p);
15930
- }
15931
- /** Sync realpath-aware canonicalization (sibling of `realpathForCompare`,
15932
- * for the on-a-timer inactivity probe which must be synchronous). */
15933
- function canonicalRealpathSync(p) {
15923
+ const timer = setTimeout(() => {
15924
+ try {
15925
+ child.kill("SIGKILL");
15926
+ } catch {}
15927
+ resolve(false);
15928
+ }, 6e4);
15929
+ timer.unref?.();
15930
+ child.on("error", () => {
15931
+ clearTimeout(timer);
15932
+ resolve(false);
15933
+ });
15934
+ child.on("close", (code) => {
15935
+ clearTimeout(timer);
15936
+ resolve(code === 0);
15937
+ });
15938
+ })) return null;
15939
+ const found = await findRegularFile(fs$2, path$1, extractDir, new Set([wantBasename, `${wantBasename}.exe`]), 6);
15940
+ if (!found) return null;
15934
15941
  try {
15935
- return canonicalForCompare(realpathSync(p));
15942
+ return await fs$2.readFile(found);
15936
15943
  } catch {
15937
- return canonicalForCompare(p);
15944
+ return null;
15938
15945
  }
15939
15946
  }
15940
- /** Recursive (bytes, fileCount) of a directory; sync + best-effort. A
15941
- * colgrep index is a bounded set of shards so the walk stays small. */
15942
- function dirSizeSync(dir) {
15943
- let bytes = 0;
15944
- let count = 0;
15947
+ async function findRegularFile(fs$2, path$1, dir, wants, depthBudget) {
15948
+ if (depthBudget < 0) return null;
15945
15949
  let entries;
15946
15950
  try {
15947
- entries = readdirSync(dir, { withFileTypes: true });
15951
+ entries = await fs$2.readdir(dir, { withFileTypes: true });
15948
15952
  } catch {
15949
- return [0, 0];
15953
+ return null;
15950
15954
  }
15951
- for (const e of entries) {
15952
- const p = nodePath.join(dir, e.name);
15953
- if (e.isDirectory()) {
15954
- const [b, c] = dirSizeSync(p);
15955
- bytes += b;
15956
- count += c;
15957
- } else try {
15958
- bytes += statSync(p).size;
15959
- count += 1;
15960
- } catch {}
15955
+ for (const e of entries) if (e.isFile() && wants.has(e.name)) return path$1.join(dir, e.name);
15956
+ for (const e of entries) if (e.isDirectory()) {
15957
+ const hit = await findRegularFile(fs$2, path$1, path$1.join(dir, e.name), wants, depthBudget - 1);
15958
+ if (hit) return hit;
15961
15959
  }
15962
- return [bytes, count];
15960
+ return null;
15963
15961
  }
15964
15962
  /**
15965
- * (sync) Progress signature of a workspace's colgrep index dir for the init
15966
- * inactivity watchdog: `${totalBytes}:${fileCount}` of the project dir, or
15967
- * `null` if it isn't on disk yet. colgrep is SILENT on a non-TTY pipe
15968
- * during the (potentially multi-hour) encode phase, so output is useless as
15969
- * a progress signal — but it writes index shards incrementally, so a
15970
- * changing signature means "still progressing" and a frozen one means
15971
- * "hung". Successive signatures drive the watchdog: change ⇒ re-arm, frozen
15972
- * ⇒ kill. Sync because it's called from a `setTimeout` (not awaited).
15963
+ * Extract the first REGULAR-FILE tar member whose basename equals
15964
+ * `wantBasename` (optionally with a `.exe` suffix). Returns its bytes,
15965
+ * or null if absent. `buf` is the gzip-compressed tarball.
15973
15966
  */
15974
- function indexDirSignature(workspace) {
15975
- const indicesDir = PATHS.COLBERT_INDICES_DIR;
15976
- let names;
15967
+ function extractTarGzMember(buf, wantBasename) {
15968
+ let tar;
15977
15969
  try {
15978
- names = readdirSync(indicesDir);
15979
- } catch {
15980
- return null;
15981
- }
15982
- const want = canonicalRealpathSync(workspace);
15983
- for (const name of names) {
15984
- if (name === ".gh-router-meta") continue;
15985
- const dir = nodePath.join(indicesDir, name);
15986
- let proj;
15987
- try {
15988
- proj = JSON.parse(readFileSync(nodePath.join(dir, "project.json"), "utf8"));
15989
- } catch {
15990
- continue;
15991
- }
15992
- const projPath = proj.path ?? proj.project_path;
15993
- if (!projPath || canonicalRealpathSync(projPath) !== want) continue;
15994
- const [bytes, count] = dirSizeSync(dir);
15995
- return `${bytes}:${count}`;
15996
- }
15997
- return null;
15998
- }
15999
- /**
16000
- * Realpath-aware canonicalization for matching a workspace against
16001
- * colgrep's stored `project_path`. colgrep stores the OS realpath (e.g.
16002
- * macOS `/tmp` → `/private/tmp`, Windows 8.3 short names), so a plain
16003
- * `path.resolve` comparison misses. Falls back to `canonicalForCompare`
16004
- * when realpath fails (path doesn't exist yet).
16005
- */
16006
- async function realpathForCompare(p) {
16007
- try {
16008
- return canonicalForCompare(await fs.realpath(p));
16009
- } catch {
16010
- return canonicalForCompare(p);
16011
- }
16012
- }
16013
- /**
16014
- * Compute the freshness verdict for a query against a workspace.
16015
- *
16016
- * Routing (per the dropped-fallback contract):
16017
- * - `failed` — sidecar says failed → caller returns isError.
16018
- * - `building` — a tracked init is live OR no completed index on disk
16019
- * → caller returns building notice (NO results).
16020
- * - `absent` — never indexed → caller kicks a debounced background
16021
- * init, returns absent (isError).
16022
- * - `stale` — ready but HEAD moved / tree dirty since index → caller
16023
- * returns stale notice (NO results, NO re-search).
16024
- * - `fresh` — ready + completed index + HEAD matches + not newly
16025
- * dirty → caller spawns colgrep search.
16026
- */
16027
- async function freshnessVerdict(workspace) {
16028
- const meta = await readColbertMeta(workspace);
16029
- if (!meta || meta.status === "absent") return {
16030
- verdict: "absent",
16031
- meta
16032
- };
16033
- if (meta.status === "failed") return {
16034
- verdict: "failed",
16035
- meta
16036
- };
16037
- if (meta.status === "building") {
16038
- const pid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
16039
- if (isInitInFlight(workspace) || pid > 0 && isPidAlive(pid)) return {
16040
- verdict: "building",
16041
- meta
16042
- };
16043
- const startedMs = meta.lastIndexedAt ? Date.parse(meta.lastIndexedAt) : NaN;
16044
- if (Number.isFinite(startedMs) && Date.now() - startedMs < BUILD_SPAWN_GRACE_MS) return {
16045
- verdict: "building",
16046
- meta
16047
- };
16048
- if (!await completedIndexOnDisk(workspace)) return {
16049
- verdict: "crashed",
16050
- meta
16051
- };
16052
- }
16053
- if (!await completedIndexOnDisk(workspace)) return {
16054
- verdict: "building",
16055
- meta
16056
- };
16057
- const git = await gitState(workspace);
16058
- if (!git.isRepo) return {
16059
- verdict: "fresh",
16060
- meta
16061
- };
16062
- const headMoved = meta.lastIndexedHead !== void 0 && git.head !== meta.lastIndexedHead;
16063
- const newlyDirty = git.dirty && meta.lastIndexedDirty !== true;
16064
- if (headMoved || newlyDirty) return {
16065
- verdict: "stale",
16066
- meta,
16067
- head: git.head,
16068
- dirty: git.dirty
16069
- };
16070
- return {
16071
- verdict: "fresh",
16072
- meta,
16073
- head: git.head,
16074
- dirty: git.dirty
16075
- };
16076
- }
16077
- /** Cheap, bounded git probe via the native-exe runner. */
16078
- async function gitState(workspace) {
16079
- const git = resolveExecutable("git");
16080
- if (!git) return { isRepo: false };
16081
- try {
16082
- const inside = await runManagedExeCapture(git, [
16083
- "-C",
16084
- workspace,
16085
- "rev-parse",
16086
- "--is-inside-work-tree"
16087
- ], {
16088
- timeoutMs: GIT_TIMEOUT_MS,
16089
- maxStdoutBytes: 64 * 1024
16090
- });
16091
- if (inside.code !== 0 || inside.stdout.trim() !== "true") return { isRepo: false };
16092
- const head = await runManagedExeCapture(git, [
16093
- "-C",
16094
- workspace,
16095
- "rev-parse",
16096
- "HEAD"
16097
- ], {
16098
- timeoutMs: GIT_TIMEOUT_MS,
16099
- maxStdoutBytes: 64 * 1024
16100
- });
16101
- const status = await runManagedExeCapture(git, [
16102
- "-C",
16103
- workspace,
16104
- "status",
16105
- "--porcelain"
16106
- ], {
16107
- timeoutMs: GIT_TIMEOUT_MS,
16108
- maxStdoutBytes: 1024 * 1024
16109
- });
16110
- return {
16111
- isRepo: true,
16112
- head: head.code === 0 ? head.stdout.trim() || void 0 : void 0,
16113
- dirty: status.code === 0 ? status.stdout.trim().length > 0 : void 0
16114
- };
16115
- } catch {
16116
- return { isRepo: false };
16117
- }
16118
- }
16119
- const _initInFlight = /* @__PURE__ */ new Set();
16120
- /** True iff a background init for this workspace is already in flight. */
16121
- function isInitInFlight(workspace) {
16122
- return _initInFlight.has(initKey(workspace));
16123
- }
16124
- /** Mark a background init started (debounce). Returns false if already running. */
16125
- function tryClaimInit(workspace) {
16126
- const k = initKey(workspace);
16127
- if (_initInFlight.has(k)) return false;
16128
- _initInFlight.add(k);
16129
- return true;
16130
- }
16131
- /** Release the debounce claim (call in the init's finally). */
16132
- function releaseInit(workspace) {
16133
- _initInFlight.delete(initKey(workspace));
16134
- }
16135
- function initKey(workspace) {
16136
- return `${MODEL_ID}::${canonicalForCompare(workspace)}`;
16137
- }
16138
-
16139
- //#endregion
16140
- //#region src/lib/toolbelt/extract.ts
16141
- function baseName(p) {
16142
- const norm = p.replace(/\\/g, "/");
16143
- const idx = norm.lastIndexOf("/");
16144
- return idx === -1 ? norm : norm.slice(idx + 1);
16145
- }
16146
- /**
16147
- * Extract the first regular-file member whose basename equals
16148
- * `wantBasename` from an **xz-compressed tarball** (`.tar.xz`).
16149
- *
16150
- * Node's `zlib` has no xz/lzma decoder and the project carries no xz
16151
- * dependency, so this shells out to the system `tar` (universally
16152
- * present on macOS/Linux, which is the ONLY place a `.tar.xz` is ever
16153
- * fetched — the colgrep Windows asset is a `.zip` handled by
16154
- * `extractZipMember`). The xz path therefore never runs on the Windows
16155
- * primary deployment target.
16156
- *
16157
- * Safety: the archive is extracted into a fresh, caller-provided temp
16158
- * dir (NOT the cwd) and we read back ONLY the named regular-file member.
16159
- * `tar` is invoked with `shell:false` (argv array, no metacharacter
16160
- * surface) and `--no-same-owner` so a hostile archive can't request a
16161
- * uid/gid change. The colgrep tarball nests its binary one dir deep
16162
- * (`colgrep-<triple>/colgrep`), so we search recursively for the
16163
- * basename rather than assuming a flat layout, and never follow
16164
- * symlinks during the walk (closes the escape-the-extract-dir vector).
16165
- *
16166
- * Returns the member bytes, or null if the member is absent or `tar`
16167
- * fails. The provisioner treats null as "skip / mismatch".
16168
- */
16169
- async function extractTarXzMember(buf, wantBasename, tmpDir) {
16170
- const { spawn: spawn$1 } = await import("node:child_process");
16171
- const fs$2 = await import("node:fs/promises");
16172
- const path$1 = await import("node:path");
16173
- const archivePath = path$1.join(tmpDir, "archive.tar.xz");
16174
- const extractDir = path$1.join(tmpDir, "x");
16175
- try {
16176
- await fs$2.mkdir(extractDir, { recursive: true });
16177
- await fs$2.writeFile(archivePath, buf);
16178
- } catch {
16179
- return null;
16180
- }
16181
- if (!await new Promise((resolve) => {
16182
- let child;
16183
- try {
16184
- child = spawn$1("tar", [
16185
- "-xJf",
16186
- archivePath,
16187
- "-C",
16188
- extractDir,
16189
- "--no-same-owner"
16190
- ], {
16191
- stdio: "ignore",
16192
- windowsHide: true
16193
- });
16194
- } catch {
16195
- resolve(false);
16196
- return;
16197
- }
16198
- const timer = setTimeout(() => {
16199
- try {
16200
- child.kill("SIGKILL");
16201
- } catch {}
16202
- resolve(false);
16203
- }, 6e4);
16204
- timer.unref?.();
16205
- child.on("error", () => {
16206
- clearTimeout(timer);
16207
- resolve(false);
16208
- });
16209
- child.on("close", (code) => {
16210
- clearTimeout(timer);
16211
- resolve(code === 0);
16212
- });
16213
- })) return null;
16214
- const found = await findRegularFile(fs$2, path$1, extractDir, new Set([wantBasename, `${wantBasename}.exe`]), 6);
16215
- if (!found) return null;
16216
- try {
16217
- return await fs$2.readFile(found);
16218
- } catch {
16219
- return null;
16220
- }
16221
- }
16222
- async function findRegularFile(fs$2, path$1, dir, wants, depthBudget) {
16223
- if (depthBudget < 0) return null;
16224
- let entries;
16225
- try {
16226
- entries = await fs$2.readdir(dir, { withFileTypes: true });
16227
- } catch {
16228
- return null;
16229
- }
16230
- for (const e of entries) if (e.isFile() && wants.has(e.name)) return path$1.join(dir, e.name);
16231
- for (const e of entries) if (e.isDirectory()) {
16232
- const hit = await findRegularFile(fs$2, path$1, path$1.join(dir, e.name), wants, depthBudget - 1);
16233
- if (hit) return hit;
16234
- }
16235
- return null;
16236
- }
16237
- /**
16238
- * Extract the first REGULAR-FILE tar member whose basename equals
16239
- * `wantBasename` (optionally with a `.exe` suffix). Returns its bytes,
16240
- * or null if absent. `buf` is the gzip-compressed tarball.
16241
- */
16242
- function extractTarGzMember(buf, wantBasename) {
16243
- let tar;
16244
- try {
16245
- tar = gunzipSync(buf);
15970
+ tar = gunzipSync(buf);
16246
15971
  } catch {
16247
15972
  return null;
16248
15973
  }
@@ -16340,6 +16065,14 @@ function colgrepBinaryPath() {
16340
16065
  function colbertModelDir() {
16341
16066
  return nodePath.join(PATHS.COLBERT_MODELS_DIR, "LateOn-Code-edge", modelDirName());
16342
16067
  }
16068
+ /**
16069
+ * Canonical model argument for every colgrep invocation and project lookup.
16070
+ * colgrep hashes the raw model string, so slash variants fork one physical
16071
+ * index into distinct project keys on Windows.
16072
+ */
16073
+ function canonicalColbertModelDir() {
16074
+ return nodePath.resolve(colbertModelDir());
16075
+ }
16343
16076
  /** Absolute path the provisioned ORT dylib lives at. */
16344
16077
  function colbertOrtDylibPath() {
16345
16078
  const lib = ortLibAsset()?.member ?? "libonnxruntime.so";
@@ -16462,7 +16195,7 @@ async function provisionColbert() {
16462
16195
  result.status = "incomplete";
16463
16196
  });
16464
16197
  if (result.binaryPath && result.ortDylibPath && result.modelDir) {
16465
- const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath, result.modelDir);
16198
+ const smoke = await runSmokeTest(result.binaryPath, result.ortDylibPath);
16466
16199
  if (smoke.ok) {
16467
16200
  await writeFile(smokeMarkerPath(), expectedSmokeMarker()).catch(() => {});
16468
16201
  result.status = "ready";
@@ -16591,72 +16324,461 @@ async function sidecarMatches(sidecar, sha256) {
16591
16324
  * scanning stderr for colgrep's exact "ignoring" warning — if present,
16592
16325
  * the dylib didn't load and we fail the smoke test even on exit 0.
16593
16326
  */
16594
- async function runSmokeTest(binaryPath, ortDylibPath, modelDir) {
16327
+ async function runSmokeTest(binaryPath, ortDylibPath) {
16595
16328
  const tmp = nodePath.join(PATHS.COLBERT_DIR, `smoke-${process$1.pid}-${randomBytes(4).toString("hex")}`);
16596
16329
  const fixtureDir = nodePath.join(tmp, "fixture");
16597
16330
  const dataDir = nodePath.join(tmp, "data");
16598
16331
  try {
16599
- await mkdir(fixtureDir, { recursive: true });
16600
- await mkdir(dataDir, { recursive: true });
16601
- await writeFile(nodePath.join(fixtureDir, "smoke.py"), "def smoke_test_function():\n return 1\n");
16332
+ await mkdir(fixtureDir, { recursive: true });
16333
+ await mkdir(dataDir, { recursive: true });
16334
+ await writeFile(nodePath.join(fixtureDir, "smoke.py"), "def smoke_test_function():\n return 1\n");
16335
+ } catch {
16336
+ return {
16337
+ ok: false,
16338
+ reason: "smoke fixture setup failed"
16339
+ };
16340
+ }
16341
+ try {
16342
+ const env = dropColgrepSecrets({
16343
+ ...process$1.env,
16344
+ COLGREP_DATA_DIR: dataDir,
16345
+ ORT_DYLIB_PATH: ortDylibPath,
16346
+ COLGREP_FORCE_CPU: "1",
16347
+ PATH: `${nodePath.dirname(ortDylibPath)}${nodePath.delimiter}${process$1.env.PATH ?? ""}`
16348
+ });
16349
+ const res = await runManagedExeCapture(binaryPath, [
16350
+ "search",
16351
+ "--json",
16352
+ "--color",
16353
+ "never",
16354
+ "--force-cpu",
16355
+ "--model",
16356
+ canonicalColbertModelDir(),
16357
+ "-y",
16358
+ "-k",
16359
+ "1",
16360
+ "smoke",
16361
+ fixtureDir
16362
+ ], {
16363
+ env,
16364
+ timeoutMs: SMOKE_TIMEOUT_MS,
16365
+ maxStdoutBytes: 4 * 1024 * 1024
16366
+ });
16367
+ if (res.timedOut) return {
16368
+ ok: false,
16369
+ reason: "smoke test timed out"
16370
+ };
16371
+ if (res.code !== 0) return {
16372
+ ok: false,
16373
+ reason: `colgrep exited ${res.code}`
16374
+ };
16375
+ if (/not a loadable onnx runtime dylib/i.test(res.stderr)) return {
16376
+ ok: false,
16377
+ reason: "ORT dylib failed to load (ORT_DYLIB_PATH ignored)"
16378
+ };
16379
+ return { ok: true };
16380
+ } catch (err) {
16381
+ consola.debug("colbert: smoke test spawn failed:", err);
16382
+ return {
16383
+ ok: false,
16384
+ reason: "colgrep failed to launch (AV quarantine / missing runtime?)"
16385
+ };
16386
+ } finally {
16387
+ await rm(tmp, {
16388
+ recursive: true,
16389
+ force: true
16390
+ }).catch(() => {});
16391
+ }
16392
+ }
16393
+
16394
+ //#endregion
16395
+ //#region src/lib/colbert/index-store.ts
16396
+ const GIT_TIMEOUT_MS = 4e3;
16397
+ /** Grace window after a `building` write before a workspace with no live
16398
+ * build PID is declared `crashed` — covers the cross-process window where
16399
+ * one proxy wrote `building` but hasn't yet recorded the colgrep child PID. */
16400
+ const BUILD_SPAWN_GRACE_MS = 3e4;
16401
+ /**
16402
+ * Hash a workspace path the same way the metadata sidecar is keyed.
16403
+ * NOTE: this is the ROUTER-OWNED meta key, independent of colgrep's
16404
+ * internal xxh3 physical-dir key (we never need to predict colgrep's
16405
+ * key because we pass the workspace as colgrep's PATH arg and let it
16406
+ * route). A stable sha256-prefix of the canonical path is sufficient.
16407
+ */
16408
+ function metaHashForWorkspace(workspace) {
16409
+ const canonical = process$1.platform === "win32" ? nodePath.resolve(workspace).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(workspace);
16410
+ let h = 2166136261;
16411
+ for (let i = 0; i < canonical.length; i++) {
16412
+ h ^= canonical.charCodeAt(i);
16413
+ h = Math.imul(h, 16777619);
16414
+ }
16415
+ return (h >>> 0).toString(16).padStart(8, "0");
16416
+ }
16417
+ function metaPath(workspace) {
16418
+ return nodePath.join(PATHS.COLBERT_META_DIR, `${metaHashForWorkspace(workspace)}.json`);
16419
+ }
16420
+ /** Read the sidecar metadata for a workspace (null if none yet). */
16421
+ async function readColbertMeta(workspace) {
16422
+ try {
16423
+ const raw = await fs.readFile(metaPath(workspace), "utf8");
16424
+ const parsed = JSON.parse(raw);
16425
+ if (parsed && typeof parsed === "object" && typeof parsed.status === "string") return parsed;
16426
+ return null;
16427
+ } catch {
16428
+ return null;
16429
+ }
16430
+ }
16431
+ /**
16432
+ * Per-workspace write serializer. `runInit` issues a pre-spawn
16433
+ * `building` write, an `onSpawn` write that patches in the colgrep child
16434
+ * PID, and a final `ready`/`failed` write. Chaining them per workspace
16435
+ * guarantees the final write lands AFTER the (fire-and-forget) onSpawn
16436
+ * write, so a `ready` result is never clobbered back to `building` by a
16437
+ * late atomic-rename.
16438
+ */
16439
+ const _metaWriteChains = /* @__PURE__ */ new Map();
16440
+ /** Atomically write the sidecar metadata for a workspace (serialized). */
16441
+ async function writeColbertMeta(meta) {
16442
+ const key = metaHashForWorkspace(meta.workspace);
16443
+ const next = (_metaWriteChains.get(key) ?? Promise.resolve()).then(() => writeColbertMetaUnchained(meta));
16444
+ _metaWriteChains.set(key, next.then(() => void 0, () => void 0));
16445
+ return next;
16446
+ }
16447
+ async function writeColbertMetaUnchained(meta) {
16448
+ await fs.mkdir(PATHS.COLBERT_META_DIR, { recursive: true });
16449
+ const dest = metaPath(meta.workspace);
16450
+ const tmp = `${dest}.${process$1.pid}.${Math.random().toString(16).slice(2, 10)}.tmp`;
16451
+ try {
16452
+ await fs.writeFile(tmp, JSON.stringify(meta, null, 2));
16453
+ await fs.rename(tmp, dest);
16454
+ } catch (err) {
16455
+ await fs.rm(tmp, { force: true }).catch(() => {});
16456
+ throw err;
16457
+ }
16458
+ }
16459
+ /**
16460
+ * Whether a COMPLETED colgrep index exists on disk for this workspace.
16461
+ * The preflight uses this to distinguish `building`/`absent` (no
16462
+ * completed index → don't spawn a foreground colgrep) from a real
16463
+ * index. We scan COLGREP_DATA_DIR for any per-project dir containing a
16464
+ * `project.json` whose canonical path matches this workspace AND an
16465
+ * `index/metadata.json` marker.
16466
+ */
16467
+ async function colbertProjectDir(workspace) {
16468
+ const indicesDir = PATHS.COLBERT_INDICES_DIR;
16469
+ let names;
16470
+ try {
16471
+ names = await fs.readdir(indicesDir);
16472
+ } catch {
16473
+ return null;
16474
+ }
16475
+ const wantCanonical = await realpathForCompare(workspace);
16476
+ for (const name of names) {
16477
+ if (name === ".gh-router-meta" || name.includes(".corrupt-")) continue;
16478
+ const dir = nodePath.join(indicesDir, name);
16479
+ let proj;
16480
+ try {
16481
+ proj = JSON.parse(await fs.readFile(nodePath.join(dir, "project.json"), "utf8"));
16482
+ } catch {
16483
+ continue;
16484
+ }
16485
+ const projPath = proj.path ?? proj.project_path;
16486
+ if (!projPath || await realpathForCompare(projPath) !== wantCanonical) continue;
16487
+ if (proj.model !== canonicalColbertModelDir()) continue;
16488
+ return dir;
16489
+ }
16490
+ return null;
16491
+ }
16492
+ /** Validate the contiguous embedding interval encoded by PLAID shard metadata. */
16493
+ function validateIndexIntegrity(projectIndexDir) {
16494
+ let names;
16495
+ try {
16496
+ names = readdirSync(projectIndexDir).filter((name) => /^\d+\.metadata\.json$/.test(name));
16497
+ } catch (err) {
16498
+ return err.code === "ENOENT" ? { verdict: "not-built" } : {
16499
+ verdict: "corrupt",
16500
+ reason: "index directory unreadable"
16501
+ };
16502
+ }
16503
+ if (names.length === 0) return { verdict: "not-built" };
16504
+ const intervals = [];
16505
+ for (const name of names) try {
16506
+ const parsed = JSON.parse(readFileSync(nodePath.join(projectIndexDir, name), "utf8"));
16507
+ const start = parsed.embedding_offset;
16508
+ const count = parsed.num_embeddings;
16509
+ if (typeof start !== "number" || typeof count !== "number" || !Number.isSafeInteger(start) || !Number.isSafeInteger(count) || start < 0 || count < 0) return {
16510
+ verdict: "corrupt",
16511
+ reason: `invalid shard metadata: ${name}`
16512
+ };
16513
+ intervals.push({
16514
+ start,
16515
+ count
16516
+ });
16517
+ } catch {
16518
+ return {
16519
+ verdict: "corrupt",
16520
+ reason: `unreadable shard metadata: ${name}`
16521
+ };
16522
+ }
16523
+ intervals.sort((a, b) => a.start - b.start || a.count - b.count);
16524
+ let cursorEnd = 0;
16525
+ let sum = 0;
16526
+ let gapped = false;
16527
+ for (const interval of intervals) {
16528
+ if (interval.start < cursorEnd) return {
16529
+ verdict: "corrupt",
16530
+ reason: "overlapping shard intervals"
16531
+ };
16532
+ if (interval.start > cursorEnd) gapped = true;
16533
+ cursorEnd = interval.start + interval.count;
16534
+ sum += interval.count;
16535
+ }
16536
+ if (gapped) return {
16537
+ verdict: "suspect",
16538
+ reason: "gap between shard intervals"
16539
+ };
16540
+ return {
16541
+ verdict: "coherent",
16542
+ shardCount: intervals.length,
16543
+ embeddingCount: sum
16544
+ };
16545
+ }
16546
+ async function completedIndexOnDisk(workspace) {
16547
+ const projectDir = await colbertProjectDir(workspace);
16548
+ if (!projectDir) return false;
16549
+ return validateIndexIntegrity(nodePath.join(projectDir, "index")).verdict !== "not-built";
16550
+ }
16551
+ function canonicalForCompare(p) {
16552
+ return process$1.platform === "win32" ? nodePath.resolve(p).toLowerCase().replace(/\\/g, "/") : nodePath.resolve(p);
16553
+ }
16554
+ /** Sync realpath-aware canonicalization (sibling of `realpathForCompare`,
16555
+ * for the on-a-timer inactivity probe which must be synchronous). */
16556
+ function canonicalRealpathSync(p) {
16557
+ try {
16558
+ return canonicalForCompare(realpathSync(p));
16559
+ } catch {
16560
+ return canonicalForCompare(p);
16561
+ }
16562
+ }
16563
+ /** Recursive (bytes, fileCount) of a directory; sync + best-effort. A
16564
+ * colgrep index is a bounded set of shards so the walk stays small. */
16565
+ function dirSizeSync(dir) {
16566
+ let bytes = 0;
16567
+ let count = 0;
16568
+ let entries;
16569
+ try {
16570
+ entries = readdirSync(dir, { withFileTypes: true });
16571
+ } catch {
16572
+ return [0, 0];
16573
+ }
16574
+ for (const e of entries) {
16575
+ const p = nodePath.join(dir, e.name);
16576
+ if (e.isDirectory()) {
16577
+ const [b, c] = dirSizeSync(p);
16578
+ bytes += b;
16579
+ count += c;
16580
+ } else try {
16581
+ bytes += statSync(p).size;
16582
+ count += 1;
16583
+ } catch {}
16584
+ }
16585
+ return [bytes, count];
16586
+ }
16587
+ /**
16588
+ * (sync) Progress signature of a workspace's colgrep index dir for the init
16589
+ * inactivity watchdog: `${totalBytes}:${fileCount}` of the project dir, or
16590
+ * `null` if it isn't on disk yet. colgrep is SILENT on a non-TTY pipe
16591
+ * during the (potentially multi-hour) encode phase, so output is useless as
16592
+ * a progress signal — but it writes index shards incrementally, so a
16593
+ * changing signature means "still progressing" and a frozen one means
16594
+ * "hung". Successive signatures drive the watchdog: change ⇒ re-arm, frozen
16595
+ * ⇒ kill. Sync because it's called from a `setTimeout` (not awaited).
16596
+ */
16597
+ function indexDirSignature(workspace) {
16598
+ const indicesDir = PATHS.COLBERT_INDICES_DIR;
16599
+ let names;
16600
+ try {
16601
+ names = readdirSync(indicesDir);
16602
+ } catch {
16603
+ return null;
16604
+ }
16605
+ const want = canonicalRealpathSync(workspace);
16606
+ for (const name of names) {
16607
+ if (name === ".gh-router-meta") continue;
16608
+ if (name.includes(".corrupt-")) continue;
16609
+ const dir = nodePath.join(indicesDir, name);
16610
+ let proj;
16611
+ try {
16612
+ proj = JSON.parse(readFileSync(nodePath.join(dir, "project.json"), "utf8"));
16613
+ } catch {
16614
+ continue;
16615
+ }
16616
+ const projPath = proj.path ?? proj.project_path;
16617
+ if (!projPath || canonicalRealpathSync(projPath) !== want) continue;
16618
+ const [bytes, count] = dirSizeSync(dir);
16619
+ return `${bytes}:${count}`;
16620
+ }
16621
+ return null;
16622
+ }
16623
+ /**
16624
+ * Realpath-aware canonicalization for matching a workspace against
16625
+ * colgrep's stored `project_path`. colgrep stores the OS realpath (e.g.
16626
+ * macOS `/tmp` → `/private/tmp`, Windows 8.3 short names), so a plain
16627
+ * `path.resolve` comparison misses. Falls back to `canonicalForCompare`
16628
+ * when realpath fails (path doesn't exist yet).
16629
+ */
16630
+ async function realpathForCompare(p) {
16631
+ try {
16632
+ return canonicalForCompare(await fs.realpath(p));
16602
16633
  } catch {
16603
- return {
16604
- ok: false,
16605
- reason: "smoke fixture setup failed"
16634
+ return canonicalForCompare(p);
16635
+ }
16636
+ }
16637
+ /**
16638
+ * Compute the freshness verdict for a query against a workspace.
16639
+ *
16640
+ * Routing (per the dropped-fallback contract):
16641
+ * - `failed` — sidecar says failed → caller returns isError.
16642
+ * - `building` — a tracked init is live OR no completed index on disk
16643
+ * → caller returns building notice (NO results).
16644
+ * - `absent` — never indexed → caller kicks a debounced background
16645
+ * init, returns absent (isError).
16646
+ * - `stale` — ready but HEAD moved / tree dirty since index → caller
16647
+ * returns stale notice (NO results, NO re-search).
16648
+ * - `fresh` — ready + completed index + HEAD matches + not newly
16649
+ * dirty → caller spawns colgrep search.
16650
+ */
16651
+ async function freshnessVerdict(workspace) {
16652
+ const meta = await readColbertMeta(workspace);
16653
+ if (!meta || meta.status === "absent") return {
16654
+ verdict: "absent",
16655
+ meta
16656
+ };
16657
+ if (meta.status === "failed") return {
16658
+ verdict: "failed",
16659
+ meta
16660
+ };
16661
+ if (meta.status === "building") {
16662
+ const pid = typeof meta.buildPid === "number" ? meta.buildPid : 0;
16663
+ if (isInitInFlight(workspace) || pid > 0 && isPidAlive(pid)) return {
16664
+ verdict: "building",
16665
+ meta
16666
+ };
16667
+ const startedMs = meta.lastIndexedAt ? Date.parse(meta.lastIndexedAt) : NaN;
16668
+ if (Number.isFinite(startedMs) && Date.now() - startedMs < BUILD_SPAWN_GRACE_MS) return {
16669
+ verdict: "building",
16670
+ meta
16671
+ };
16672
+ if (!await completedIndexOnDisk(workspace)) return {
16673
+ verdict: "crashed",
16674
+ meta
16606
16675
  };
16607
16676
  }
16677
+ const projectDir = await colbertProjectDir(workspace);
16678
+ if (!projectDir) return {
16679
+ verdict: "building",
16680
+ meta
16681
+ };
16682
+ const integrity = validateIndexIntegrity(nodePath.join(projectDir, "index"));
16683
+ if (integrity.verdict === "not-built") return {
16684
+ verdict: "building",
16685
+ meta
16686
+ };
16687
+ if (integrity.verdict === "corrupt") return {
16688
+ verdict: "corrupt",
16689
+ meta
16690
+ };
16691
+ if (integrity.verdict === "suspect") return {
16692
+ verdict: "stale",
16693
+ meta
16694
+ };
16695
+ const binarySha = colgrepBinAsset()?.sha256;
16696
+ const ortSha = ortLibAsset()?.sha256;
16697
+ if (binarySha && meta.binarySha !== binarySha || ortSha && meta.ortSha !== ortSha) return {
16698
+ verdict: "stale",
16699
+ meta
16700
+ };
16701
+ const git = await gitState(workspace);
16702
+ if (!git.isRepo) return {
16703
+ verdict: "fresh",
16704
+ meta
16705
+ };
16706
+ const headMoved = meta.lastIndexedHead !== void 0 && git.head !== meta.lastIndexedHead;
16707
+ const newlyDirty = git.dirty && meta.lastIndexedDirty !== true;
16708
+ if (headMoved || newlyDirty) return {
16709
+ verdict: "stale",
16710
+ meta,
16711
+ head: git.head,
16712
+ dirty: git.dirty
16713
+ };
16714
+ return {
16715
+ verdict: "fresh",
16716
+ meta,
16717
+ head: git.head,
16718
+ dirty: git.dirty
16719
+ };
16720
+ }
16721
+ /** Cheap, bounded git probe via the native-exe runner. */
16722
+ async function gitState(workspace) {
16723
+ const git = resolveExecutable("git");
16724
+ if (!git) return { isRepo: false };
16608
16725
  try {
16609
- const env = dropColgrepSecrets({
16610
- ...process$1.env,
16611
- COLGREP_DATA_DIR: dataDir,
16612
- ORT_DYLIB_PATH: ortDylibPath,
16613
- COLGREP_FORCE_CPU: "1",
16614
- PATH: `${nodePath.dirname(ortDylibPath)}${nodePath.delimiter}${process$1.env.PATH ?? ""}`
16726
+ const inside = await runManagedExeCapture(git, [
16727
+ "-C",
16728
+ workspace,
16729
+ "rev-parse",
16730
+ "--is-inside-work-tree"
16731
+ ], {
16732
+ timeoutMs: GIT_TIMEOUT_MS,
16733
+ maxStdoutBytes: 64 * 1024
16615
16734
  });
16616
- const res = await runManagedExeCapture(binaryPath, [
16617
- "search",
16618
- "--json",
16619
- "--color",
16620
- "never",
16621
- "--force-cpu",
16622
- "--model",
16623
- modelDir,
16624
- "-y",
16625
- "-k",
16626
- "1",
16627
- "smoke",
16628
- fixtureDir
16735
+ if (inside.code !== 0 || inside.stdout.trim() !== "true") return { isRepo: false };
16736
+ const head = await runManagedExeCapture(git, [
16737
+ "-C",
16738
+ workspace,
16739
+ "rev-parse",
16740
+ "HEAD"
16629
16741
  ], {
16630
- env,
16631
- timeoutMs: SMOKE_TIMEOUT_MS,
16632
- maxStdoutBytes: 4 * 1024 * 1024
16742
+ timeoutMs: GIT_TIMEOUT_MS,
16743
+ maxStdoutBytes: 64 * 1024
16744
+ });
16745
+ const status = await runManagedExeCapture(git, [
16746
+ "-C",
16747
+ workspace,
16748
+ "status",
16749
+ "--porcelain"
16750
+ ], {
16751
+ timeoutMs: GIT_TIMEOUT_MS,
16752
+ maxStdoutBytes: 1024 * 1024
16633
16753
  });
16634
- if (res.timedOut) return {
16635
- ok: false,
16636
- reason: "smoke test timed out"
16637
- };
16638
- if (res.code !== 0) return {
16639
- ok: false,
16640
- reason: `colgrep exited ${res.code}`
16641
- };
16642
- if (/not a loadable onnx runtime dylib/i.test(res.stderr)) return {
16643
- ok: false,
16644
- reason: "ORT dylib failed to load (ORT_DYLIB_PATH ignored)"
16645
- };
16646
- return { ok: true };
16647
- } catch (err) {
16648
- consola.debug("colbert: smoke test spawn failed:", err);
16649
16754
  return {
16650
- ok: false,
16651
- reason: "colgrep failed to launch (AV quarantine / missing runtime?)"
16755
+ isRepo: true,
16756
+ head: head.code === 0 ? head.stdout.trim() || void 0 : void 0,
16757
+ dirty: status.code === 0 ? status.stdout.trim().length > 0 : void 0
16652
16758
  };
16653
- } finally {
16654
- await rm(tmp, {
16655
- recursive: true,
16656
- force: true
16657
- }).catch(() => {});
16759
+ } catch {
16760
+ return { isRepo: false };
16658
16761
  }
16659
16762
  }
16763
+ const _initInFlight = /* @__PURE__ */ new Set();
16764
+ /** True iff a background init for this workspace is already in flight. */
16765
+ function isInitInFlight(workspace) {
16766
+ return _initInFlight.has(initKey(workspace));
16767
+ }
16768
+ /** Mark a background init started (debounce). Returns false if already running. */
16769
+ function tryClaimInit(workspace) {
16770
+ const k = initKey(workspace);
16771
+ if (_initInFlight.has(k)) return false;
16772
+ _initInFlight.add(k);
16773
+ return true;
16774
+ }
16775
+ /** Release the debounce claim (call in the init's finally). */
16776
+ function releaseInit(workspace) {
16777
+ _initInFlight.delete(initKey(workspace));
16778
+ }
16779
+ function initKey(workspace) {
16780
+ return `${MODEL_ID}::${canonicalForCompare(workspace)}`;
16781
+ }
16660
16782
 
16661
16783
  //#endregion
16662
16784
  //#region src/lib/colbert/runner.ts
@@ -16727,6 +16849,10 @@ function makeIndexProgressProbe(workspace) {
16727
16849
  * colgrep writer per workspace" goal as the init debounce. Cleared when the
16728
16850
  * detached search completes. */
16729
16851
  const _searchIndexInFlight = /* @__PURE__ */ new Set();
16852
+ const _initPromises = /* @__PURE__ */ new Map();
16853
+ /** In-flight quarantine deletions, drained by teardown (Windows EBUSY). */
16854
+ const _quarantineRemovals = /* @__PURE__ */ new Set();
16855
+ let _runManagedExeCapture = runManagedExeCapture;
16730
16856
  /** Build the isolating env for any colgrep child (search or init). */
16731
16857
  function colgrepEnv() {
16732
16858
  const ortDir = nodePath.dirname(colbertOrtDylibPath());
@@ -16764,6 +16890,7 @@ async function runSemanticSearch(opts) {
16764
16890
  };
16765
16891
  case "failed": return handleFailure(workspace, fresh.meta, false);
16766
16892
  case "crashed": return handleFailure(workspace, fresh.meta, true);
16893
+ case "corrupt": return repairCorruptIndex(workspace, fresh.meta);
16767
16894
  case "building": return {
16768
16895
  status: "building",
16769
16896
  notice: "semantic index is being built for this workspace; retry shortly (or use code_search now)"
@@ -16796,6 +16923,64 @@ async function runSemanticSearch(opts) {
16796
16923
  * by the inactivity watchdog) retries at most once — re-running a hung build
16797
16924
  * usually hangs again; transient classes retry up to `MAX_FAILED_ATTEMPTS`.
16798
16925
  */
16926
+ async function quarantineProjectDir(projectDir) {
16927
+ const quarantine = `${projectDir}.corrupt-${process$1.pid}-${randomBytes(4).toString("hex")}`;
16928
+ try {
16929
+ await fs.rename(projectDir, quarantine);
16930
+ } catch (err) {
16931
+ consola.debug("colbert: corrupt index quarantine rename failed:", err);
16932
+ return false;
16933
+ }
16934
+ const removal = fs.rm(quarantine, {
16935
+ recursive: true,
16936
+ force: true
16937
+ }).catch((err) => {
16938
+ consola.debug("colbert: corrupt index quarantine cleanup failed:", err);
16939
+ }).finally(() => {
16940
+ _quarantineRemovals.delete(removal);
16941
+ });
16942
+ _quarantineRemovals.add(removal);
16943
+ return true;
16944
+ }
16945
+ async function repairCorruptIndex(workspace, meta) {
16946
+ const wsKey = nodePath.resolve(workspace);
16947
+ const attempts = (meta?.failureClass === "corrupt" ? meta.failedAttempts ?? 0 : 0) + 1;
16948
+ const failedMeta = {
16949
+ workspace,
16950
+ model: meta?.model ?? MODEL_ID,
16951
+ modelRev: meta?.modelRev ?? MODEL_REVISION,
16952
+ binarySha: colgrepBinAsset()?.sha256,
16953
+ ortSha: ortLibAsset()?.sha256,
16954
+ status: "failed",
16955
+ failureClass: "corrupt",
16956
+ failedAttempts: attempts,
16957
+ lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString(),
16958
+ lastIndexedHead: meta?.lastIndexedHead,
16959
+ lastIndexedDirty: meta?.lastIndexedDirty,
16960
+ ownerInstanceId: getColbertInstanceUuid()
16961
+ };
16962
+ if (_searchIndexInFlight.has(wsKey) || isInitInFlight(workspace)) return {
16963
+ status: "building",
16964
+ notice: "semantic index was found corrupt but a writer is still active; returned results are disabled until it exits"
16965
+ };
16966
+ const projectDir = await colbertProjectDir(workspace);
16967
+ if (!projectDir) return handleFailure(workspace, failedMeta, false);
16968
+ if (!await quarantineProjectDir(projectDir)) {
16969
+ await writeColbertMeta(failedMeta).catch(() => {});
16970
+ return {
16971
+ status: "failed",
16972
+ isError: true,
16973
+ notice: "semantic index is corrupt but could not be quarantined; returned lexical results — close active colgrep processes and retry"
16974
+ };
16975
+ }
16976
+ await writeColbertMeta(failedMeta).catch(() => {});
16977
+ if (attempts < 2) kickBackgroundInit(workspace);
16978
+ return {
16979
+ status: "failed",
16980
+ isError: true,
16981
+ notice: attempts < 2 ? "semantic index was found corrupt and quarantined; a clean rebuild was started — retry mode:\"semantic\" shortly" : "semantic index repeatedly failed integrity checks; automatic rebuild is capped — do NOT retry mode:\"semantic\", use lexical search with specific symbol/keyword terms and see proxy logs"
16982
+ };
16983
+ }
16799
16984
  async function handleFailure(workspace, meta, crashedVerdict) {
16800
16985
  const cls = crashedVerdict ? "crashed" : meta?.failureClass ?? "error";
16801
16986
  const attempts = crashedVerdict ? (meta?.failedAttempts ?? 0) + 1 : meta?.failedAttempts ?? 1;
@@ -16812,7 +16997,7 @@ async function handleFailure(workspace, meta, crashedVerdict) {
16812
16997
  lastIndexedDirty: meta?.lastIndexedDirty,
16813
16998
  ownerInstanceId: getColbertInstanceUuid()
16814
16999
  }).catch(() => {});
16815
- const cap = cls === "stuck" ? 2 : MAX_FAILED_ATTEMPTS;
17000
+ const cap = cls === "stuck" || cls === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
16816
17001
  const lastMs = lastAt ? Date.parse(lastAt) : NaN;
16817
17002
  const backoffElapsed = !Number.isFinite(lastMs) || Date.now() - lastMs >= FAILED_RETRY_BACKOFF_MS;
16818
17003
  if (attempts < cap && backoffElapsed) {
@@ -16855,7 +17040,7 @@ async function spawnSearch(opts) {
16855
17040
  "never",
16856
17041
  "--force-cpu",
16857
17042
  "--model",
16858
- colbertModelDir(),
17043
+ canonicalColbertModelDir(),
16859
17044
  "-y",
16860
17045
  "-k",
16861
17046
  String(opts.limit)
@@ -16870,7 +17055,7 @@ async function spawnSearch(opts) {
16870
17055
  _searchIndexInFlight.add(wsKey);
16871
17056
  let searchPromise;
16872
17057
  try {
16873
- searchPromise = runManagedExeCapture(binary, args, {
17058
+ searchPromise = _runManagedExeCapture(binary, args, {
16874
17059
  env: colgrepEnv(),
16875
17060
  inactivityTimeoutMs: INIT_STALL_MS,
16876
17061
  onInactivityCheck: makeIndexProgressProbe(opts.workspace),
@@ -16986,14 +17171,24 @@ function buildSnippet(unit) {
16986
17171
  const sig = typeof unit.signature === "string" ? unit.signature.trim() : "";
16987
17172
  const code = typeof unit.code === "string" ? unit.code : "";
16988
17173
  if (!code) return sig;
16989
- const body = code.split("\n").slice(0, 6).join("\n");
16990
- const snippet = sig && !body.startsWith(sig) ? `${sig}\n${body}` : body;
17174
+ const lines = code.split("\n");
17175
+ const body = lines.slice(0, 6).join("\n");
17176
+ const firstBodyLine = lines[0]?.trim() ?? "";
17177
+ const snippet = sig && firstBodyLine !== sig ? `${sig}\n${body}` : body;
16991
17178
  return snippet.length > 600 ? snippet.slice(0, 600) + "…" : snippet;
16992
17179
  }
17180
+ function stripExtendedPathPrefix(value) {
17181
+ if (/^\\\\\?\\UNC\\/i.test(value)) return `\\\\${value.slice(8)}`;
17182
+ if (/^\\\\\?\\[a-z]:\\/i.test(value)) return value.slice(4);
17183
+ return value;
17184
+ }
16993
17185
  function relativize(file, workspace, workspaceReal) {
16994
- for (const base of [workspace, workspaceReal]) try {
16995
- const rel = nodePath.relative(base, file);
16996
- if (rel && !rel.startsWith("..") && !nodePath.isAbsolute(rel)) return rel;
17186
+ const normalizedFile = stripExtendedPathPrefix(file);
17187
+ for (const rawBase of [workspace, workspaceReal]) try {
17188
+ const base = stripExtendedPathPrefix(rawBase);
17189
+ const flavor = /^[a-z]:[\\/]/i.test(base) || base.startsWith("\\\\") ? nodePath.win32 : nodePath;
17190
+ const rel = flavor.relative(base, normalizedFile);
17191
+ if (rel && !rel.startsWith("..") && !flavor.isAbsolute(rel)) return rel;
16997
17192
  } catch {}
16998
17193
  return file;
16999
17194
  }
@@ -17020,9 +17215,31 @@ function clampLimit(limit) {
17020
17215
  function kickBackgroundInit(workspace) {
17021
17216
  if (isInitInFlight(workspace)) return;
17022
17217
  if (!tryClaimInit(workspace)) return;
17023
- runInit(workspace).catch((err) => {
17024
- consola.debug("colbert: background init failed:", err);
17218
+ const key = nodePath.resolve(workspace);
17219
+ const promise = runInit(workspace).catch(async (err) => {
17220
+ releaseInit(workspace);
17221
+ consola.error("colbert: background init failed:", err);
17222
+ const prior = await readColbertMeta(workspace);
17223
+ await writeColbertMeta({
17224
+ workspace,
17225
+ model: prior?.model ?? MODEL_ID,
17226
+ modelRev: prior?.modelRev ?? MODEL_REVISION,
17227
+ binarySha: colgrepBinAsset()?.sha256,
17228
+ ortSha: ortLibAsset()?.sha256,
17229
+ status: "failed",
17230
+ failureClass: "launch",
17231
+ failedAttempts: (prior?.failedAttempts ?? 0) + 1,
17232
+ lastIndexedAt: (/* @__PURE__ */ new Date()).toISOString(),
17233
+ lastIndexedHead: prior?.lastIndexedHead,
17234
+ lastIndexedDirty: prior?.lastIndexedDirty,
17235
+ ownerInstanceId: getColbertInstanceUuid()
17236
+ }).catch((writeErr) => {
17237
+ consola.error("colbert: failed to record background init failure:", writeErr);
17238
+ });
17239
+ }).finally(() => {
17240
+ _initPromises.delete(key);
17025
17241
  });
17242
+ _initPromises.set(key, promise);
17026
17243
  }
17027
17244
  /**
17028
17245
  * Whether the STARTUP auto-kick should fire for a workspace. Skips a build
@@ -17035,25 +17252,72 @@ function kickBackgroundInit(workspace) {
17035
17252
  async function startupKickAllowed(workspace) {
17036
17253
  const meta = await readColbertMeta(workspace);
17037
17254
  if (!meta || meta.status !== "failed") return true;
17038
- if ((meta.failedAttempts ?? 0) >= MAX_FAILED_ATTEMPTS) return false;
17255
+ const cap = meta.failureClass === "stuck" || meta.failureClass === "corrupt" ? 2 : MAX_FAILED_ATTEMPTS;
17256
+ if ((meta.failedAttempts ?? 0) >= cap) return false;
17039
17257
  if (meta.failureClass === "stuck") return false;
17040
17258
  return true;
17041
17259
  }
17260
+ /**
17261
+ * Encoding sessions colgrep may run in parallel: 25% of the machine's
17262
+ * threads, never fewer than 2.
17263
+ *
17264
+ * colgrep defaults this to the FULL CPU count, so a background index build
17265
+ * saturates the machine — the exact opposite of what a background build
17266
+ * should do during an interactive agent session (the proxy even holds a
17267
+ * keep-awake assertion so those sessions run long and unattended). The floor
17268
+ * of 2 keeps a 1- to 7-thread box from dropping to a single session and
17269
+ * taking proportionally forever.
17270
+ *
17271
+ * Override with `GH_ROUTER_COLBERT_PARALLEL` (a positive integer).
17272
+ */
17273
+ function colbertParallelSessions() {
17274
+ const raw = Number(process$1.env.GH_ROUTER_COLBERT_PARALLEL);
17275
+ if (Number.isSafeInteger(raw) && raw > 0) return raw;
17276
+ const threads = os.availableParallelism?.() ?? os.cpus?.().length ?? 4;
17277
+ return Math.max(2, Math.floor(threads * .25));
17278
+ }
17279
+ /**
17280
+ * Persist the parallelism cap into the router-owned colgrep config.
17281
+ *
17282
+ * `--parallel` exists only on the `settings` subcommand — there is no
17283
+ * per-run flag and no env var. It writes `parallel_sessions` to
17284
+ * `<COLGREP_DATA_DIR>/../config.json`, which for us is
17285
+ * `<APP_DIR>/colbert/config.json`: router-owned, and NOT the user's own
17286
+ * colgrep config (verified — after we write ours, a plain `colgrep
17287
+ * settings` with no COLGREP_DATA_DIR still reports `auto`).
17288
+ *
17289
+ * Best-effort: failing here means colgrep encodes at its default (all
17290
+ * threads), which is greedy but not incorrect, so it must never block a
17291
+ * build.
17292
+ */
17293
+ async function applyParallelismCap(binary) {
17294
+ const sessions$1 = colbertParallelSessions();
17295
+ try {
17296
+ await _runManagedExeCapture(binary, [
17297
+ "settings",
17298
+ "--parallel",
17299
+ String(sessions$1)
17300
+ ], {
17301
+ env: colgrepEnv(),
17302
+ timeoutMs: 3e4,
17303
+ maxStdoutBytes: MAX_STDOUT_BYTES
17304
+ });
17305
+ } catch (err) {
17306
+ consola.debug("colbert: could not cap colgrep parallelism:", err);
17307
+ }
17308
+ }
17042
17309
  async function runInit(workspace) {
17043
17310
  const binary = colgrepBinaryPath();
17044
- if (!existsSync(binary)) {
17045
- releaseInit(workspace);
17046
- return;
17047
- }
17048
- if (!existsSync(colbertOrtDylibPath())) {
17049
- releaseInit(workspace);
17050
- return;
17051
- }
17311
+ if (!existsSync(binary)) throw new Error("colgrep binary is missing");
17312
+ if (!existsSync(colbertOrtDylibPath())) throw new Error("ColBERT ONNX runtime is missing");
17313
+ await applyParallelismCap(binary);
17052
17314
  const prior = await readColbertMeta(workspace);
17053
17315
  const baseMeta = {
17054
17316
  workspace,
17055
17317
  model: MODEL_ID,
17056
17318
  modelRev: MODEL_REVISION,
17319
+ binarySha: colgrepBinAsset()?.sha256,
17320
+ ortSha: ortLibAsset()?.sha256,
17057
17321
  status: "building",
17058
17322
  buildPid: void 0,
17059
17323
  ownerInstanceId: getColbertInstanceUuid(),
@@ -17075,7 +17339,7 @@ async function runInit(workspace) {
17075
17339
  "never",
17076
17340
  "--force-cpu",
17077
17341
  "--model",
17078
- colbertModelDir(),
17342
+ canonicalColbertModelDir(),
17079
17343
  workspace
17080
17344
  ];
17081
17345
  const onInactivityCheck = makeIndexProgressProbe(workspace);
@@ -17083,7 +17347,7 @@ async function runInit(workspace) {
17083
17347
  let ok$3 = false;
17084
17348
  let failureClass;
17085
17349
  try {
17086
- const res = await runManagedExeCapture(binary, args, {
17350
+ const res = await _runManagedExeCapture(binary, args, {
17087
17351
  env: colgrepEnv(),
17088
17352
  timeoutMs: INIT_TIMEOUT_MS,
17089
17353
  inactivityTimeoutMs: INIT_STALL_MS,
@@ -17098,10 +17362,18 @@ async function runInit(workspace) {
17098
17362
  }
17099
17363
  });
17100
17364
  ok$3 = !res.stalled && !res.timedOut && res.code === 0;
17101
- if (!ok$3) failureClass = res.stalled || res.timedOut ? "stuck" : "error";
17102
- } catch {
17365
+ if (ok$3) {
17366
+ const projectDir = await colbertProjectDir(workspace);
17367
+ ok$3 = projectDir !== null && validateIndexIntegrity(nodePath.join(projectDir, "index")).verdict === "coherent";
17368
+ if (!ok$3) {
17369
+ failureClass = "corrupt";
17370
+ if (projectDir) await quarantineProjectDir(projectDir);
17371
+ }
17372
+ } else failureClass = res.stalled || res.timedOut ? "stuck" : "error";
17373
+ } catch (err) {
17103
17374
  ok$3 = false;
17104
17375
  failureClass = "launch";
17376
+ consola.error("colbert: init failed to launch:", err);
17105
17377
  } finally {
17106
17378
  releaseInit(workspace);
17107
17379
  }
@@ -17204,8 +17476,10 @@ function lexicalSearchCodeMode(mode) {
17204
17476
  * it both levers: retry `mode:"semantic"` shortly (the index is self-healing
17205
17477
  * in the background) OR re-query now with specific symbol/keyword terms.
17206
17478
  */
17479
+ const FALLBACK_GUIDANCE_MARKER = "retry mode:\"semantic\"";
17480
+ const FALLBACK_GUIDANCE = `${FALLBACK_GUIDANCE_MARKER} shortly, or re-query now with specific symbol/keyword terms`;
17207
17481
  function fallbackNoticeFor(status) {
17208
- const tail = "retry mode:\"semantic\" shortly, or re-query now with specific symbol/keyword terms";
17482
+ const tail = FALLBACK_GUIDANCE;
17209
17483
  switch (status) {
17210
17484
  case "building": return `semantic index is building; returned lexical keyword matches — ${tail}`;
17211
17485
  case "stale": return `semantic index predates the current HEAD/tree (a background re-index was started); returned lexical keyword matches — ${tail}`;
@@ -17224,6 +17498,38 @@ function joinNotice(primary, secondary) {
17224
17498
  if (primary && secondary) return `${primary} (${secondary})`;
17225
17499
  return primary || secondary || void 0;
17226
17500
  }
17501
+ /** Preserve runner-specific context while guaranteeing actionable guidance once. */
17502
+ function semanticFallbackNotice(sem) {
17503
+ if (!sem.notice) return fallbackNoticeFor(sem.status);
17504
+ if (sem.notice.includes(FALLBACK_GUIDANCE_MARKER)) return sem.notice;
17505
+ return `${sem.notice} — ${FALLBACK_GUIDANCE}`;
17506
+ }
17507
+ async function outlinesForSemanticResults(input, results, signal) {
17508
+ if (input.summary === false) return void 0;
17509
+ const seen = /* @__PURE__ */ new Set();
17510
+ const files = [];
17511
+ for (const result of results) {
17512
+ if (seen.has(result.file)) continue;
17513
+ seen.add(result.file);
17514
+ files.push(result.file);
17515
+ if (files.length >= 10) break;
17516
+ }
17517
+ const outlines = [];
17518
+ const deadline = Date.now() + 2e3;
17519
+ const workspace = nodePath.resolve(input.workspace);
17520
+ for (const file of files) {
17521
+ if (signal?.aborted || Date.now() > deadline) break;
17522
+ const abs = nodePath.resolve(workspace, file);
17523
+ const rel = nodePath.relative(workspace, abs);
17524
+ if (!rel || rel.startsWith("..") || nodePath.isAbsolute(rel)) continue;
17525
+ const outlined = await outlineFile(abs, signal);
17526
+ outlines.push({
17527
+ file,
17528
+ outline: outlined.outline
17529
+ });
17530
+ }
17531
+ return outlines;
17532
+ }
17227
17533
  async function runLexical(input, mode, source, signal) {
17228
17534
  const isAst = mode === "ast";
17229
17535
  const resp = await searchCode({
@@ -17285,22 +17591,26 @@ async function runUnifiedCodeSearch(input, signal) {
17285
17591
  notice: joinNotice(r$1.notice, "semantic search errored; returned lexical results")
17286
17592
  };
17287
17593
  }
17288
- if (sem.status === "ready") return {
17289
- source: "semantic",
17290
- results: (sem.results ?? []).map((r$1) => ({
17594
+ if (sem.status === "ready") {
17595
+ const results = (sem.results ?? []).map((r$1) => ({
17291
17596
  file: r$1.file,
17292
17597
  line: r$1.line,
17293
17598
  snippet: r$1.snippet,
17294
17599
  ...r$1.endLine !== void 0 ? { endLine: r$1.endLine } : {},
17295
17600
  ...r$1.name !== void 0 ? { name: r$1.name } : {},
17296
17601
  ...r$1.score !== void 0 ? { score: r$1.score } : {}
17297
- })),
17298
- ...sem.notice ? { notice: sem.notice } : {}
17299
- };
17602
+ }));
17603
+ return {
17604
+ source: "semantic",
17605
+ results,
17606
+ outlines: await outlinesForSemanticResults(input, results, signal),
17607
+ ...sem.notice ? { notice: sem.notice } : {}
17608
+ };
17609
+ }
17300
17610
  const r = await runLexical(input, "lexical", "lexical-fallback", signal);
17301
17611
  return {
17302
17612
  ...r,
17303
- notice: joinNotice(r.notice, fallbackNoticeFor(sem.status))
17613
+ notice: joinNotice(r.notice, semanticFallbackNotice(sem))
17304
17614
  };
17305
17615
  }
17306
17616
 
@@ -33901,5 +34211,5 @@ function enumerateInjectedMcpToolNames(groupKeys, opts = {}) {
33901
34211
  }
33902
34212
 
33903
34213
  //#endregion
33904
- export { searchWeb as $, UPSTREAM_INACTIVITY_TIMEOUT_MS as $t, trustRepo as A, createResponses as At, TEST_DEFAULT_MODEL as B, CONDENSED_OPERATING_SEQUENCE as Bt, fileLastPromptStore as C, copilotHeaders as Cn, shimDefaultsToXhigh as Ct, repoRoot as D, assembleResponsesPayload as Dt, repoFingerprint as E, getTokenCount as Et, EXPLORE_DEFAULT_MODEL as F, provisionBrowserAssets as Ft, buildEnv as G, buildWorkspaceHeaderJson as Gt, resolveModeDefaults as H, shouldUseInsecureTls as Ht, EXPLORE_DEFAULT_THINKING as I, hasSupportedBrowserInstalled as It, toolbeltEnabled as J, DEFAULT_CLAUDE_MODEL_FALLBACKS as Jt, availableToolCommands as K, collapsePathKeys as Kt, IMPLEMENT_DEFAULT_MODEL as L, provisionAndIndexColbert as Lt, resolveSealedGate as M, MAX_RESPONSE_BODY_BYTES as Mt, BROWSE_DEFAULT_MODEL as N, readResponseBodyCapped as Nt, stopGateEnabledForRepo as O, resolveMcpToolTimeoutMs as Ot, DEFAULT_MODEL as P, parseJsonOrDiagnose as Pt, assetFor as Q, UPSTREAM_FETCH_TIMEOUT_MS as Qt, PLAN_DEFAULT_MODEL as R, extractTarGzMember as Rt, fileFindingsStore as S, copilotBaseUrl as Sn, workerToolsEnabled as St, isSubagentContext as T, state as Tn, createMessages as Tt, resolveWorkerRunOpts as U, ArtifactClient as Ut, appendPlanReminder as V, DEFINITION_OF_GREATNESS as Vt, runWorkerAgent as W, buildWorkspaceHeaderHelperCommand as Wt, vscodeRipgrepPath as X, DEFAULT_CODEX_MODEL_FALLBACKS as Xt, toolbeltSkipSet as Y, DEFAULT_CODEX_MODEL as Yt, TOOLBELT_TOOLS$1 as Z, DEFAULT_PORT as Zt, stopGateDisabled as _, getGitHubUser as _n, browserToolsEnabled as _t, buildPeerAwarenessSnippet as a, setupGitHubAgentToken as an, buildAnthropicErrorEvent as at, stopReviewEnabled as b, forwardError as bn, nativeSubagentModel as bt, personasFor as c, cacheCopilotVersion as cn, logStreamError as ct, buildStopHookCommand as d, filterBetaHeader as dn, handleMcpDelete as dt, generateRandomPort as en, ADVISOR_INTERNAL_TOOL_NAME as et, captureLaunchBaseline as f, isNullish as fn, handleMcpPost as ft, launchBaselineKey as g, getModels as gn, browserCompoundToolsEnabled as gt, injectStopHookIntoSettingsFile as h, sleep as hn, browseAgentEnabled as ht, buildAgentPrompt as i, setupCopilotToken as in, isAdvisorRequested as it, liveExec as j, createChatCompletions as jt, stopReviewStateDir as k, pickEndpoint as kt, buildArtifactOpenHookCommand as l, cacheModels as ln, readIteratorWithTimeout as lt, fileBlockBudget as m, resolveModel as mn, artifactToolsEnabled as mt, MCP_GROUPS as n, getPackageVersion as nn, buildAdvisorStream as nt, buildPeerAwarenessSummary as o, setupGitHubToken as on, buildOpenAIErrorEvent as ot, decideStopHook as p, resolveCodexModel as pn, agentToolsEnabled as pt, buildToolbeltAwareness as q, toolbeltPathOverride as qt, assertMcpToolSurfaceConsistent as r, withInstallLock as rn, injectAdvisorTool as rt, enumerateInjectedMcpToolNames as s, tryRefreshAndRetry as sn, isControllerClosedError as st, GROUP_META as t, pickClaudeDefault as tn, ADVISOR_TOOL_INSTRUCTIONS as tt, buildSessionBindHookCommand as u, cacheVSCodeVersion as un, relayAnthropicStream as ut, stopGateId as v, fetchWithTransientRetry as vn, fleetToolsEnabled as vt, fileReviewDebounce as w, githubHeaders as wn, countTokens as wt, fileBaselineStore as x, GITHUB_API_BASE_URL as xn, standInToolEnabled as xt, stopGatePlanMode as y, HTTPError as yn, geminiAvailable as yt, REVIEW_DEFAULT_MODEL as z, extractZipMember as zt };
33905
- //# sourceMappingURL=peer-mcp-personas-A6PytLD6.js.map
34214
+ export { searchWeb as $, UPSTREAM_FETCH_TIMEOUT_MS as $t, trustRepo as A, createResponses as At, TEST_DEFAULT_MODEL as B, warmTreeSitterPool as Bt, fileLastPromptStore as C, copilotBaseUrl as Cn, shimDefaultsToXhigh as Ct, repoRoot as D, assembleResponsesPayload as Dt, repoFingerprint as E, state as En, getTokenCount as Et, EXPLORE_DEFAULT_MODEL as F, provisionBrowserAssets as Ft, buildEnv as G, buildWorkspaceHeaderHelperCommand as Gt, resolveModeDefaults as H, DEFINITION_OF_GREATNESS as Ht, EXPLORE_DEFAULT_THINKING as I, hasSupportedBrowserInstalled as It, toolbeltEnabled as J, toolbeltPathOverride as Jt, availableToolCommands as K, buildWorkspaceHeaderJson as Kt, IMPLEMENT_DEFAULT_MODEL as L, provisionAndIndexColbert as Lt, resolveSealedGate as M, MAX_RESPONSE_BODY_BYTES as Mt, BROWSE_DEFAULT_MODEL as N, readResponseBodyCapped as Nt, stopGateEnabledForRepo as O, resolveMcpToolTimeoutMs as Ot, DEFAULT_MODEL as P, parseJsonOrDiagnose as Pt, assetFor as Q, DEFAULT_PORT as Qt, PLAN_DEFAULT_MODEL as R, extractTarGzMember as Rt, fileFindingsStore as S, GITHUB_API_BASE_URL as Sn, workerToolsEnabled as St, isSubagentContext as T, githubHeaders as Tn, createMessages as Tt, resolveWorkerRunOpts as U, shouldUseInsecureTls as Ut, appendPlanReminder as V, CONDENSED_OPERATING_SEQUENCE as Vt, runWorkerAgent as W, ArtifactClient as Wt, vscodeRipgrepPath as X, DEFAULT_CODEX_MODEL as Xt, toolbeltSkipSet as Y, DEFAULT_CLAUDE_MODEL_FALLBACKS as Yt, TOOLBELT_TOOLS$1 as Z, DEFAULT_CODEX_MODEL_FALLBACKS as Zt, stopGateDisabled as _, getModels as _n, browserToolsEnabled as _t, buildPeerAwarenessSnippet as a, setupCopilotToken as an, buildAnthropicErrorEvent as at, stopReviewEnabled as b, HTTPError as bn, nativeSubagentModel as bt, personasFor as c, tryRefreshAndRetry as cn, logStreamError as ct, buildStopHookCommand as d, cacheVSCodeVersion as dn, handleMcpDelete as dt, UPSTREAM_INACTIVITY_TIMEOUT_MS as en, ADVISOR_INTERNAL_TOOL_NAME as et, captureLaunchBaseline as f, filterBetaHeader as fn, handleMcpPost as ft, launchBaselineKey as g, sleep as gn, browserCompoundToolsEnabled as gt, injectStopHookIntoSettingsFile as h, resolveModel as hn, browseAgentEnabled as ht, buildAgentPrompt as i, withInstallLock as in, isAdvisorRequested as it, liveExec as j, createChatCompletions as jt, stopReviewStateDir as k, pickEndpoint as kt, buildArtifactOpenHookCommand as l, cacheCopilotVersion as ln, readIteratorWithTimeout as lt, fileBlockBudget as m, resolveCodexModel as mn, artifactToolsEnabled as mt, MCP_GROUPS as n, pickClaudeDefault as nn, buildAdvisorStream as nt, buildPeerAwarenessSummary as o, setupGitHubAgentToken as on, buildOpenAIErrorEvent as ot, decideStopHook as p, isNullish as pn, agentToolsEnabled as pt, buildToolbeltAwareness as q, collapsePathKeys as qt, assertMcpToolSurfaceConsistent as r, getPackageVersion as rn, injectAdvisorTool as rt, enumerateInjectedMcpToolNames as s, setupGitHubToken as sn, isControllerClosedError as st, GROUP_META as t, generateRandomPort as tn, ADVISOR_TOOL_INSTRUCTIONS as tt, buildSessionBindHookCommand as u, cacheModels as un, relayAnthropicStream as ut, stopGateId as v, getGitHubUser as vn, fleetToolsEnabled as vt, fileReviewDebounce as w, copilotHeaders as wn, countTokens as wt, fileBaselineStore as x, forwardError as xn, standInToolEnabled as xt, stopGatePlanMode as y, fetchWithTransientRetry as yn, geminiAvailable as yt, REVIEW_DEFAULT_MODEL as z, extractZipMember as zt };
34215
+ //# sourceMappingURL=peer-mcp-personas-BKwMCOsl.js.map