@merittdev/horus 0.21.0 → 0.21.1

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.0" : "dev";
3060
+ HORUS_VERSION = true ? "0.21.1" : "dev";
3061
3061
  PINNED_SOURCE_VERSION = HORUS_VERSION;
3062
3062
  SOURCE_PIN_ENFORCED = HORUS_VERSION !== "dev";
3063
3063
  }
@@ -11591,24 +11591,50 @@ function decryptConnectorSecrets(root) {
11591
11591
  }
11592
11592
  return out2;
11593
11593
  }
11594
+ function hasHorusIgnoreEntry(text2) {
11595
+ return text2.split("\n").map((l2) => l2.trim()).some((l2) => l2 === ".horus" || l2 === ".horus/" || l2 === "/.horus" || l2 === "/.horus/");
11596
+ }
11597
+ function gitInfoExcludePath(root) {
11598
+ const gitPath = (0, import_node_path2.join)(root, ".git");
11599
+ if (!(0, import_node_fs2.existsSync)(gitPath)) return null;
11600
+ let gitDir;
11601
+ try {
11602
+ const st3 = (0, import_node_fs2.statSync)(gitPath);
11603
+ if (st3.isDirectory()) {
11604
+ gitDir = gitPath;
11605
+ } else {
11606
+ const m5 = /^gitdir:\s*(.+?)\s*$/m.exec((0, import_node_fs2.readFileSync)(gitPath, "utf8"));
11607
+ if (!m5) return null;
11608
+ const resolved = m5[1];
11609
+ gitDir = resolved.startsWith("/") ? resolved : (0, import_node_path2.join)(root, resolved);
11610
+ }
11611
+ } catch {
11612
+ return null;
11613
+ }
11614
+ return (0, import_node_path2.join)(gitDir, "info", "exclude");
11615
+ }
11594
11616
  function isHorusGitignored(root) {
11595
11617
  if (!(0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, ".git"))) return true;
11618
+ const excludePath = gitInfoExcludePath(root);
11619
+ if (excludePath !== null && (0, import_node_fs2.existsSync)(excludePath) && hasHorusIgnoreEntry((0, import_node_fs2.readFileSync)(excludePath, "utf8"))) {
11620
+ return true;
11621
+ }
11596
11622
  const gitignorePath = (0, import_node_path2.join)(root, ".gitignore");
11597
- if (!(0, import_node_fs2.existsSync)(gitignorePath)) return false;
11598
- return (0, import_node_fs2.readFileSync)(gitignorePath, "utf8").split("\n").map((l2) => l2.trim()).some((l2) => l2 === ".horus" || l2 === ".horus/" || l2 === "/.horus" || l2 === "/.horus/");
11623
+ return (0, import_node_fs2.existsSync)(gitignorePath) && hasHorusIgnoreEntry((0, import_node_fs2.readFileSync)(gitignorePath, "utf8"));
11599
11624
  }
11600
11625
  function ensureProjectGitignore(root) {
11601
- if (!(0, import_node_fs2.existsSync)((0, import_node_path2.join)(root, ".git"))) return;
11602
- const gitignorePath = (0, import_node_path2.join)(root, ".gitignore");
11626
+ const excludePath = gitInfoExcludePath(root);
11627
+ if (excludePath === null) return;
11603
11628
  const entry2 = ".horus/";
11604
- if (!(0, import_node_fs2.existsSync)(gitignorePath)) {
11605
- (0, import_node_fs2.writeFileSync)(gitignorePath, entry2 + "\n");
11606
- return;
11629
+ try {
11630
+ const infoDir = (0, import_node_path2.dirname)(excludePath);
11631
+ if (!(0, import_node_fs2.existsSync)(infoDir)) (0, import_node_fs2.mkdirSync)(infoDir, { recursive: true });
11632
+ const existing = (0, import_node_fs2.existsSync)(excludePath) ? (0, import_node_fs2.readFileSync)(excludePath, "utf8") : "";
11633
+ if (hasHorusIgnoreEntry(existing)) return;
11634
+ const prefix = existing.trim() === "" ? "" : existing.trimEnd() + "\n";
11635
+ (0, import_node_fs2.writeFileSync)(excludePath, prefix + entry2 + "\n");
11636
+ } catch {
11607
11637
  }
11608
- const existing = (0, import_node_fs2.readFileSync)(gitignorePath, "utf8");
11609
- const alreadyIgnored = existing.split("\n").map((l2) => l2.trim()).some((l2) => l2 === ".horus" || l2 === ".horus/" || l2 === "/.horus" || l2 === "/.horus/");
11610
- if (alreadyIgnored) return;
11611
- (0, import_node_fs2.writeFileSync)(gitignorePath, existing.trimEnd() + "\n" + entry2 + "\n");
11612
11638
  }
11613
11639
  function patchLocalConnector(configPath, connectorType, patch, envName) {
11614
11640
  const file = readLocalConfig(configPath);
@@ -12610,8 +12636,8 @@ var init_path_classify = __esm({
12610
12636
  "../../packages/core/src/path-classify.ts"() {
12611
12637
  "use strict";
12612
12638
  init_cjs_shims();
12613
- TEST_DIRS = /* @__PURE__ */ new Set(["test", "tests", "__tests__", "spec", "specs", "testdata", "fixtures", "__fixtures__", "__mocks__", "e2e"]);
12614
- DOCS_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "docs_src", "website"]);
12639
+ TEST_DIRS = /* @__PURE__ */ new Set(["test", "tests", "__tests__", "spec", "specs", "testdata", "fixtures", "__fixtures__", "__testfixtures__", "testfixtures", "__mocks__", "e2e"]);
12640
+ DOCS_DIRS = /* @__PURE__ */ new Set(["docs", "doc", "docs_src", "website", "www", "site"]);
12615
12641
  EXAMPLE_DIRS = /* @__PURE__ */ new Set([
12616
12642
  "examples",
12617
12643
  "example",
@@ -12642,8 +12668,17 @@ var init_path_classify = __esm({
12642
12668
  function isGenericQueueName(name2) {
12643
12669
  return GENERIC_QUEUE_NAMES.has(name2.toLowerCase());
12644
12670
  }
12671
+ function isConstantShapedQueueName(name2) {
12672
+ return /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(name2);
12673
+ }
12674
+ function needsWorkerVouch(name2) {
12675
+ return isGenericQueueName(name2) || isConstantShapedQueueName(name2);
12676
+ }
12645
12677
  function isPlausibleQueueName(name2) {
12646
- return name2.length > 0 && name2.length <= 100 && !/[\s<>{}()`"';$]/.test(name2);
12678
+ if (name2.length === 0 || name2.length > 100) return false;
12679
+ if (/[\s<>{}()`"';$]/.test(name2)) return false;
12680
+ if ((name2.match(/\./g)?.length ?? 0) >= 2) return false;
12681
+ return true;
12647
12682
  }
12648
12683
  function isFixtureQueueEdge(edge) {
12649
12684
  const files = [edge.producerFile, edge.workerFile].filter(
@@ -12652,12 +12687,20 @@ function isFixtureQueueEdge(edge) {
12652
12687
  if (files.length > 0) return files.every((f5) => !isProductPath(f5));
12653
12688
  return isGenericQueueName(edge.queueName);
12654
12689
  }
12690
+ function isEnumConstantEdge(edge) {
12691
+ const pf = edge.producerFile;
12692
+ const wf = edge.workerFile;
12693
+ if (pf == null || pf === "" || pf !== wf) return false;
12694
+ const base2 = (pf.split("/").pop() ?? pf).toLowerCase();
12695
+ return /(enum|constant|attribute|telemetry)/.test(base2);
12696
+ }
12655
12697
  function shouldKeepQueueEdge(edge) {
12656
- return isPlausibleQueueName(edge.queueName) && !isFixtureQueueEdge(edge);
12698
+ return isPlausibleQueueName(edge.queueName) && !isFixtureQueueEdge(edge) && !isEnumConstantEdge(edge);
12657
12699
  }
12658
12700
  function isRealWorkerEvidence(edge) {
12659
12701
  const sym = edge.workerSymbol;
12660
12702
  if (sym == null || sym === "") return false;
12703
+ if (isConstantShapedQueueName(sym)) return false;
12661
12704
  const file = edge.workerFile;
12662
12705
  if (file != null && file !== "") {
12663
12706
  if (!isProductPath(file)) return false;
@@ -12675,7 +12718,7 @@ function filterQueueEdges(edges) {
12675
12718
  if (isRealWorkerEvidence(e)) hasRealWorker.add(e.queueName);
12676
12719
  }
12677
12720
  return kept.filter(
12678
- (e) => !isGenericQueueName(e.queueName) || hasRealWorker.has(e.queueName)
12721
+ (e) => !needsWorkerVouch(e.queueName) || hasRealWorker.has(e.queueName)
12679
12722
  );
12680
12723
  }
12681
12724
  var GENERIC_QUEUE_NAMES;
@@ -12745,6 +12788,7 @@ __export(src_exports, {
12745
12788
  getHeadSha: () => getHeadSha,
12746
12789
  getMasterKeyForRead: () => getMasterKeyForRead,
12747
12790
  horusConfigSchema: () => horusConfigSchema,
12791
+ isConstantShapedQueueName: () => isConstantShapedQueueName,
12748
12792
  isEncryptedSecret: () => isEncryptedSecret,
12749
12793
  isFixtureQueueEdge: () => isFixtureQueueEdge,
12750
12794
  isGenericQueueName: () => isGenericQueueName,
@@ -97300,6 +97344,7 @@ function correlate(evidence2, opts) {
97300
97344
 
97301
97345
  // ../../packages/engine/src/seeds.ts
97302
97346
  init_cjs_shims();
97347
+ init_src();
97303
97348
  var PREFER = /resolver|controller|\bservice\b|route|router|handler|gateway|processor|worker|consumer|repository|usecase|use-case|endpoint/i;
97304
97349
  var DEMOTE = /\butil|helper|\bscript|backfill|legacy|migration|seeder|fixture|\bmock\b|\btest\b|\bspec\b/i;
97305
97350
  var PRESENTATION_NAME = /^(build|format|render|compose|serialize)[A-Z].*(Message|Markup|Html|Text|Label|Summary|Notification|String)$/;
@@ -97483,9 +97528,8 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode, qualifier,
97483
97528
  return score;
97484
97529
  }
97485
97530
  function isDeprioritizedSeedPath(filePath) {
97486
- return /(^|\/)(tests?|__tests__|spec)\//i.test(filePath) || /(\.|_)(test|spec)\.[jt]sx?$/i.test(filePath) || /(^|\/)test_[^/]*\.py$/i.test(filePath) || /_test\.py$/i.test(filePath) || /(^|\/)(examples?|samples?|demos?|fixtures?|sandbox|playground|docs|docs_src|tutorials?)(\/|$)/i.test(
97487
- filePath
97488
- );
97531
+ const kind = classifyPath(filePath);
97532
+ return kind === "test" || kind === "docs" || kind === "example" || kind === "vendored";
97489
97533
  }
97490
97534
  function isDeprioritizedSeed(s5) {
97491
97535
  return isDeprioritizedSeedPath(s5.filePath) || isCodegenPath(s5.filePath) || isTypeDeclarationSymbol(s5);
@@ -98706,7 +98750,8 @@ async function investigate(input, deps) {
98706
98750
  }
98707
98751
  const degradedNoSource = !code;
98708
98752
  const changeWindowDays = deps.changeWindowDays ?? DEFAULT_CHANGE_WINDOW_DAYS;
98709
- const changeRangeLabel = input.since !== void 0 ? `${input.since}..HEAD` : `the last ${changeWindowDays} days`;
98753
+ const autoAnchorIso = input.since === void 0 && deps.repoPath !== void 0 ? await latestCommitDate(deps.repoPath) : void 0;
98754
+ const changeRangeLabel = input.since !== void 0 ? `${input.since}..HEAD` : autoAnchorIso !== void 0 ? `the ${changeWindowDays} days before the last commit (${autoAnchorIso.slice(0, 10)})` : `the last ${changeWindowDays} days`;
98710
98755
  let recentChanges;
98711
98756
  if (deps.repoPath) {
98712
98757
  try {
@@ -101250,6 +101295,10 @@ function isTestOrExamplePath(p5) {
101250
101295
  const kind = classifyPath(p5);
101251
101296
  return kind === "test" || kind === "docs" || kind === "example";
101252
101297
  }
101298
+ var DATA_FILE_RE2 = /(^|\/)(datasets?|seed(?:s|-?data)?|dictionar(?:y|ies)|word-?lists?|locales?|i18n|corpus)(\/|$)|(^|\/)(first-?names?|last-?names?|sur-?names?|full-?names?|countries|cities|nouns|adjectives|verbs|words|colou?rs|jobs?-?titles?|emojis?)\.[jt]sx?$/i;
101299
+ function isDataFilePath(p5) {
101300
+ return DATA_FILE_RE2.test(p5);
101301
+ }
101253
101302
  async function discoverArchitecture(deps) {
101254
101303
  const nodeStats = await (async () => {
101255
101304
  try {
@@ -101358,7 +101407,13 @@ async function discoverArchitecture(deps) {
101358
101407
  const matches = await deps.code.filesContaining?.(markers, 500) ?? {};
101359
101408
  return markers.map((marker) => ({
101360
101409
  name: marker,
101361
- files: (matches[marker] ?? []).filter((p5) => p5 !== "" && !isTestOrExamplePath(p5)).length
101410
+ // Count only genuine product source. `classifyPath === 'product'` additionally drops
101411
+ // config/lockfiles (a marker in `pnpm-lock.yaml`/`package.json` is a transitive dep,
101412
+ // not an integration) and vendored trees over the old test/example-only filter;
101413
+ // data files are excluded because their string contents collide with markers.
101414
+ files: (matches[marker] ?? []).filter(
101415
+ (p5) => p5 !== "" && classifyPath(p5) === "product" && !isDataFilePath(p5)
101416
+ ).length
101362
101417
  })).filter((r) => r.files > 0).sort((a2, b5) => b5.files - a2.files);
101363
101418
  } catch {
101364
101419
  return [];
@@ -124676,6 +124731,15 @@ async function runIndex(opts) {
124676
124731
  console.error(import_picocolors41.default.red(` source analysis failed: ${err2.message}`));
124677
124732
  return 1;
124678
124733
  }
124734
+ const incomplete = isAnalyzed(root) ? indexNeedsReanalyze(root) : "no index was produced";
124735
+ if (incomplete) {
124736
+ console.error(
124737
+ import_picocolors41.default.red(
124738
+ ` 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).`
124739
+ )
124740
+ );
124741
+ return 1;
124742
+ }
124679
124743
  } else {
124680
124744
  console.log(import_picocolors41.default.dim(" already analyzed"));
124681
124745
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {