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.
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{engine-DyY5qdpL.js → engine-v9-pEKoD.js} +1 -1
- package/dist/lib/tree-sitter-pool/worker.js +30 -23
- package/dist/main.js +6 -3
- package/dist/main.js.map +1 -1
- package/dist/{peer-mcp-personas-A6PytLD6.js → peer-mcp-personas-BKwMCOsl.js} +859 -549
- package/dist/peer-mcp-personas-BKwMCOsl.js.map +1 -0
- package/package.json +2 -2
- package/dist/peer-mcp-personas-A6PytLD6.js.map +0 -1
|
@@ -13335,40 +13335,47 @@ function deriveDefinitionName(node) {
|
|
|
13335
13335
|
return null;
|
|
13336
13336
|
}
|
|
13337
13337
|
/**
|
|
13338
|
-
* Collect
|
|
13339
|
-
*
|
|
13340
|
-
*
|
|
13341
|
-
*
|
|
13342
|
-
*
|
|
13343
|
-
|
|
13344
|
-
|
|
13345
|
-
|
|
13346
|
-
|
|
13347
|
-
|
|
13348
|
-
|
|
13349
|
-
|
|
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
|
-
|
|
13359
|
-
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
13365
|
-
|
|
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
|
-
*
|
|
13516
|
-
*
|
|
13517
|
-
*
|
|
13518
|
-
*
|
|
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 >=
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
14261
|
-
*
|
|
14262
|
-
*
|
|
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}/${
|
|
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
|
|
15188
|
-
*
|
|
15189
|
-
*
|
|
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
|
|
15197
|
-
|
|
15198
|
-
|
|
15199
|
-
|
|
15200
|
-
|
|
15201
|
-
|
|
15202
|
-
|
|
15203
|
-
|
|
15204
|
-
|
|
15205
|
-
|
|
15206
|
-
return
|
|
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:
|
|
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/
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
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
|
-
*
|
|
15894
|
-
*
|
|
15895
|
-
*
|
|
15896
|
-
*
|
|
15897
|
-
*
|
|
15898
|
-
*
|
|
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
|
|
15901
|
-
const
|
|
15902
|
-
|
|
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
|
-
|
|
15901
|
+
await fs$2.mkdir(extractDir, { recursive: true });
|
|
15902
|
+
await fs$2.writeFile(archivePath, buf);
|
|
15905
15903
|
} catch {
|
|
15906
|
-
return
|
|
15904
|
+
return null;
|
|
15907
15905
|
}
|
|
15908
|
-
|
|
15909
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15920
|
+
resolve(false);
|
|
15921
|
+
return;
|
|
15917
15922
|
}
|
|
15918
|
-
const
|
|
15919
|
-
|
|
15920
|
-
|
|
15921
|
-
|
|
15922
|
-
|
|
15923
|
-
|
|
15924
|
-
|
|
15925
|
-
|
|
15926
|
-
|
|
15927
|
-
|
|
15928
|
-
|
|
15929
|
-
|
|
15930
|
-
|
|
15931
|
-
|
|
15932
|
-
|
|
15933
|
-
|
|
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
|
|
15942
|
+
return await fs$2.readFile(found);
|
|
15936
15943
|
} catch {
|
|
15937
|
-
return
|
|
15944
|
+
return null;
|
|
15938
15945
|
}
|
|
15939
15946
|
}
|
|
15940
|
-
|
|
15941
|
-
|
|
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 =
|
|
15951
|
+
entries = await fs$2.readdir(dir, { withFileTypes: true });
|
|
15948
15952
|
} catch {
|
|
15949
|
-
return
|
|
15953
|
+
return null;
|
|
15950
15954
|
}
|
|
15951
|
-
for (const e of entries)
|
|
15952
|
-
|
|
15953
|
-
|
|
15954
|
-
|
|
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
|
|
15960
|
+
return null;
|
|
15963
15961
|
}
|
|
15964
15962
|
/**
|
|
15965
|
-
*
|
|
15966
|
-
*
|
|
15967
|
-
*
|
|
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
|
|
15975
|
-
|
|
15976
|
-
let names;
|
|
15967
|
+
function extractTarGzMember(buf, wantBasename) {
|
|
15968
|
+
let tar;
|
|
15977
15969
|
try {
|
|
15978
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
16605
|
-
|
|
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
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
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
|
-
|
|
16617
|
-
|
|
16618
|
-
"
|
|
16619
|
-
|
|
16620
|
-
"
|
|
16621
|
-
"
|
|
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
|
-
|
|
16631
|
-
|
|
16632
|
-
|
|
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
|
-
|
|
16651
|
-
|
|
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
|
-
}
|
|
16654
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
16990
|
-
const
|
|
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
|
-
|
|
16995
|
-
|
|
16996
|
-
|
|
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
|
-
|
|
17024
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17046
|
-
|
|
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
|
-
|
|
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
|
|
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 (
|
|
17102
|
-
|
|
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 =
|
|
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")
|
|
17289
|
-
|
|
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
|
-
|
|
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,
|
|
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 $,
|
|
33905
|
-
//# sourceMappingURL=peer-mcp-personas-
|
|
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
|