@merittdev/horus 0.8.6 → 0.8.8
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 +133 -68
- 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.8.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.8.8" : "dev";
|
|
65884
65884
|
var PINNED_SOURCE_VERSION = "1.5.0";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
@@ -70614,6 +70614,11 @@ async function loadConfig(configPath, opts) {
|
|
|
70614
70614
|
} else {
|
|
70615
70615
|
const jsPath = (0, import_node_path2.resolve)(cwd, "config/horus.config.js");
|
|
70616
70616
|
const tsPath = (0, import_node_path2.resolve)(cwd, "config/horus.config.ts");
|
|
70617
|
+
if (!(0, import_node_fs2.existsSync)(jsPath) && !(0, import_node_fs2.existsSync)(tsPath)) {
|
|
70618
|
+
throw new Error(
|
|
70619
|
+
`No Horus config found for ${cwd}. Run \`horus index\` in this repo to set it up, or pass --config <path> / --name <project>.`
|
|
70620
|
+
);
|
|
70621
|
+
}
|
|
70617
70622
|
target = (0, import_node_fs2.existsSync)(jsPath) ? jsPath : tsPath;
|
|
70618
70623
|
}
|
|
70619
70624
|
}
|
|
@@ -91925,6 +91930,8 @@ function correlate(evidence2) {
|
|
|
91925
91930
|
init_cjs_shims();
|
|
91926
91931
|
var PREFER = /resolver|controller|\bservice\b|route|router|handler|gateway|processor|worker|consumer|repository|usecase|use-case|endpoint/i;
|
|
91927
91932
|
var DEMOTE = /\butil|helper|\bscript|backfill|legacy|migration|seeder|fixture|\bmock\b|\btest\b|\bspec\b/i;
|
|
91933
|
+
var PRESENTATION_NAME = /^(build|format|render|compose|serialize)[A-Z].*(Message|Markup|Html|Text|Label|Summary|Notification|String)$/;
|
|
91934
|
+
var PREDICATE_NAME = /^(is|has|can|should|was|were|are|does|did|needs|allows?|must)[A-Z]/;
|
|
91928
91935
|
var TYPE_SUFFIX = /(Result|Input|Response|Payload|Args|Dto|Props|Edge|Connection)$/;
|
|
91929
91936
|
var INTERFACE_PREFIX = /^I(?=[A-Z][a-z])/;
|
|
91930
91937
|
var TYPE_LIKE_NAME = new RegExp(`(?:${TYPE_SUFFIX.source})|(?:${INTERFACE_PREFIX.source})`);
|
|
@@ -91952,8 +91959,9 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode) {
|
|
|
91952
91959
|
let score = 0;
|
|
91953
91960
|
if (PREFER.test(hay)) score += hintHasCode ? 0 : 3;
|
|
91954
91961
|
if (DEMOTE.test(hay)) score -= 3;
|
|
91962
|
+
if (PRESENTATION_NAME.test(s5.name)) score -= 3;
|
|
91955
91963
|
if (isTypeLikeName(s5.name)) score -= 4;
|
|
91956
|
-
if (s5.startLine !== void 0 && s5.endLine !== void 0 && s5.endLine - s5.startLine <= 3) {
|
|
91964
|
+
if (s5.startLine !== void 0 && s5.endLine !== void 0 && s5.endLine - s5.startLine <= 3 && !PREDICATE_NAME.test(s5.name)) {
|
|
91957
91965
|
score -= 3;
|
|
91958
91966
|
}
|
|
91959
91967
|
if (changedFiles !== void 0 && changedFiles.has(s5.filePath)) score += 5;
|
|
@@ -92279,6 +92287,15 @@ function clamp015(n4) {
|
|
|
92279
92287
|
var SEV_ERROR = 2;
|
|
92280
92288
|
var SEV_WARN = 1;
|
|
92281
92289
|
var SEV_INFO = 0;
|
|
92290
|
+
var GENERIC_SYMBOL_NAMES = /* @__PURE__ */ new Set([
|
|
92291
|
+
"",
|
|
92292
|
+
"constructor",
|
|
92293
|
+
"unknown",
|
|
92294
|
+
"unknown-producer",
|
|
92295
|
+
"unknown-worker",
|
|
92296
|
+
"anonymous",
|
|
92297
|
+
"default"
|
|
92298
|
+
]);
|
|
92282
92299
|
function seedEmittedSeverityTier(level, code, message) {
|
|
92283
92300
|
const lv = level?.toLowerCase();
|
|
92284
92301
|
if (lv === "error" || lv === "fatal" || lv === "critical") return SEV_ERROR;
|
|
@@ -92740,16 +92757,33 @@ async function investigate(input, deps) {
|
|
|
92740
92757
|
} catch {
|
|
92741
92758
|
edges = [];
|
|
92742
92759
|
}
|
|
92743
|
-
const symbolNames =
|
|
92744
|
-
top.name,
|
|
92745
|
-
|
|
92746
|
-
|
|
92747
|
-
|
|
92760
|
+
const symbolNames = new Set(
|
|
92761
|
+
[top.name, ...ctx.callers.map((s5) => s5.name), ...ctx.callees.map((s5) => s5.name)].filter(
|
|
92762
|
+
(n4) => n4 && !GENERIC_SYMBOL_NAMES.has(n4)
|
|
92763
|
+
)
|
|
92764
|
+
);
|
|
92748
92765
|
queueHits = edges.filter((e) => {
|
|
92749
92766
|
const bySymbol = e.producerSymbol !== null && symbolNames.has(e.producerSymbol) || e.workerSymbol !== null && symbolNames.has(e.workerSymbol);
|
|
92750
92767
|
const byFile = e.producerFile === top.filePath || e.workerFile === top.filePath;
|
|
92751
92768
|
return bySymbol || byFile;
|
|
92752
92769
|
});
|
|
92770
|
+
if (queueHits.length > 0 && typeof code.searchSymbols === "function") {
|
|
92771
|
+
const resolvesInRepo = async (name2) => {
|
|
92772
|
+
if (!name2 || GENERIC_SYMBOL_NAMES.has(name2)) return false;
|
|
92773
|
+
try {
|
|
92774
|
+
return (await code.searchSymbols(name2, 3)).some((s5) => s5.name === name2);
|
|
92775
|
+
} catch {
|
|
92776
|
+
return true;
|
|
92777
|
+
}
|
|
92778
|
+
};
|
|
92779
|
+
const validated2 = [];
|
|
92780
|
+
for (const e of queueHits) {
|
|
92781
|
+
if (await resolvesInRepo(e.producerSymbol) || await resolvesInRepo(e.workerSymbol)) {
|
|
92782
|
+
validated2.push(e);
|
|
92783
|
+
}
|
|
92784
|
+
}
|
|
92785
|
+
queueHits = validated2;
|
|
92786
|
+
}
|
|
92753
92787
|
if (input.since !== void 0 && looksDiffable(input.since)) {
|
|
92754
92788
|
try {
|
|
92755
92789
|
changes = await code.detectChanges({ base: input.since, compare: "HEAD" });
|
|
@@ -92933,7 +92967,10 @@ async function investigate(input, deps) {
|
|
|
92933
92967
|
message: sig.sampleMessage ?? "",
|
|
92934
92968
|
evId: existingEvId,
|
|
92935
92969
|
isNew: sig.isNew ?? false,
|
|
92936
|
-
level: sig.level ?? null
|
|
92970
|
+
level: sig.level ?? null,
|
|
92971
|
+
raisedBySeed: true,
|
|
92972
|
+
// literal is in the seed's own body
|
|
92973
|
+
raiserName: top?.name ?? ""
|
|
92937
92974
|
});
|
|
92938
92975
|
}
|
|
92939
92976
|
}
|
|
@@ -92981,7 +93018,10 @@ async function investigate(input, deps) {
|
|
|
92981
93018
|
message: sig.sampleMessage ?? "",
|
|
92982
93019
|
evId: ev.id,
|
|
92983
93020
|
isNew: sig.isNew ?? false,
|
|
92984
|
-
level: sig.level ?? null
|
|
93021
|
+
level: sig.level ?? null,
|
|
93022
|
+
raisedBySeed: true,
|
|
93023
|
+
// literal is in the seed's own body
|
|
93024
|
+
raiserName: top?.name ?? ""
|
|
92985
93025
|
});
|
|
92986
93026
|
}
|
|
92987
93027
|
} catch {
|
|
@@ -93005,13 +93045,14 @@ async function investigate(input, deps) {
|
|
|
93005
93045
|
])
|
|
93006
93046
|
].slice(0, GAP_D_MAX_RESOLVE * 2);
|
|
93007
93047
|
for (const sig of candidates) {
|
|
93008
|
-
let
|
|
93048
|
+
let raisers = [];
|
|
93009
93049
|
try {
|
|
93010
|
-
|
|
93050
|
+
raisers = await deps.code.searchSymbols(sig.key, 3);
|
|
93011
93051
|
} catch {
|
|
93012
93052
|
continue;
|
|
93013
93053
|
}
|
|
93014
|
-
|
|
93054
|
+
const raiser = raisers.find((r) => r.id === top.id) ?? raisers.find((r) => seedScope.has(r.id));
|
|
93055
|
+
if (raiser === void 0) continue;
|
|
93015
93056
|
let evId = directEvIdByKey.get(sig.key);
|
|
93016
93057
|
if (evId === void 0) {
|
|
93017
93058
|
const where = raiser.id === top.id ? "the seed" : "a callee of the seed";
|
|
@@ -93054,7 +93095,9 @@ async function investigate(input, deps) {
|
|
|
93054
93095
|
message: sig.sampleMessage ?? "",
|
|
93055
93096
|
evId,
|
|
93056
93097
|
isNew: sig.isNew ?? false,
|
|
93057
|
-
level: sig.level ?? null
|
|
93098
|
+
level: sig.level ?? null,
|
|
93099
|
+
raisedBySeed: raiser.id === top.id,
|
|
93100
|
+
raiserName: raiser.name
|
|
93058
93101
|
});
|
|
93059
93102
|
}
|
|
93060
93103
|
}
|
|
@@ -93643,6 +93686,7 @@ async function investigate(input, deps) {
|
|
|
93643
93686
|
}
|
|
93644
93687
|
if (seedEmittedJoins.length > 0 && top && seedEv) {
|
|
93645
93688
|
const ordered = [...seedEmittedJoins].sort((a2, b5) => {
|
|
93689
|
+
if (a2.raisedBySeed !== b5.raisedBySeed) return a2.raisedBySeed ? -1 : 1;
|
|
93646
93690
|
const ta = seedEmittedSeverityTier(a2.level, a2.code, a2.message);
|
|
93647
93691
|
const tb = seedEmittedSeverityTier(b5.level, b5.code, b5.message);
|
|
93648
93692
|
if (ta !== tb) return tb - ta;
|
|
@@ -93656,17 +93700,21 @@ async function investigate(input, deps) {
|
|
|
93656
93700
|
const tier = seedEmittedSeverityTier(lead.level, lead.code, lead.message);
|
|
93657
93701
|
const volumeBoost = Math.min(0.1, Math.log10(Math.max(1, lead.count)) * 0.025);
|
|
93658
93702
|
const msgClause = lead.message ? ` \u2014 "${lead.message.replace(/\s+/g, " ").trim().slice(0, 90)}"` : "";
|
|
93703
|
+
const bySeed = lead.raisedBySeed;
|
|
93704
|
+
const raiser = bySeed ? top.name : lead.raiserName || top.name;
|
|
93705
|
+
const depClause = bySeed ? "" : `, a dependency of ${top.name}`;
|
|
93706
|
+
const calleePenalty = bySeed ? 0 : 0.18;
|
|
93659
93707
|
let title;
|
|
93660
93708
|
let baseScore;
|
|
93661
93709
|
if (tier === SEV_ERROR) {
|
|
93662
|
-
title = `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${
|
|
93663
|
-
baseScore = clamp015(0.6 + volumeBoost);
|
|
93710
|
+
title = bySeed ? `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${raiser} \u2014 the likely failure${msgClause}` : `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) surfaces on ${raiser}${depClause}${msgClause}`;
|
|
93711
|
+
baseScore = clamp015(0.6 + volumeBoost - calleePenalty);
|
|
93664
93712
|
} else if (tier === SEV_WARN) {
|
|
93665
|
-
title = `Warning ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${
|
|
93666
|
-
baseScore = clamp015(0.42 + volumeBoost);
|
|
93713
|
+
title = `Warning ${lead.code} (${lead.count}x in Elasticsearch) ${bySeed ? "is raised by" : "surfaces on"} ${raiser}${depClause}${msgClause}`;
|
|
93714
|
+
baseScore = clamp015(0.42 + volumeBoost - calleePenalty);
|
|
93667
93715
|
} else {
|
|
93668
|
-
title = `Diagnostic signal ${lead.code} (${lead.count}x in Elasticsearch) is logged by ${
|
|
93669
|
-
baseScore = clamp015(0.3 + volumeBoost);
|
|
93716
|
+
title = `Diagnostic signal ${lead.code} (${lead.count}x in Elasticsearch) is logged by ${raiser}${depClause} \u2014 informational, not an error${msgClause}`;
|
|
93717
|
+
baseScore = clamp015(0.3 + volumeBoost - calleePenalty);
|
|
93670
93718
|
}
|
|
93671
93719
|
causeInputs.push({
|
|
93672
93720
|
id: "cause:seed-emitted-error",
|
|
@@ -97230,6 +97278,50 @@ async function stitch(client, db, opts = {}) {
|
|
|
97230
97278
|
};
|
|
97231
97279
|
}
|
|
97232
97280
|
|
|
97281
|
+
// ../../packages/cli/src/lib/ensure-host.ts
|
|
97282
|
+
init_cjs_shims();
|
|
97283
|
+
function parseHostPort(hostUrl) {
|
|
97284
|
+
try {
|
|
97285
|
+
const port = parseInt(new URL(hostUrl).port, 10);
|
|
97286
|
+
return Number.isNaN(port) ? null : port;
|
|
97287
|
+
} catch {
|
|
97288
|
+
return null;
|
|
97289
|
+
}
|
|
97290
|
+
}
|
|
97291
|
+
function ensureHostReasonHint(reason) {
|
|
97292
|
+
switch (reason) {
|
|
97293
|
+
case "source-unavailable":
|
|
97294
|
+
return "horus-source is not installed \u2014 install it: pip install horus-source";
|
|
97295
|
+
case "version-mismatch":
|
|
97296
|
+
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)";
|
|
97297
|
+
case "not-analyzed":
|
|
97298
|
+
return "this repo has not been indexed yet \u2014 run: horus index";
|
|
97299
|
+
case "bad-url":
|
|
97300
|
+
return "the configured source host URL is not a valid URL";
|
|
97301
|
+
default:
|
|
97302
|
+
return "the host did not become healthy \u2014 run: horus index";
|
|
97303
|
+
}
|
|
97304
|
+
}
|
|
97305
|
+
async function ensureSourceHost(root, hostUrl, opts = {}) {
|
|
97306
|
+
if (await isHostHealthy(hostUrl)) return { ok: true, hostUrl };
|
|
97307
|
+
if (!await sourceAvailable()) return { ok: false, reason: "source-unavailable" };
|
|
97308
|
+
try {
|
|
97309
|
+
await assertSourceVersionPinned();
|
|
97310
|
+
} catch {
|
|
97311
|
+
return { ok: false, reason: "version-mismatch" };
|
|
97312
|
+
}
|
|
97313
|
+
if (!isAnalyzed(root)) return { ok: false, reason: "not-analyzed" };
|
|
97314
|
+
const port = parseHostPort(hostUrl);
|
|
97315
|
+
if (port === null) return { ok: false, reason: "bad-url" };
|
|
97316
|
+
startHost(root, port);
|
|
97317
|
+
if (await waitForHost(hostUrl, opts.timeoutMs ?? 2e4)) {
|
|
97318
|
+
reconcileSpawnedHostPid(root, port);
|
|
97319
|
+
return { ok: true, hostUrl };
|
|
97320
|
+
}
|
|
97321
|
+
removeSpawnedHostRecord(root);
|
|
97322
|
+
return { ok: false, reason: "unhealthy" };
|
|
97323
|
+
}
|
|
97324
|
+
|
|
97233
97325
|
// ../../packages/cli/src/commands/index-repo.ts
|
|
97234
97326
|
async function stitchQueueMap(hostUrl, dbUrl, label) {
|
|
97235
97327
|
const source = new SourceHttpClient({ baseUrl: hostUrl });
|
|
@@ -97243,6 +97335,14 @@ async function stitchQueueMap(hostUrl, dbUrl, label) {
|
|
|
97243
97335
|
await sql2.end();
|
|
97244
97336
|
}
|
|
97245
97337
|
}
|
|
97338
|
+
async function hostServesRepo(hostUrl, root) {
|
|
97339
|
+
try {
|
|
97340
|
+
const info2 = await new SourceHttpClient({ baseUrl: hostUrl }).hostInfo();
|
|
97341
|
+
return (0, import_node_path10.resolve)(info2.repoPath) === (0, import_node_path10.resolve)(root);
|
|
97342
|
+
} catch {
|
|
97343
|
+
return true;
|
|
97344
|
+
}
|
|
97345
|
+
}
|
|
97246
97346
|
function findConfiguredRepo(config2, root) {
|
|
97247
97347
|
const target = (0, import_node_path10.resolve)(root);
|
|
97248
97348
|
for (const p5 of config2.projects) {
|
|
@@ -97329,7 +97429,7 @@ async function runIndex(opts) {
|
|
|
97329
97429
|
let hostUrl;
|
|
97330
97430
|
let spawned = false;
|
|
97331
97431
|
for (const candidate of [configuredHost, readSourceHostUrl(root) ?? void 0]) {
|
|
97332
|
-
if (candidate && await isHostHealthy(candidate)) {
|
|
97432
|
+
if (candidate && await isHostHealthy(candidate) && await hostServesRepo(candidate, root)) {
|
|
97333
97433
|
hostUrl = candidate;
|
|
97334
97434
|
break;
|
|
97335
97435
|
}
|
|
@@ -97360,7 +97460,17 @@ async function runIndex(opts) {
|
|
|
97360
97460
|
} else {
|
|
97361
97461
|
console.log(import_picocolors3.default.dim(" already analyzed"));
|
|
97362
97462
|
}
|
|
97363
|
-
const
|
|
97463
|
+
const priorPort = parseHostPort(configuredHost ?? "") ?? parseHostPort(readSourceHostUrl(root) ?? "");
|
|
97464
|
+
let port;
|
|
97465
|
+
if (priorPort !== null) {
|
|
97466
|
+
try {
|
|
97467
|
+
port = await findFreePort(priorPort, priorPort);
|
|
97468
|
+
} catch {
|
|
97469
|
+
port = await findFreePort();
|
|
97470
|
+
}
|
|
97471
|
+
} else {
|
|
97472
|
+
port = await findFreePort();
|
|
97473
|
+
}
|
|
97364
97474
|
hostUrl = `http://127.0.0.1:${port}`;
|
|
97365
97475
|
console.log(import_picocolors3.default.dim(` starting source-intelligence host on port ${port}\u2026`));
|
|
97366
97476
|
startHost(root, port);
|
|
@@ -97375,7 +97485,8 @@ async function runIndex(opts) {
|
|
|
97375
97485
|
}
|
|
97376
97486
|
await stitchQueueMap(hostUrl, dbUrl, label);
|
|
97377
97487
|
buildKnowledgeIndex(root, config2, label, configuredProject, opts);
|
|
97378
|
-
|
|
97488
|
+
const hasLocalConfig = discoverLocalConfig(root) !== null;
|
|
97489
|
+
if (!isConfigured && !hasLocalConfig) {
|
|
97379
97490
|
const file = {
|
|
97380
97491
|
version: 1,
|
|
97381
97492
|
project: {
|
|
@@ -110296,52 +110407,6 @@ async function maybePromptFeedback(opts) {
|
|
|
110296
110407
|
// ../../packages/cli/src/lib/investigation-runner.ts
|
|
110297
110408
|
init_cjs_shims();
|
|
110298
110409
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
110299
|
-
|
|
110300
|
-
// ../../packages/cli/src/lib/ensure-host.ts
|
|
110301
|
-
init_cjs_shims();
|
|
110302
|
-
function parseHostPort(hostUrl) {
|
|
110303
|
-
try {
|
|
110304
|
-
const port = parseInt(new URL(hostUrl).port, 10);
|
|
110305
|
-
return Number.isNaN(port) ? null : port;
|
|
110306
|
-
} catch {
|
|
110307
|
-
return null;
|
|
110308
|
-
}
|
|
110309
|
-
}
|
|
110310
|
-
function ensureHostReasonHint(reason) {
|
|
110311
|
-
switch (reason) {
|
|
110312
|
-
case "source-unavailable":
|
|
110313
|
-
return "horus-source is not installed \u2014 install it: pip install horus-source";
|
|
110314
|
-
case "version-mismatch":
|
|
110315
|
-
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)";
|
|
110316
|
-
case "not-analyzed":
|
|
110317
|
-
return "this repo has not been indexed yet \u2014 run: horus index";
|
|
110318
|
-
case "bad-url":
|
|
110319
|
-
return "the configured source host URL is not a valid URL";
|
|
110320
|
-
default:
|
|
110321
|
-
return "the host did not become healthy \u2014 run: horus index";
|
|
110322
|
-
}
|
|
110323
|
-
}
|
|
110324
|
-
async function ensureSourceHost(root, hostUrl, opts = {}) {
|
|
110325
|
-
if (await isHostHealthy(hostUrl)) return { ok: true, hostUrl };
|
|
110326
|
-
if (!await sourceAvailable()) return { ok: false, reason: "source-unavailable" };
|
|
110327
|
-
try {
|
|
110328
|
-
await assertSourceVersionPinned();
|
|
110329
|
-
} catch {
|
|
110330
|
-
return { ok: false, reason: "version-mismatch" };
|
|
110331
|
-
}
|
|
110332
|
-
if (!isAnalyzed(root)) return { ok: false, reason: "not-analyzed" };
|
|
110333
|
-
const port = parseHostPort(hostUrl);
|
|
110334
|
-
if (port === null) return { ok: false, reason: "bad-url" };
|
|
110335
|
-
startHost(root, port);
|
|
110336
|
-
if (await waitForHost(hostUrl, opts.timeoutMs ?? 2e4)) {
|
|
110337
|
-
reconcileSpawnedHostPid(root, port);
|
|
110338
|
-
return { ok: true, hostUrl };
|
|
110339
|
-
}
|
|
110340
|
-
removeSpawnedHostRecord(root);
|
|
110341
|
-
return { ok: false, reason: "unhealthy" };
|
|
110342
|
-
}
|
|
110343
|
-
|
|
110344
|
-
// ../../packages/cli/src/lib/investigation-runner.ts
|
|
110345
110410
|
async function buildInvestigationContext(renv, opts) {
|
|
110346
110411
|
const log = opts.log ?? ((l2) => console.error(l2));
|
|
110347
110412
|
const code = codeForEnv(renv);
|