@merittdev/horus 0.14.1 → 0.15.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.
- package/dist/index.cjs +119 -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.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.15.1" : "dev";
|
|
65884
65884
|
var PINNED_SOURCE_VERSION = "2.0.2";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
@@ -93580,6 +93580,94 @@ function selectHeadlineCause(rankedCauses, isLinkedToSeed, rerank) {
|
|
|
93580
93580
|
return { topCause, topLinkedCause, headlineCause, headlineLinked };
|
|
93581
93581
|
}
|
|
93582
93582
|
|
|
93583
|
+
// ../../packages/engine/src/detect-data-flow-cause.ts
|
|
93584
|
+
init_cjs_shims();
|
|
93585
|
+
function stripComments(s5) {
|
|
93586
|
+
return s5.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/\/\/[^\n]*/g, " ");
|
|
93587
|
+
}
|
|
93588
|
+
function grp(m5, i3) {
|
|
93589
|
+
return m5[i3] ?? "";
|
|
93590
|
+
}
|
|
93591
|
+
function hedged(symbolName, clause) {
|
|
93592
|
+
return `${symbolName} could explain this \u2014 it ${clause}. Source-only signal: verify against runtime evidence.`;
|
|
93593
|
+
}
|
|
93594
|
+
var CONFIG_SRC = /process\.env|config\.|opts\.|options\.|this\.|props\.|import\.meta/;
|
|
93595
|
+
function detectFixedCadence(body2, name2) {
|
|
93596
|
+
const timer2 = /\bset(?:Interval|Timeout)\s*\(\s*[^,]+,\s*([0-9_]{3,})\s*\)/.exec(body2);
|
|
93597
|
+
if (timer2) {
|
|
93598
|
+
const frag = grp(timer2, 0).trim().slice(0, 48);
|
|
93599
|
+
return { pattern: "fixed-cadence", matched: frag, baseScore: 0.2, title: hedged(name2, `polls/schedules on a fixed interval (\`${frag}\`), so data freshness is bounded by that cadence rather than the event`) };
|
|
93600
|
+
}
|
|
93601
|
+
const cron = /(['"`])(?:\*|[0-9,*/-]+)(?:\s+[0-9,*/-]+){4}\1/.exec(body2);
|
|
93602
|
+
if (cron) {
|
|
93603
|
+
const frag = grp(cron, 0);
|
|
93604
|
+
return { pattern: "fixed-cadence", matched: frag.slice(0, 48), baseScore: 0.2, title: hedged(name2, `runs on a fixed cron cadence (\`${frag}\`), so an effect activates on the next tick, not at its target time`) };
|
|
93605
|
+
}
|
|
93606
|
+
const cad = /\b(?:pollInterval|refetchInterval|cadence|intervalMs|tickMs)\b\s*[:=]\s*[0-9_]{2,}/.exec(body2);
|
|
93607
|
+
if (cad && !CONFIG_SRC.test(grp(cad, 0))) {
|
|
93608
|
+
const frag = grp(cad, 0).trim().slice(0, 40);
|
|
93609
|
+
return { pattern: "fixed-cadence", matched: frag, baseScore: 0.2, title: hedged(name2, `uses a fixed cadence (\`${frag}\`); the symptom may track that interval`) };
|
|
93610
|
+
}
|
|
93611
|
+
return null;
|
|
93612
|
+
}
|
|
93613
|
+
function detectInPlaceMutation(body2, ctx) {
|
|
93614
|
+
const hay = `${ctx.symbol.name} ${ctx.symbol.filePath ?? ""} ${ctx.symbol.signature ?? ""}`;
|
|
93615
|
+
const contextual = /reduc|\bslice\b|store|mutat|\bstate\b/i.test(hay) || ctx.imports.some((i3) => /redux|zustand|immer|valtio|mobx/i.test(i3));
|
|
93616
|
+
if (!contextual) return null;
|
|
93617
|
+
if (/return\s*\{\s*\.\.\.|=>\s*\(?\s*\{\s*\.\.\.|\.\.\.(?:state|prev|draft)\b/.test(body2)) return null;
|
|
93618
|
+
const assign = /\b(state|store|draft|cache|acc|accumulator)(?:\.[A-Za-z0-9_]+)+\s*=(?!=)/.exec(body2);
|
|
93619
|
+
const method = /\b(state|store|draft|cache)(?:\.[A-Za-z0-9_]+)*\.(?:push|splice|sort|reverse|pop|shift|unshift)\s*\(/.exec(body2);
|
|
93620
|
+
const m5 = assign ?? method;
|
|
93621
|
+
if (!m5) return null;
|
|
93622
|
+
const obj = grp(m5, 1) || "state";
|
|
93623
|
+
const frag = grp(m5, 0).trim().slice(0, 40);
|
|
93624
|
+
return { pattern: "in-place-mutation", matched: frag, baseScore: 0.2, title: hedged(ctx.symbol.name, `appears to mutate the shared \`${obj}\` object in place (\`${frag}\`) rather than producing a new reference, so downstream readers may observe stale or partially-updated state`) };
|
|
93625
|
+
}
|
|
93626
|
+
function detectUnawaitedWrite(body2, ctx) {
|
|
93627
|
+
const isAsync2 = /\basync\b/.test(ctx.symbol.signature ?? "") || /\bawait\b/.test(body2);
|
|
93628
|
+
if (!isAsync2 || !/\bawait\b/.test(body2)) return null;
|
|
93629
|
+
const calleeNames = new Set(ctx.callees.map((c4) => c4.name));
|
|
93630
|
+
for (const line2 of body2.split("\n")) {
|
|
93631
|
+
const t2 = line2.trim();
|
|
93632
|
+
const call = /^([A-Za-z_$][\w.$]*)\s*\(/.exec(t2);
|
|
93633
|
+
if (!call) continue;
|
|
93634
|
+
if (/^(?:await|return|void|yield|const|let|var|if|for|while|}|else|switch|throw|case)\b/.test(t2)) continue;
|
|
93635
|
+
if (t2.includes(".then(") || t2.includes(".catch(") || t2.includes("await ")) continue;
|
|
93636
|
+
const callee = grp(call, 1);
|
|
93637
|
+
const fn2 = callee.split(".").pop() ?? "";
|
|
93638
|
+
if (/^(?:emit|log|console|track|debug|warn|error|info|print|notify|next|push|set[A-Z])/.test(fn2)) continue;
|
|
93639
|
+
const writeLike = /^(?:save|write|flush|persist|commit|sync|update|insert|upsert|fetch|load|send|post)/i.test(fn2) || /Async$/.test(fn2);
|
|
93640
|
+
if (!writeLike) continue;
|
|
93641
|
+
if (!calleeNames.has(callee) && !calleeNames.has(fn2)) continue;
|
|
93642
|
+
return { pattern: "unawaited-async-write", matched: t2.slice(0, 60), baseScore: 0.19, title: hedged(ctx.symbol.name, `calls \`${fn2}\` without \`await\` while awaiting elsewhere, so a later read may race ahead of that write`) };
|
|
93643
|
+
}
|
|
93644
|
+
return null;
|
|
93645
|
+
}
|
|
93646
|
+
function detectHardcodedBound(body2, name2) {
|
|
93647
|
+
const eq2 = /\b(Object\.is|shallowEqual|isEqual)\s*\(/.exec(body2);
|
|
93648
|
+
if (eq2) {
|
|
93649
|
+
return { pattern: "hardcoded-bound", matched: grp(eq2, 0).slice(0, 40), baseScore: 0.18, title: hedged(name2, `gates on a reference/shallow equality check (\`${grp(eq2, 1)}\`), so an in-place or structurally-equal update can be skipped and a change goes unobserved`) };
|
|
93650
|
+
}
|
|
93651
|
+
const re = /\b([A-Za-z_$][\w$]*)\s*(?:<=|>=|<|>|===|!==)\s*([0-9]+(?:\.[0-9]+)?)\b/g;
|
|
93652
|
+
for (let m5 = re.exec(body2); m5 !== null; m5 = re.exec(body2)) {
|
|
93653
|
+
const id = grp(m5, 1);
|
|
93654
|
+
const n4 = Number(grp(m5, 2));
|
|
93655
|
+
if (n4 === 0 || n4 === 1) continue;
|
|
93656
|
+
if (!/thresh|limit|max|min|retr|attempt|backoff|count|rate|ratio|ttl|timeout|delay|capacity|tolerance|distance|score/i.test(id)) continue;
|
|
93657
|
+
if (/\.length\b|\bidx\b|\bindex\b|\bi\b/.test(id)) continue;
|
|
93658
|
+
const frag = grp(m5, 0).trim().slice(0, 40);
|
|
93659
|
+
return { pattern: "hardcoded-bound", matched: frag, baseScore: 0.2, title: hedged(name2, `gates the flow on a fixed bound (\`${frag}\`), so behavior can flip at that constant independent of any deploy`) };
|
|
93660
|
+
}
|
|
93661
|
+
return null;
|
|
93662
|
+
}
|
|
93663
|
+
function detectDataFlowCause(ctx) {
|
|
93664
|
+
const raw = ctx.sourceBody ?? ctx.snippet ?? "";
|
|
93665
|
+
if (raw.length < 40) return null;
|
|
93666
|
+
const body2 = stripComments(raw);
|
|
93667
|
+
const nm = ctx.symbol.name;
|
|
93668
|
+
return detectFixedCadence(body2, nm) ?? detectInPlaceMutation(body2, ctx) ?? detectUnawaitedWrite(body2, ctx) ?? detectHardcodedBound(body2, nm);
|
|
93669
|
+
}
|
|
93670
|
+
|
|
93583
93671
|
// ../../packages/engine/src/hypotheses.ts
|
|
93584
93672
|
init_cjs_shims();
|
|
93585
93673
|
function generateHypotheses(evidence2, _correlation, ctx) {
|
|
@@ -95112,6 +95200,17 @@ var TYPE_LIKE_NAME = new RegExp(`(?:${TYPE_SUFFIX.source})|(?:${INTERFACE_PREFIX
|
|
|
95112
95200
|
function isTypeLikeName(name2) {
|
|
95113
95201
|
return TYPE_LIKE_NAME.test(name2);
|
|
95114
95202
|
}
|
|
95203
|
+
var CODEGEN_PATH_RE = /(^|\/)(?:__generated__|generated)\//i;
|
|
95204
|
+
var CODEGEN_FILE_RE = /(?:(?:^|\/)generated|[._-](?:generated|gen))\.[jt]sx?$/i;
|
|
95205
|
+
var GQL_GEN_FILE_RE = /\.(?:graphql|gql)\.[jt]sx?$/i;
|
|
95206
|
+
function isCodegenPath(filePath) {
|
|
95207
|
+
return CODEGEN_PATH_RE.test(filePath) || CODEGEN_FILE_RE.test(filePath) || GQL_GEN_FILE_RE.test(filePath);
|
|
95208
|
+
}
|
|
95209
|
+
var TYPE_DECL_KIND_RE = /^(?:type_alias|typealias|type|interface|enum)$/i;
|
|
95210
|
+
function isTypeDeclarationSymbol(s5) {
|
|
95211
|
+
const kind = s5.id.split(":", 1)[0] ?? "";
|
|
95212
|
+
return TYPE_DECL_KIND_RE.test(kind);
|
|
95213
|
+
}
|
|
95115
95214
|
function executableBaseName(name2) {
|
|
95116
95215
|
const stripped = name2.replace(TYPE_SUFFIX, "").replace(INTERFACE_PREFIX, "");
|
|
95117
95216
|
return stripped !== name2 && stripped.length >= 4 ? stripped : null;
|
|
@@ -95243,6 +95342,8 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode, qualifier,
|
|
|
95243
95342
|
score += Math.max(0, 5 - index2) * (hintHasCode ? 0.8 : 0.1);
|
|
95244
95343
|
if (s5.score !== void 0) score += s5.score * (hintHasCode ? 8 : 2);
|
|
95245
95344
|
if (/\.[jt]sx?$/i.test(s5.name)) score -= 5;
|
|
95345
|
+
if (isCodegenPath(s5.filePath)) score -= 5;
|
|
95346
|
+
if (isTypeDeclarationSymbol(s5)) score -= 6;
|
|
95246
95347
|
if (hintTokens !== void 0 && hintTokens.length > 0) {
|
|
95247
95348
|
let hintBoost = 0;
|
|
95248
95349
|
for (const tok of hintTokens) {
|
|
@@ -95262,6 +95363,9 @@ function isDeprioritizedSeedPath(filePath) {
|
|
|
95262
95363
|
filePath
|
|
95263
95364
|
);
|
|
95264
95365
|
}
|
|
95366
|
+
function isDeprioritizedSeed(s5) {
|
|
95367
|
+
return isDeprioritizedSeedPath(s5.filePath) || isCodegenPath(s5.filePath) || isTypeDeclarationSymbol(s5);
|
|
95368
|
+
}
|
|
95265
95369
|
function isAnchoredExactSeed(s5, qualifier, hintHasCode, preferNamed) {
|
|
95266
95370
|
if (qualifier && qualifierBoost(s5, qualifier) >= 40) return true;
|
|
95267
95371
|
if (preferNamed !== void 0 && s5.name.toLowerCase() === preferNamed.toLowerCase()) return true;
|
|
@@ -95274,7 +95378,7 @@ function rankSeeds(seeds, hintTokens, changedFiles, hintHasCode, qualifier, pref
|
|
|
95274
95378
|
score: scoreSeed(symbol, i3, hintTokens, changedFiles, hintHasCode, qualifier, preferNamed),
|
|
95275
95379
|
role: seedRole(symbol),
|
|
95276
95380
|
i: i3,
|
|
95277
|
-
deprio:
|
|
95381
|
+
deprio: isDeprioritizedSeed(symbol),
|
|
95278
95382
|
// HOR-430: a deprioritized candidate the request anchors on exactly is exempt from the
|
|
95279
95383
|
// hard demotion below — it keeps only the soft path penalty so a strong/exact match in an
|
|
95280
95384
|
// example/test file can still surface.
|
|
@@ -97690,6 +97794,19 @@ async function investigate(input, deps) {
|
|
|
97690
97794
|
});
|
|
97691
97795
|
}
|
|
97692
97796
|
}
|
|
97797
|
+
if (top && seedEv && ctx && !sourceImpactHint) {
|
|
97798
|
+
const dataFlow = detectDataFlowCause(ctx);
|
|
97799
|
+
if (dataFlow) {
|
|
97800
|
+
causeInputs.push({
|
|
97801
|
+
id: "cause:seed-data-flow",
|
|
97802
|
+
title: dataFlow.title.slice(0, 220),
|
|
97803
|
+
category: "data-flow",
|
|
97804
|
+
sourceEvidenceIds: [seedEv.id],
|
|
97805
|
+
baseScore: dataFlow.baseScore,
|
|
97806
|
+
metadata: { dataFlowPattern: dataFlow.pattern, matched: dataFlow.matched, blastRadius }
|
|
97807
|
+
});
|
|
97808
|
+
}
|
|
97809
|
+
}
|
|
97693
97810
|
if (analysis !== null && analysis.signatures.length > 0 && queueHits.length > 0 && directLogEvIds.length > 0) {
|
|
97694
97811
|
const firstQueue = queueHits[0];
|
|
97695
97812
|
const queueLabel = firstQueue !== void 0 ? `"${firstQueue.queueName}" (${firstQueue.producerSymbol ?? "unknown"} -> ${firstQueue.workerSymbol ?? "unknown"})` : "the queue path";
|