@merittdev/horus 0.15.0 → 0.15.2

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 +45 -5
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -65880,7 +65880,7 @@ init_cjs_shims();
65880
65880
 
65881
65881
  // ../../packages/core/src/version.ts
65882
65882
  init_cjs_shims();
65883
- var HORUS_VERSION = true ? "0.15.0" : "dev";
65883
+ var HORUS_VERSION = true ? "0.15.2" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "2.0.2";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -93610,6 +93610,19 @@ function detectFixedCadence(body2, name2) {
93610
93610
  }
93611
93611
  return null;
93612
93612
  }
93613
+ function detectExactMatchQuery(body2, name2) {
93614
+ const sql2 = /\bWHERE\b[^;]*?["'`]?\b\w+\b["'`]?\s*=\s*[$?:]\w*/i.exec(body2);
93615
+ if (sql2 && !/\b(?:LIKE|ILIKE|LOWER|UPPER|TRIM|SIMILAR|REGEXP)\b/i.test(grp(sql2, 0))) {
93616
+ const frag = grp(sql2, 0).trim().slice(0, 48);
93617
+ return { pattern: "exact-match-query", matched: frag, baseScore: 0.2, title: hedged(name2, `matches on an exact-equality query (\`${frag}\`) with no normalization, so a value differing only in formatting (case, whitespace, leading zeros) returns no rows`) };
93618
+ }
93619
+ const orm = /\.(?:find|findOne|findFirst|findUnique)\s*\(\s*\{\s*\w+\s*:/.exec(body2);
93620
+ if (orm) {
93621
+ const frag = grp(orm, 0).trim().slice(0, 40);
93622
+ return { pattern: "exact-match-query", matched: frag, baseScore: 0.2, title: hedged(name2, `looks up by an exact field match (\`${frag}\`) with no normalization, so a value differing only in formatting returns no match`) };
93623
+ }
93624
+ return null;
93625
+ }
93613
93626
  function detectInPlaceMutation(body2, ctx) {
93614
93627
  const hay = `${ctx.symbol.name} ${ctx.symbol.filePath ?? ""} ${ctx.symbol.signature ?? ""}`;
93615
93628
  const contextual = /reduc|\bslice\b|store|mutat|\bstate\b/i.test(hay) || ctx.imports.some((i3) => /redux|zustand|immer|valtio|mobx/i.test(i3));
@@ -93665,7 +93678,14 @@ function detectDataFlowCause(ctx) {
93665
93678
  if (raw.length < 40) return null;
93666
93679
  const body2 = stripComments(raw);
93667
93680
  const nm = ctx.symbol.name;
93668
- return detectFixedCadence(body2, nm) ?? detectInPlaceMutation(body2, ctx) ?? detectUnawaitedWrite(body2, ctx) ?? detectHardcodedBound(body2, nm);
93681
+ return detectFixedCadence(body2, nm) ?? detectExactMatchQuery(body2, nm) ?? detectInPlaceMutation(body2, ctx) ?? detectUnawaitedWrite(body2, ctx) ?? detectHardcodedBound(body2, nm);
93682
+ }
93683
+ function detectDataFlowCauseAcross(contexts) {
93684
+ for (const c4 of contexts) {
93685
+ const f5 = detectDataFlowCause(c4);
93686
+ if (f5) return f5;
93687
+ }
93688
+ return null;
93669
93689
  }
93670
93690
 
93671
93691
  // ../../packages/engine/src/hypotheses.ts
@@ -95200,6 +95220,17 @@ var TYPE_LIKE_NAME = new RegExp(`(?:${TYPE_SUFFIX.source})|(?:${INTERFACE_PREFIX
95200
95220
  function isTypeLikeName(name2) {
95201
95221
  return TYPE_LIKE_NAME.test(name2);
95202
95222
  }
95223
+ var CODEGEN_PATH_RE = /(^|\/)(?:__generated__|generated)\//i;
95224
+ var CODEGEN_FILE_RE = /(?:(?:^|\/)generated|[._-](?:generated|gen))\.[jt]sx?$/i;
95225
+ var GQL_GEN_FILE_RE = /\.(?:graphql|gql)\.[jt]sx?$/i;
95226
+ function isCodegenPath(filePath) {
95227
+ return CODEGEN_PATH_RE.test(filePath) || CODEGEN_FILE_RE.test(filePath) || GQL_GEN_FILE_RE.test(filePath);
95228
+ }
95229
+ var TYPE_DECL_KIND_RE = /^(?:type_alias|typealias|type|interface|enum)$/i;
95230
+ function isTypeDeclarationSymbol(s5) {
95231
+ const kind = s5.id.split(":", 1)[0] ?? "";
95232
+ return TYPE_DECL_KIND_RE.test(kind);
95233
+ }
95203
95234
  function executableBaseName(name2) {
95204
95235
  const stripped = name2.replace(TYPE_SUFFIX, "").replace(INTERFACE_PREFIX, "");
95205
95236
  return stripped !== name2 && stripped.length >= 4 ? stripped : null;
@@ -95331,6 +95362,8 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode, qualifier,
95331
95362
  score += Math.max(0, 5 - index2) * (hintHasCode ? 0.8 : 0.1);
95332
95363
  if (s5.score !== void 0) score += s5.score * (hintHasCode ? 8 : 2);
95333
95364
  if (/\.[jt]sx?$/i.test(s5.name)) score -= 5;
95365
+ if (isCodegenPath(s5.filePath)) score -= 5;
95366
+ if (isTypeDeclarationSymbol(s5)) score -= 6;
95334
95367
  if (hintTokens !== void 0 && hintTokens.length > 0) {
95335
95368
  let hintBoost = 0;
95336
95369
  for (const tok of hintTokens) {
@@ -95350,6 +95383,9 @@ function isDeprioritizedSeedPath(filePath) {
95350
95383
  filePath
95351
95384
  );
95352
95385
  }
95386
+ function isDeprioritizedSeed(s5) {
95387
+ return isDeprioritizedSeedPath(s5.filePath) || isCodegenPath(s5.filePath) || isTypeDeclarationSymbol(s5);
95388
+ }
95353
95389
  function isAnchoredExactSeed(s5, qualifier, hintHasCode, preferNamed) {
95354
95390
  if (qualifier && qualifierBoost(s5, qualifier) >= 40) return true;
95355
95391
  if (preferNamed !== void 0 && s5.name.toLowerCase() === preferNamed.toLowerCase()) return true;
@@ -95362,7 +95398,7 @@ function rankSeeds(seeds, hintTokens, changedFiles, hintHasCode, qualifier, pref
95362
95398
  score: scoreSeed(symbol, i3, hintTokens, changedFiles, hintHasCode, qualifier, preferNamed),
95363
95399
  role: seedRole(symbol),
95364
95400
  i: i3,
95365
- deprio: isDeprioritizedSeedPath(symbol.filePath),
95401
+ deprio: isDeprioritizedSeed(symbol),
95366
95402
  // HOR-430: a deprioritized candidate the request anchors on exactly is exempt from the
95367
95403
  // hard demotion below — it keeps only the soft path penalty so a strong/exact match in an
95368
95404
  // example/test file can still surface.
@@ -97778,8 +97814,12 @@ async function investigate(input, deps) {
97778
97814
  });
97779
97815
  }
97780
97816
  }
97781
- if (top && seedEv && ctx && !sourceImpactHint) {
97782
- const dataFlow = detectDataFlowCause(ctx);
97817
+ if (top && seedEv && ctx && code && !sourceImpactHint) {
97818
+ const scanIds = [
97819
+ .../* @__PURE__ */ new Set([...seeds.slice(1, 4).map((s5) => s5.id), ...(ctx.callees ?? []).slice(0, 4).map((c4) => c4.id)])
97820
+ ].filter((id) => id !== top.id);
97821
+ const extraCtxs = (await Promise.all(scanIds.map((id) => code.context(id).catch(() => null)))).filter((c4) => c4 !== null);
97822
+ const dataFlow = detectDataFlowCauseAcross([ctx, ...extraCtxs]);
97783
97823
  if (dataFlow) {
97784
97824
  causeInputs.push({
97785
97825
  id: "cause:seed-data-flow",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {