@merittdev/horus 0.8.12 → 0.8.13

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.
Files changed (2) hide show
  1. package/dist/index.cjs +121 -41
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -43846,7 +43846,7 @@ var require_pg_pool = __commonJS({
43846
43846
  function throwOnDoubleRelease() {
43847
43847
  throw new Error("Release called on client which has already been released to the pool.");
43848
43848
  }
43849
- function promisify5(Promise2, callback) {
43849
+ function promisify6(Promise2, callback) {
43850
43850
  if (callback) {
43851
43851
  return { callback, result: void 0 };
43852
43852
  }
@@ -43984,7 +43984,7 @@ var require_pg_pool = __commonJS({
43984
43984
  const err2 = new Error("Cannot use a pool after calling end on the pool");
43985
43985
  return cb ? cb(err2) : this.Promise.reject(err2);
43986
43986
  }
43987
- const response = promisify5(this.Promise, cb);
43987
+ const response = promisify6(this.Promise, cb);
43988
43988
  const result = response.result;
43989
43989
  if (this._isFull() || this._idle.length) {
43990
43990
  if (this._idle.length) {
@@ -44169,7 +44169,7 @@ var require_pg_pool = __commonJS({
44169
44169
  }
44170
44170
  query(text2, values2, cb) {
44171
44171
  if (typeof text2 === "function") {
44172
- const response2 = promisify5(this.Promise, text2);
44172
+ const response2 = promisify6(this.Promise, text2);
44173
44173
  setImmediate(function() {
44174
44174
  return response2.callback(new Error("Passing a function as the first parameter to pool.query is not supported"));
44175
44175
  });
@@ -44179,7 +44179,7 @@ var require_pg_pool = __commonJS({
44179
44179
  cb = values2;
44180
44180
  values2 = void 0;
44181
44181
  }
44182
- const response = promisify5(this.Promise, cb);
44182
+ const response = promisify6(this.Promise, cb);
44183
44183
  cb = response.callback;
44184
44184
  this.connect((err2, client) => {
44185
44185
  if (err2) {
@@ -44224,7 +44224,7 @@ var require_pg_pool = __commonJS({
44224
44224
  return cb ? cb(err2) : this.Promise.reject(err2);
44225
44225
  }
44226
44226
  this.ending = true;
44227
- const promised = promisify5(this.Promise, cb);
44227
+ const promised = promisify6(this.Promise, cb);
44228
44228
  this._endCallback = promised.callback;
44229
44229
  this._pulseQueue();
44230
44230
  return promised.result;
@@ -65880,8 +65880,8 @@ init_cjs_shims();
65880
65880
 
65881
65881
  // ../../packages/core/src/version.ts
65882
65882
  init_cjs_shims();
65883
- var HORUS_VERSION = true ? "0.8.12" : "dev";
65884
- var PINNED_SOURCE_VERSION = "1.5.1";
65883
+ var HORUS_VERSION = true ? "0.8.13" : "dev";
65884
+ var PINNED_SOURCE_VERSION = "1.5.2";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
65887
65887
  init_cjs_shims();
@@ -70987,7 +70987,7 @@ async function getSourceVersion() {
70987
70987
  var SourceVersionMismatchError = class extends Error {
70988
70988
  constructor(installed, pinned) {
70989
70989
  super(
70990
- `horus-source ${installed} is installed but Horus is pinned to ${pinned}. A drifted backend builds a graph this CLI cannot map and can corrupt the index (e.g. duplicate-primary-key failures during "Running initial index"). Install the pinned version: pip install 'horus-source==${pinned}'`
70990
+ `horus-source ${installed} is installed but Horus is pinned to ${pinned}. A drifted backend builds a graph this CLI cannot map and can corrupt the index (e.g. duplicate-primary-key failures during "Running initial index"). Run \`horus update\` to sync the backend, or re-run the installer: curl -fsSL https://horus.sh/install.sh | bash`
70991
70991
  );
70992
70992
  this.installed = installed;
70993
70993
  this.pinned = pinned;
@@ -71007,7 +71007,7 @@ function isAnalyzed(root) {
71007
71007
  }
71008
71008
  async function analyzeRepo(root) {
71009
71009
  const bin = await resolveSourceBin();
71010
- if (!bin) throw new Error("horus-source not found on PATH. Install it: pip install horus-source");
71010
+ if (!bin) throw new Error("horus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash");
71011
71011
  await exec(bin, ["analyze", "."], {
71012
71012
  cwd: root,
71013
71013
  timeout: 9e5,
@@ -71083,7 +71083,7 @@ function startHost(root, port) {
71083
71083
  child.on("error", (err2) => {
71084
71084
  if (err2.code === "ENOENT") {
71085
71085
  process.stderr.write(
71086
- "\nhorus-source not found on PATH. Install it: pip install horus-source\n"
71086
+ "\nhorus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash\n"
71087
71087
  );
71088
71088
  }
71089
71089
  });
@@ -71159,7 +71159,14 @@ var SourceCodeProvider = class {
71159
71159
  };
71160
71160
  }
71161
71161
  async rows(query) {
71162
- return (await this.client.cypher(query)).rows;
71162
+ try {
71163
+ return (await this.client.cypher(query)).rows;
71164
+ } catch (err2) {
71165
+ if (err2 instanceof SourceHttpError && err2.status >= 400 && err2.status < 500) {
71166
+ return [];
71167
+ }
71168
+ throw err2;
71169
+ }
71163
71170
  }
71164
71171
  cypherRowToSymbol(row, idIdx, nameIdx, fileIdx, lineIdx, classNameIdx) {
71165
71172
  const startLine = lineIdx != null && row[lineIdx] != null ? Number(row[lineIdx]) : void 0;
@@ -74284,6 +74291,14 @@ function parseGitLog(stdout) {
74284
74291
  }
74285
74292
  return commits;
74286
74293
  }
74294
+ async function resolvesToCommit(repoPath, rev) {
74295
+ try {
74296
+ await exec2("git", ["-C", repoPath, "rev-parse", "--verify", "--quiet", `${rev}^{commit}`]);
74297
+ return true;
74298
+ } catch {
74299
+ return false;
74300
+ }
74301
+ }
74287
74302
  async function gitLog(repoPath, opts = {}) {
74288
74303
  const args2 = [
74289
74304
  "-C",
@@ -74293,11 +74308,16 @@ async function gitLog(repoPath, opts = {}) {
74293
74308
  "--pretty=format:%H%h%an%aI%s",
74294
74309
  "--name-only"
74295
74310
  ];
74296
- if (opts.since !== void 0) {
74297
- args2.push("--since=" + opts.since);
74298
- }
74299
- if (opts.until !== void 0) {
74300
- args2.push("--until=" + opts.until);
74311
+ if (opts.since !== void 0 && await resolvesToCommit(repoPath, opts.since)) {
74312
+ const end = opts.until !== void 0 && await resolvesToCommit(repoPath, opts.until) ? opts.until : "HEAD";
74313
+ args2.push(`${opts.since}..${end}`);
74314
+ } else {
74315
+ if (opts.since !== void 0) {
74316
+ args2.push("--since=" + opts.since);
74317
+ }
74318
+ if (opts.until !== void 0) {
74319
+ args2.push("--until=" + opts.until);
74320
+ }
74301
74321
  }
74302
74322
  args2.push("--max-count=" + (opts.maxCount ?? 200));
74303
74323
  const { stdout } = await exec2("git", args2, { maxBuffer: 10 * 1024 * 1024 });
@@ -93869,7 +93889,7 @@ async function investigate(input, deps) {
93869
93889
  const headlineLinked = headlineCause !== void 0 && isLinkedToSeed(headlineCause.sourceEvidenceIds);
93870
93890
  if (headlineCause && !headlineLinked) confidence = Math.min(confidence, 0.6);
93871
93891
  const banner2 = degradedNoSource ? "Runtime-only (source intelligence unavailable). " : "";
93872
- const scope = top ? `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
93892
+ const scope = top ? seedIsLowConfidence ? "could not be confidently localized from source alone" : `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
93873
93893
  const seedDisclaimer = seedIsLowConfidence && top ? `\u26A0 No symbol closely matched "${hint}" \u2014 "${top.name}" is a low-confidence closest match (semantic). Refine with an exact symbol or error code to target precisely. ` : "";
93874
93894
  let summary = headlineCause ? headlineLinked ? `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. Top suspected cause: ${headlineCause.title}.` : `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. No cause is structurally linked to the seed; the strongest co-occurring signal (a lead to verify, not a diagnosis) is: ${headlineCause.title}.` : `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. No dominant suspected cause emerged from the available ${degradedNoSource ? "runtime" : "structural"} evidence.`;
93875
93895
  const hasIncidentSignal = directLogEvIds.length > 0 || latencyMetricEvIds.length > 0 || queueMetricEvIds.length > 0 || dataAnomalyEvIds.length > 0 || queueFailureSignals.length > 0 || findings2.some((f5) => f5.kind === "anomaly") || headlineCause !== void 0;
@@ -94389,7 +94409,19 @@ var EXTERNAL_MARKERS = [
94389
94409
  "elasticsearch",
94390
94410
  "prometheus",
94391
94411
  "mongo",
94392
- "graphql"
94412
+ "graphql",
94413
+ // Python ecosystem (HOR-356): the markers above are Node-centric, so Python backends'
94414
+ // DB/queue/web stacks went undetected (e.g. redash's Postgres + Celery). 'mongo'/'redis'
94415
+ // already cover pymongo/redis-py by substring.
94416
+ "sqlalchemy",
94417
+ "sqlmodel",
94418
+ "psycopg",
94419
+ "celery",
94420
+ "fastapi",
94421
+ "flask",
94422
+ "django",
94423
+ "kafka",
94424
+ "boto3"
94393
94425
  ];
94394
94426
  async function discoverArchitecture(deps) {
94395
94427
  const nodeStats = await (async () => {
@@ -97310,7 +97342,7 @@ function parseHostPort(hostUrl) {
97310
97342
  function ensureHostReasonHint(reason) {
97311
97343
  switch (reason) {
97312
97344
  case "source-unavailable":
97313
- return "horus-source is not installed \u2014 install it: pip install horus-source";
97345
+ return "horus-source is not installed \u2014 install it: curl -fsSL https://horus.sh/install.sh | bash";
97314
97346
  case "version-mismatch":
97315
97347
  return "the installed horus-source backend does not match the version Horus is pinned to \u2014 reinstall the pinned version (run `horus status` to see it)";
97316
97348
  case "not-analyzed":
@@ -97459,7 +97491,7 @@ async function runIndex(opts) {
97459
97491
  spawned = true;
97460
97492
  console.log(import_picocolors3.default.bold(`Indexing ${label}`) + import_picocolors3.default.dim(` (${root})`));
97461
97493
  if (!await sourceAvailable()) {
97462
- console.error(import_picocolors3.default.red("horus-source not found on PATH. Install it: pip install horus-source"));
97494
+ console.error(import_picocolors3.default.red("horus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash"));
97463
97495
  return 1;
97464
97496
  }
97465
97497
  try {
@@ -97480,28 +97512,41 @@ async function runIndex(opts) {
97480
97512
  console.log(import_picocolors3.default.dim(" already analyzed"));
97481
97513
  }
97482
97514
  const priorPort = parseHostPort(configuredHost ?? "") ?? parseHostPort(readSourceHostUrl(root) ?? "");
97483
- let port;
97484
- if (priorPort !== null) {
97485
- try {
97486
- port = await findFreePort(priorPort, priorPort);
97487
- } catch {
97515
+ const MAX_PORT_ATTEMPTS = 4;
97516
+ let started = false;
97517
+ for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS && !started; attempt += 1) {
97518
+ let port;
97519
+ if (attempt === 0 && priorPort !== null) {
97520
+ try {
97521
+ port = await findFreePort(priorPort, priorPort);
97522
+ } catch {
97523
+ port = await findFreePort();
97524
+ }
97525
+ } else {
97488
97526
  port = await findFreePort();
97489
97527
  }
97490
- } else {
97491
- port = await findFreePort();
97528
+ hostUrl = `http://127.0.0.1:${port}`;
97529
+ console.log(
97530
+ import_picocolors3.default.dim(
97531
+ attempt === 0 ? ` starting source-intelligence host on port ${port}\u2026` : ` port contended \u2014 retrying on ${port}\u2026`
97532
+ )
97533
+ );
97534
+ startHost(root, port);
97535
+ if (await waitForHost(hostUrl)) {
97536
+ reconcileSpawnedHostPid(root, port);
97537
+ started = true;
97538
+ } else {
97539
+ removeSpawnedHostRecord(root);
97540
+ }
97492
97541
  }
97493
- hostUrl = `http://127.0.0.1:${port}`;
97494
- console.log(import_picocolors3.default.dim(` starting source-intelligence host on port ${port}\u2026`));
97495
- startHost(root, port);
97496
- if (!await waitForHost(hostUrl)) {
97497
- removeSpawnedHostRecord(root);
97542
+ if (!started) {
97498
97543
  console.error(
97499
97544
  import_picocolors3.default.red(` Source-intelligence host did not become healthy \u2014 see ${root}/.horus/source-host.log`)
97500
97545
  );
97501
97546
  return 1;
97502
97547
  }
97503
- reconcileSpawnedHostPid(root, port);
97504
97548
  }
97549
+ if (!hostUrl) return 1;
97505
97550
  await stitchQueueMap(hostUrl, dbUrl, label);
97506
97551
  buildKnowledgeIndex(root, config2, label, configuredProject, opts);
97507
97552
  const hasLocalConfig = discoverLocalConfig(root) !== null;
@@ -112897,7 +112942,7 @@ async function runSetup(opts) {
112897
112942
  write(
112898
112943
  import_picocolors32.default.dim(
112899
112944
  ` install it (Python 3.11+ required):
112900
- pip install horus-source
112945
+ curl -fsSL https://horus.sh/install.sh | bash
112901
112946
  ensure ~/.local/bin is on your PATH`
112902
112947
  )
112903
112948
  );
@@ -112909,7 +112954,7 @@ async function runSetup(opts) {
112909
112954
  write(
112910
112955
  import_picocolors32.default.dim(
112911
112956
  ` update it:
112912
- pip install --upgrade horus-source`
112957
+ horus update`
112913
112958
  )
112914
112959
  );
112915
112960
  } else {
@@ -114851,7 +114896,7 @@ async function runReadiness(opts) {
114851
114896
  status: "warn",
114852
114897
  blocking: false,
114853
114898
  detail: "not installed \u2014 source intelligence unavailable",
114854
- next: `pip install horus-source`
114899
+ next: `curl -fsSL https://horus.sh/install.sh | bash`
114855
114900
  });
114856
114901
  } else if (sourceVersion !== PINNED_SOURCE_VERSION) {
114857
114902
  checks.push({
@@ -114859,7 +114904,7 @@ async function runReadiness(opts) {
114859
114904
  status: "warn",
114860
114905
  blocking: false,
114861
114906
  detail: `version mismatch (installed: ${sourceVersion}, required: ${PINNED_SOURCE_VERSION})`,
114862
- next: `pip install horus-source`
114907
+ next: `horus update`
114863
114908
  });
114864
114909
  } else {
114865
114910
  checks.push({
@@ -115449,7 +115494,40 @@ var import_node_fs19 = require("fs");
115449
115494
  var import_node_path21 = require("path");
115450
115495
  var import_node_os6 = require("os");
115451
115496
  var import_node_crypto7 = require("crypto");
115497
+ var import_node_child_process9 = require("child_process");
115498
+ var import_node_util5 = require("util");
115452
115499
  var import_picocolors43 = __toESM(require_picocolors(), 1);
115500
+ var execFileAsync2 = (0, import_node_util5.promisify)(import_node_child_process9.execFile);
115501
+ async function ensureBackendPinned(write) {
115502
+ let installed = null;
115503
+ try {
115504
+ installed = await getSourceVersion();
115505
+ } catch {
115506
+ installed = null;
115507
+ }
115508
+ if (installed === PINNED_SOURCE_VERSION) {
115509
+ write(` ${import_picocolors43.default.green("\u2713")} Source backend already on pinned ${PINNED_SOURCE_VERSION}.`);
115510
+ return;
115511
+ }
115512
+ if (installed === null) {
115513
+ write(
115514
+ ` ${import_picocolors43.default.dim("Source backend not installed \u2014 install it: curl -fsSL https://horus.sh/install.sh | bash")}`
115515
+ );
115516
+ return;
115517
+ }
115518
+ write(` Upgrading source backend ${installed} \u2192 ${PINNED_SOURCE_VERSION}\u2026`);
115519
+ try {
115520
+ await execFileAsync2(
115521
+ "uv",
115522
+ ["tool", "install", "--force", `horus-source==${PINNED_SOURCE_VERSION}`],
115523
+ { timeout: 3e5 }
115524
+ );
115525
+ write(` ${import_picocolors43.default.green("\u2713")} Source backend upgraded to ${PINNED_SOURCE_VERSION}.`);
115526
+ } catch {
115527
+ write(` ${import_picocolors43.default.yellow("!")} Couldn't auto-upgrade the source backend. Re-run the installer:`);
115528
+ write(` ${import_picocolors43.default.dim("curl -fsSL https://horus.sh/install.sh | bash")}`);
115529
+ }
115530
+ }
115453
115531
  var RELEASES_API = "https://api.github.com/repos/meritt-dev/horus/releases/latest";
115454
115532
  function parseVersion(v4) {
115455
115533
  const m5 = v4.replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
@@ -115517,6 +115595,7 @@ ${import_picocolors43.default.bold("Horus update")}
115517
115595
  if (!newer && !opts.force) {
115518
115596
  write(`
115519
115597
  ${import_picocolors43.default.green("\u2713")} Already on the latest version.`);
115598
+ await ensureBackendPinned(write);
115520
115599
  return 0;
115521
115600
  }
115522
115601
  const tag = release.tag_name;
@@ -115612,19 +115691,20 @@ ${import_picocolors43.default.bold("Horus update")}
115612
115691
  }
115613
115692
  write(` ${import_picocolors43.default.green("\u2713")} Updated: ${import_picocolors43.default.bold(currentVersion)} \u2192 ${import_picocolors43.default.bold(latest)}`);
115614
115693
  write(` ${import_picocolors43.default.dim(binaryPath)}`);
115694
+ await ensureBackendPinned(write);
115615
115695
  return 0;
115616
115696
  }
115617
115697
 
115618
115698
  // ../../packages/cli/src/commands/login.ts
115619
115699
  init_cjs_shims();
115620
- var import_node_child_process9 = require("child_process");
115700
+ var import_node_child_process10 = require("child_process");
115621
115701
  var import_picocolors44 = __toESM(require_picocolors(), 1);
115622
115702
  var sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms));
115623
115703
  function openBrowser(url) {
115624
115704
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
115625
115705
  const args2 = process.platform === "win32" ? ["/c", "start", "", url] : [url];
115626
115706
  try {
115627
- (0, import_node_child_process9.execFile)(cmd, args2, () => {
115707
+ (0, import_node_child_process10.execFile)(cmd, args2, () => {
115628
115708
  });
115629
115709
  } catch {
115630
115710
  }
@@ -115763,7 +115843,7 @@ var import_promises3 = require("readline/promises");
115763
115843
 
115764
115844
  // ../../packages/cli/src/lib/cloud/git.ts
115765
115845
  init_cjs_shims();
115766
- var import_node_child_process10 = require("child_process");
115846
+ var import_node_child_process11 = require("child_process");
115767
115847
  function parseRepoName(remoteUrl) {
115768
115848
  const cleaned = remoteUrl.trim().replace(/\.git$/, "");
115769
115849
  const lastSegment = cleaned.split(/[/:]/).filter(Boolean).pop() ?? "";
@@ -115787,7 +115867,7 @@ function inferProvider(remoteUrl) {
115787
115867
  }
115788
115868
  function detectGitRemote(root) {
115789
115869
  try {
115790
- const remoteUrl = (0, import_node_child_process10.execFileSync)("git", ["-C", root, "remote", "get-url", "origin"], {
115870
+ const remoteUrl = (0, import_node_child_process11.execFileSync)("git", ["-C", root, "remote", "get-url", "origin"], {
115791
115871
  encoding: "utf8",
115792
115872
  stdio: ["ignore", "pipe", "ignore"]
115793
115873
  }).trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.8.12",
3
+ "version": "0.8.13",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {