@merittdev/horus 0.21.2 → 0.21.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
package/dist/index.cjs CHANGED
@@ -3057,7 +3057,7 @@ var init_version = __esm({
3057
3057
  "../../packages/core/src/version.ts"() {
3058
3058
  "use strict";
3059
3059
  init_cjs_shims();
3060
- HORUS_VERSION = true ? "0.21.2" : "dev";
3060
+ HORUS_VERSION = true ? "0.21.4" : "dev";
3061
3061
  PINNED_SOURCE_VERSION = HORUS_VERSION;
3062
3062
  SOURCE_PIN_ENFORCED = HORUS_VERSION !== "dev";
3063
3063
  }
@@ -72160,12 +72160,17 @@ function readSourceMeta(root) {
72160
72160
  return {
72161
72161
  storeBackend: typeof j3["store_backend"] === "string" ? j3["store_backend"].toLowerCase() : void 0,
72162
72162
  embeddingsComplete: typeof j3["embeddings_complete"] === "boolean" ? j3["embeddings_complete"] : void 0,
72163
+ structuralComplete: typeof j3["structural_complete"] === "boolean" ? j3["structural_complete"] : void 0,
72163
72164
  stats: stats ? { symbols: stats["symbols"], embeddings: stats["embeddings"] } : void 0
72164
72165
  };
72165
72166
  } catch {
72166
72167
  return null;
72167
72168
  }
72168
72169
  }
72170
+ function indexEmbeddingsPending(root) {
72171
+ const meta = readSourceMeta(root);
72172
+ return meta?.structuralComplete === true && meta?.embeddingsComplete === false;
72173
+ }
72169
72174
  function metaHasEmbeddings(meta) {
72170
72175
  if (meta.embeddingsComplete === false) return false;
72171
72176
  const embeddings = meta.stats?.embeddings ?? 0;
@@ -72183,25 +72188,88 @@ function indexNeedsReanalyze(root) {
72183
72188
  return `store backend changed (${meta.storeBackend} -> ${CURRENT_STORE_BACKEND})`;
72184
72189
  }
72185
72190
  if (!metaHasEmbeddings(meta)) {
72191
+ if (meta.structuralComplete === true) return null;
72186
72192
  return "index has no semantic embeddings (symbols present but 0 vectors)";
72187
72193
  }
72188
72194
  return null;
72189
72195
  }
72196
+ var BASE_ANALYZE_TIMEOUT_MS = 9e5;
72197
+ var MAX_ANALYZE_TIMEOUT_MS = 36e5;
72198
+ var ANALYZE_BASELINE_FILES = 5e3;
72199
+ var ANALYZE_TIMEOUT_PER_FILE_MS = 15;
72200
+ var ANALYZE_COUNT_SKIP_DIRS = /* @__PURE__ */ new Set([
72201
+ "node_modules",
72202
+ ".git",
72203
+ ".horus",
72204
+ "dist",
72205
+ "build",
72206
+ "out",
72207
+ "target",
72208
+ "vendor",
72209
+ ".venv",
72210
+ "venv",
72211
+ "__pycache__",
72212
+ ".next",
72213
+ ".turbo",
72214
+ "coverage",
72215
+ ".cache"
72216
+ ]);
72217
+ function countRepoFiles(root, limit = 2e5) {
72218
+ let count = 0;
72219
+ const stack = [root];
72220
+ while (stack.length > 0) {
72221
+ const dir = stack.pop();
72222
+ let entries;
72223
+ try {
72224
+ entries = (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true });
72225
+ } catch {
72226
+ continue;
72227
+ }
72228
+ for (const e of entries) {
72229
+ if (e.isDirectory()) {
72230
+ if (ANALYZE_COUNT_SKIP_DIRS.has(e.name) || e.name.startsWith(".")) continue;
72231
+ stack.push((0, import_node_path4.join)(dir, e.name));
72232
+ } else if (e.isFile()) {
72233
+ count++;
72234
+ if (count >= limit) return count;
72235
+ }
72236
+ }
72237
+ }
72238
+ return count;
72239
+ }
72240
+ function resolveAnalyzeTimeoutMs(root) {
72241
+ const override = process.env["HORUS_ANALYZE_TIMEOUT_MS"];
72242
+ if (override !== void 0 && override.trim() !== "") {
72243
+ const n4 = Number(override);
72244
+ if (Number.isFinite(n4) && n4 > 0) return Math.floor(n4);
72245
+ }
72246
+ let files = 0;
72247
+ try {
72248
+ files = countRepoFiles(root);
72249
+ } catch {
72250
+ return BASE_ANALYZE_TIMEOUT_MS;
72251
+ }
72252
+ if (files <= ANALYZE_BASELINE_FILES) return BASE_ANALYZE_TIMEOUT_MS;
72253
+ const scaled = BASE_ANALYZE_TIMEOUT_MS + (files - ANALYZE_BASELINE_FILES) * ANALYZE_TIMEOUT_PER_FILE_MS;
72254
+ return Math.min(scaled, MAX_ANALYZE_TIMEOUT_MS);
72255
+ }
72190
72256
  async function analyzeRepo(root) {
72191
72257
  const bin = await resolveSourceBin();
72192
72258
  if (!bin) throw new Error("horus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash");
72259
+ const timeoutMs = resolveAnalyzeTimeoutMs(root);
72193
72260
  try {
72194
- await exec(bin, ["analyze", "."], {
72261
+ await exec(bin, ["analyze", ".", "--defer-embeddings"], {
72195
72262
  cwd: root,
72196
- timeout: 9e5,
72263
+ timeout: timeoutMs,
72197
72264
  maxBuffer: 64 * 1024 * 1024
72198
72265
  });
72199
72266
  } catch (err2) {
72200
72267
  const e = err2;
72201
72268
  const tail = redactSecrets((e.stderr ?? "").trim()).slice(-800);
72202
72269
  if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
72270
+ const secs = Math.round(timeoutMs / 1e3);
72203
72271
  throw new Error(
72204
- `horus-source analyze timed out after 900s (large repo / slow embeddings phase)` + (tail ? ` \u2014 last output: ${tail}` : "")
72272
+ `horus-source analyze timed out after ${secs}s while building the structural index (large repo). Raise the cap with HORUS_ANALYZE_TIMEOUT_MS.` + (tail ? ` \u2014 last output: ${tail}` : "")
72205
72273
  );
72206
72274
  }
72207
72275
  throw new Error(tail ? `horus-source analyze failed: ${tail}` : redactSecrets(e.message ?? "horus-source analyze failed"));
@@ -72396,7 +72464,7 @@ var SourceCodeProvider = class {
72396
72464
  kind = "code";
72397
72465
  // --- helpers -------------------------------------------------------------
72398
72466
  nodeToSymbol(n4) {
72399
- return {
72467
+ const sym = {
72400
72468
  id: n4.id,
72401
72469
  name: n4.name,
72402
72470
  filePath: n4.filePath,
@@ -72406,6 +72474,8 @@ var SourceCodeProvider = class {
72406
72474
  language: n4.language,
72407
72475
  className: n4.className
72408
72476
  };
72477
+ if (n4.aliasOf) sym.aliasOf = n4.aliasOf;
72478
+ return sym;
72409
72479
  }
72410
72480
  /** Map a serialized graph node to a `Symbol`, coercing the host's `0` line sentinel to undefined. */
72411
72481
  serializedToSymbol(n4) {
@@ -72419,6 +72489,7 @@ var SourceCodeProvider = class {
72419
72489
  if (n4.className) sym.className = n4.className;
72420
72490
  if (n4.signature) sym.signature = n4.signature;
72421
72491
  if (n4.language) sym.language = n4.language;
72492
+ if (n4.aliasOf) sym.aliasOf = n4.aliasOf;
72422
72493
  return sym;
72423
72494
  }
72424
72495
  /**
@@ -97554,6 +97625,16 @@ function parseSymbolQuery(query) {
97554
97625
  if (container.length < 2 || m5[2].length < 2) return null;
97555
97626
  return { symbol: m5[2], container, isPath: false };
97556
97627
  }
97628
+ function redirectExportAlias(candidates) {
97629
+ const head = candidates[0];
97630
+ if (head === void 0 || head.aliasOf === void 0 || head.aliasOf === "") return candidates;
97631
+ const implName = head.aliasOf.toLowerCase();
97632
+ const impl = candidates.find(
97633
+ (s5) => s5.id !== head.id && s5.name.toLowerCase() === implName && s5.filePath === head.filePath
97634
+ ) ?? candidates.find((s5) => s5.id !== head.id && s5.name.toLowerCase() === implName);
97635
+ if (impl === void 0) return candidates;
97636
+ return [impl, ...candidates.filter((s5) => s5.id !== impl.id)];
97637
+ }
97557
97638
  function resolveSymbolQuery(query, symbols, opts) {
97558
97639
  const includeTests = opts?.includeTests === true;
97559
97640
  const qualifier = parseSymbolQuery(query);
@@ -97577,7 +97658,10 @@ function resolveSymbolQuery(query, symbols, opts) {
97577
97658
  const ambiguous = kind !== "fuzzy" && kind !== "none" && first !== void 0 && second !== void 0 && first.score === second.score && first.symbol.filePath !== second.symbol.filePath;
97578
97659
  return {
97579
97660
  kind,
97580
- candidates: [...scoredTier.map((s5) => s5.symbol), ...rest],
97661
+ // B2: redirect a public export-alias hit (`Component` → `BaseComponent`) to the
97662
+ // implementation so explain/blast-radius/search land on real product code, not the
97663
+ // empty alias stub. No-op unless the head candidate carries `aliasOf`.
97664
+ candidates: redirectExportAlias([...scoredTier.map((s5) => s5.symbol), ...rest]),
97581
97665
  ambiguous,
97582
97666
  qualifier
97583
97667
  };
@@ -97591,7 +97675,16 @@ async function resolveSeedSymbol(code, query, opts) {
97591
97675
  const seen = new Set(pool.map((s5) => s5.id));
97592
97676
  for (const s5 of extra) if (!seen.has(s5.id)) pool.push(s5);
97593
97677
  }
97594
- return resolveSymbolQuery(query, pool, { includeTests: opts?.includeTests ?? false });
97678
+ const includeTests = opts?.includeTests ?? false;
97679
+ let resolution = resolveSymbolQuery(query, pool, { includeTests });
97680
+ const head = resolution.candidates[0];
97681
+ if (head?.aliasOf !== void 0 && head.aliasOf !== "" && !pool.some((s5) => s5.id !== head.id && s5.name.toLowerCase() === head.aliasOf.toLowerCase())) {
97682
+ const implHits = await code.searchSymbols(head.aliasOf, Math.max(limit, 20));
97683
+ const seen = new Set(pool.map((s5) => s5.id));
97684
+ for (const s5 of implHits) if (!seen.has(s5.id)) pool.push(s5);
97685
+ resolution = resolveSymbolQuery(query, pool, { includeTests });
97686
+ }
97687
+ return resolution;
97595
97688
  }
97596
97689
  function rankSeeds(seeds, hintTokens, changedFiles, hintHasCode, qualifier, preferNamed) {
97597
97690
  const scored = seeds.map((symbol, i3) => ({
@@ -124755,7 +124848,7 @@ async function runIndex(opts) {
124755
124848
  if (incomplete) {
124756
124849
  console.error(
124757
124850
  import_picocolors41.default.red(
124758
- ` source analysis did not finish (${incomplete}) \u2014 the index has symbols but no embeddings. Re-run \`horus init\` to resume (the embedding cache is warm).`
124851
+ ` source analysis did not finish (${incomplete}) \u2014 the structural index is missing or broken. Re-run \`horus init\` to rebuild it.`
124759
124852
  )
124760
124853
  );
124761
124854
  return 1;
@@ -124810,6 +124903,9 @@ async function runIndex(opts) {
124810
124903
  );
124811
124904
  return 1;
124812
124905
  }
124906
+ if (indexEmbeddingsPending(root)) {
124907
+ console.log(import_picocolors41.default.dim(" index ready (semantic search warming up)"));
124908
+ }
124813
124909
  try {
124814
124910
  await stitchQueueMap(hostUrl, label);
124815
124911
  } catch (err2) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.21.2",
3
+ "version": "0.21.4",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {