@merittdev/horus 0.1.11 → 0.1.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.
- package/dist/index.cjs +768 -149
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50314,7 +50314,7 @@ init_cjs_shims();
|
|
|
50314
50314
|
|
|
50315
50315
|
// ../../packages/core/src/version.ts
|
|
50316
50316
|
init_cjs_shims();
|
|
50317
|
-
var HORUS_VERSION = true ? "0.1.
|
|
50317
|
+
var HORUS_VERSION = true ? "0.1.13" : "dev";
|
|
50318
50318
|
var PINNED_AXON_VERSION = "1.0.7";
|
|
50319
50319
|
var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
|
|
50320
50320
|
|
|
@@ -54699,6 +54699,19 @@ var modelsSchema = external_exports.object({
|
|
|
54699
54699
|
reasoning: external_exports.string().default("claude-opus-4-8"),
|
|
54700
54700
|
extraction: external_exports.string().default("claude-haiku-4-5")
|
|
54701
54701
|
});
|
|
54702
|
+
var AI_PROVIDERS = ["anthropic", "claude", "codex", "gemini", "kimi", "cursor"];
|
|
54703
|
+
var aiSchema = external_exports.object({
|
|
54704
|
+
/** Preferred provider for AI narrative. */
|
|
54705
|
+
provider: external_exports.enum(AI_PROVIDERS).optional(),
|
|
54706
|
+
anthropic: external_exports.object({
|
|
54707
|
+
/** Direct API key (takes priority over apiKeyEnv). Stored redacted in display. */
|
|
54708
|
+
apiKey: external_exports.string().optional(),
|
|
54709
|
+
/** Env var holding the key. Defaults to ANTHROPIC_API_KEY. */
|
|
54710
|
+
apiKeyEnv: external_exports.string().optional(),
|
|
54711
|
+
/** Default model for narrative generation. */
|
|
54712
|
+
model: external_exports.string().optional()
|
|
54713
|
+
}).optional()
|
|
54714
|
+
}).optional();
|
|
54702
54715
|
var horusConfigSchema = external_exports.object({
|
|
54703
54716
|
projects: external_exports.array(projectSchema).default([]),
|
|
54704
54717
|
axon: external_exports.object({
|
|
@@ -54706,8 +54719,19 @@ var horusConfigSchema = external_exports.object({
|
|
|
54706
54719
|
pinnedVersion: external_exports.string().default("1.0.7")
|
|
54707
54720
|
}).default({}),
|
|
54708
54721
|
database: databaseSchema,
|
|
54709
|
-
models: modelsSchema.default({})
|
|
54722
|
+
models: modelsSchema.default({}),
|
|
54723
|
+
ai: aiSchema
|
|
54710
54724
|
});
|
|
54725
|
+
function resolveAiSettings(config) {
|
|
54726
|
+
const ai = config.ai;
|
|
54727
|
+
const fromConfig = ai?.anthropic?.apiKey;
|
|
54728
|
+
const key = fromConfig ?? process.env[ai?.anthropic?.apiKeyEnv ?? "ANTHROPIC_API_KEY"];
|
|
54729
|
+
const out = { anthropicKeyFromConfig: fromConfig !== void 0 };
|
|
54730
|
+
if (ai?.provider !== void 0) out.provider = ai.provider;
|
|
54731
|
+
if (key !== void 0) out.anthropicApiKey = key;
|
|
54732
|
+
if (ai?.anthropic?.model !== void 0) out.model = ai.anthropic.model;
|
|
54733
|
+
return out;
|
|
54734
|
+
}
|
|
54711
54735
|
function listEnvironments(config) {
|
|
54712
54736
|
const out = [];
|
|
54713
54737
|
for (const p of config.projects) {
|
|
@@ -54892,7 +54916,8 @@ async function loadConfigFile(target) {
|
|
|
54892
54916
|
projects: file.project ? [file.project] : [],
|
|
54893
54917
|
database: file.database ?? {
|
|
54894
54918
|
url: process.env["DATABASE_URL"] ?? DEFAULT_DB_URL
|
|
54895
|
-
}
|
|
54919
|
+
},
|
|
54920
|
+
...file.ai !== void 0 ? { ai: file.ai } : {}
|
|
54896
54921
|
};
|
|
54897
54922
|
return parseConfig(raw, target);
|
|
54898
54923
|
}
|
|
@@ -55307,7 +55332,7 @@ var AxonCodeProvider = class {
|
|
|
55307
55332
|
}
|
|
55308
55333
|
async searchSymbols(query, limit = 10) {
|
|
55309
55334
|
const E = this.escapeId(query);
|
|
55310
|
-
const exactQuery = `MATCH (n) WHERE toLower(n.name) = toLower("${E}") AND
|
|
55335
|
+
const exactQuery = `MATCH (n) WHERE toLower(n.name) = toLower("${E}") AND label(n) <> "File" RETURN n.id, n.name, n.file_path LIMIT ${limit}`;
|
|
55311
55336
|
const [exactRows, semanticRes] = await Promise.all([
|
|
55312
55337
|
this.rows(exactQuery).catch(() => []),
|
|
55313
55338
|
this.client.search(query, limit)
|
|
@@ -67396,9 +67421,14 @@ async function runExplain(query, opts) {
|
|
|
67396
67421
|
}
|
|
67397
67422
|
async function isQueueBoundary(config, query) {
|
|
67398
67423
|
try {
|
|
67424
|
+
let project;
|
|
67425
|
+
try {
|
|
67426
|
+
project = resolveEnvironment(config).project;
|
|
67427
|
+
} catch {
|
|
67428
|
+
}
|
|
67399
67429
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
67400
67430
|
try {
|
|
67401
|
-
const edges = await listQueueEdges(db, { queueName: query });
|
|
67431
|
+
const edges = await listQueueEdges(db, { queueName: query, project });
|
|
67402
67432
|
return edges.length > 0;
|
|
67403
67433
|
} finally {
|
|
67404
67434
|
await sql2.end().catch(() => {
|
|
@@ -67508,22 +67538,22 @@ function extractQueueGraph(input) {
|
|
|
67508
67538
|
}
|
|
67509
67539
|
}
|
|
67510
67540
|
const producers = [];
|
|
67511
|
-
for (const
|
|
67512
|
-
for (const m of
|
|
67541
|
+
for (const pc35 of input.producerClasses) {
|
|
67542
|
+
for (const m of pc35.content.matchAll(INJECT_QUEUE_RE)) {
|
|
67513
67543
|
const queue = m[1] ?? "";
|
|
67514
|
-
if (queue) producers.push({ queue, symbol:
|
|
67544
|
+
if (queue) producers.push({ queue, symbol: pc35.name, file: pc35.filePath });
|
|
67515
67545
|
}
|
|
67516
|
-
for (const m of
|
|
67546
|
+
for (const m of pc35.content.matchAll(NEW_BULL_RE)) {
|
|
67517
67547
|
if (m[2] !== "Queue") continue;
|
|
67518
67548
|
const queue = resolveArg(m[3] ?? "", constMap);
|
|
67519
67549
|
if (queue) {
|
|
67520
|
-
producers.push({ queue, symbol: (m[1] ??
|
|
67550
|
+
producers.push({ queue, symbol: (m[1] ?? pc35.name) || baseName(pc35.filePath), file: pc35.filePath });
|
|
67521
67551
|
}
|
|
67522
67552
|
}
|
|
67523
|
-
for (const m of
|
|
67553
|
+
for (const m of pc35.content.matchAll(QUEUE_NAME_CONST_RE)) {
|
|
67524
67554
|
const ident = m[1] ?? "";
|
|
67525
67555
|
const queue = m[2] ?? "";
|
|
67526
|
-
if (queue) producers.push({ queue, symbol: ident || baseName(
|
|
67556
|
+
if (queue) producers.push({ queue, symbol: ident || baseName(pc35.filePath), file: pc35.filePath });
|
|
67527
67557
|
}
|
|
67528
67558
|
}
|
|
67529
67559
|
const workers = [];
|
|
@@ -67796,6 +67826,13 @@ async function runQueues(name, opts) {
|
|
|
67796
67826
|
}
|
|
67797
67827
|
}
|
|
67798
67828
|
const rows = await listQueueEdges(db, { project, queueName: name });
|
|
67829
|
+
if (opts.json) {
|
|
67830
|
+
const topology = topologyToJson(buildQueueMap(rows));
|
|
67831
|
+
const out = { project: project ?? null, topology };
|
|
67832
|
+
if (opts.live) out["live"] = await gatherLiveState(config, rows, name);
|
|
67833
|
+
console.log(JSON.stringify(out, null, 2));
|
|
67834
|
+
return 0;
|
|
67835
|
+
}
|
|
67799
67836
|
console.log(
|
|
67800
67837
|
import_picocolors4.default.bold("Queue topology") + import_picocolors4.default.dim(" \xB7 source: code / source intelligence \xB7 static (run horus index to refresh)")
|
|
67801
67838
|
);
|
|
@@ -67837,6 +67874,55 @@ function buildQueueMap(rows) {
|
|
|
67837
67874
|
}
|
|
67838
67875
|
return byQueue;
|
|
67839
67876
|
}
|
|
67877
|
+
function endpoints(edges, symKey, fileKey) {
|
|
67878
|
+
const seen = /* @__PURE__ */ new Map();
|
|
67879
|
+
for (const e of edges) {
|
|
67880
|
+
const symbol = e[symKey];
|
|
67881
|
+
if (!symbol) continue;
|
|
67882
|
+
if (!seen.has(symbol)) seen.set(symbol, { symbol, file: e[fileKey] ?? null });
|
|
67883
|
+
}
|
|
67884
|
+
return [...seen.values()];
|
|
67885
|
+
}
|
|
67886
|
+
function topologyToJson(byQueue) {
|
|
67887
|
+
return [...byQueue.entries()].map(([queueName, edges]) => ({
|
|
67888
|
+
queueName,
|
|
67889
|
+
producers: endpoints(edges, "producerSymbol", "producerFile"),
|
|
67890
|
+
workers: endpoints(edges, "workerSymbol", "workerFile")
|
|
67891
|
+
}));
|
|
67892
|
+
}
|
|
67893
|
+
async function gatherLiveState(config, rows, nameFilter) {
|
|
67894
|
+
let renv;
|
|
67895
|
+
try {
|
|
67896
|
+
renv = resolveEnvironment(config);
|
|
67897
|
+
} catch (err) {
|
|
67898
|
+
return { ok: false, error: err.message };
|
|
67899
|
+
}
|
|
67900
|
+
const queueProvider = queueForEnv(renv);
|
|
67901
|
+
if (!queueProvider) return { ok: false, error: "Redis not configured" };
|
|
67902
|
+
try {
|
|
67903
|
+
const health = await queueProvider.health();
|
|
67904
|
+
if (!health.ok) return { ok: false, error: health.detail };
|
|
67905
|
+
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
67906
|
+
let queueNames;
|
|
67907
|
+
if (nameFilter !== void 0) {
|
|
67908
|
+
queueNames = [nameFilter];
|
|
67909
|
+
} else {
|
|
67910
|
+
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
67911
|
+
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
67912
|
+
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
67913
|
+
}
|
|
67914
|
+
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
67915
|
+
return {
|
|
67916
|
+
ok: true,
|
|
67917
|
+
prefix: state.prefix,
|
|
67918
|
+
collectedAt: state.collectedAt,
|
|
67919
|
+
queues: state.queues.map((q) => ({ ...q, runtimeOnly: !staticNames.has(q.queueName) }))
|
|
67920
|
+
};
|
|
67921
|
+
} finally {
|
|
67922
|
+
await queueProvider.close().catch(() => {
|
|
67923
|
+
});
|
|
67924
|
+
}
|
|
67925
|
+
}
|
|
67840
67926
|
function printTopology(byQueue) {
|
|
67841
67927
|
for (const [queueName, edges] of byQueue) {
|
|
67842
67928
|
console.log(import_picocolors4.default.bold(queueName));
|
|
@@ -70237,8 +70323,10 @@ async function investigate(input, deps) {
|
|
|
70237
70323
|
const latencyMetricEvIds = [];
|
|
70238
70324
|
const queueMetricEvIds = [];
|
|
70239
70325
|
const queueMetricEvIdsByQueue = /* @__PURE__ */ new Map();
|
|
70326
|
+
const nominalMetricEvIds = [];
|
|
70240
70327
|
let metricsCollected = false;
|
|
70241
70328
|
let metricsFailureReason;
|
|
70329
|
+
let metricSeriesChecked = 0;
|
|
70242
70330
|
if (deps.metrics) {
|
|
70243
70331
|
const ac = new AbortController();
|
|
70244
70332
|
let metricsTimerId;
|
|
@@ -70298,6 +70386,19 @@ async function investigate(input, deps) {
|
|
|
70298
70386
|
}
|
|
70299
70387
|
}
|
|
70300
70388
|
}
|
|
70389
|
+
metricSeriesChecked = mFindings.length;
|
|
70390
|
+
if (metricEvIds.length === 0 && mFindings.length > 0) {
|
|
70391
|
+
const panels = [...new Set(mFindings.map((f) => f.panelTitle))];
|
|
70392
|
+
const ev = mkEv(
|
|
70393
|
+
"metric",
|
|
70394
|
+
`Metrics checked \u2014 ${mFindings.length} series across ${panels.length} panel(s), no anomalies in window`,
|
|
70395
|
+
{ seriesChecked: mFindings.length, panelCount: panels.length, panels: panels.slice(0, 10), anomalies: 0, stance: "neutral" },
|
|
70396
|
+
{},
|
|
70397
|
+
collectedAt2,
|
|
70398
|
+
0.2
|
|
70399
|
+
);
|
|
70400
|
+
nominalMetricEvIds.push(ev.id);
|
|
70401
|
+
}
|
|
70301
70402
|
metricsCollected = true;
|
|
70302
70403
|
} catch (metricsErr) {
|
|
70303
70404
|
metricsFailureReason = metricsErr?.message?.slice(0, 120) ?? "unknown error";
|
|
@@ -70495,6 +70596,13 @@ async function investigate(input, deps) {
|
|
|
70495
70596
|
confidence: 0.7,
|
|
70496
70597
|
evidenceIds: metricEvIds
|
|
70497
70598
|
});
|
|
70599
|
+
} else if (nominalMetricEvIds.length > 0) {
|
|
70600
|
+
findings2.push({
|
|
70601
|
+
kind: "observation",
|
|
70602
|
+
title: `Metrics nominal \u2014 ${metricSeriesChecked} series checked, no anomalies in window`,
|
|
70603
|
+
confidence: 0.5,
|
|
70604
|
+
evidenceIds: nominalMetricEvIds
|
|
70605
|
+
});
|
|
70498
70606
|
}
|
|
70499
70607
|
const causeInputs = [];
|
|
70500
70608
|
const blastRadius = impact.affected;
|
|
@@ -71546,7 +71654,7 @@ async function discoverArchitecture(deps) {
|
|
|
71546
71654
|
})();
|
|
71547
71655
|
const asyncBoundaries = await (async () => {
|
|
71548
71656
|
try {
|
|
71549
|
-
const edges = await listQueueEdges(deps.db);
|
|
71657
|
+
const edges = await listQueueEdges(deps.db, { project: deps.project });
|
|
71550
71658
|
const byQueue = /* @__PURE__ */ new Map();
|
|
71551
71659
|
for (const edge of edges) {
|
|
71552
71660
|
const key = edge.queueName;
|
|
@@ -71710,7 +71818,7 @@ async function analyzeBlastRadius(query, deps, depth = 3) {
|
|
|
71710
71818
|
]);
|
|
71711
71819
|
const upstream = ctx.callees;
|
|
71712
71820
|
const downstream = impact.byDepth;
|
|
71713
|
-
const edges = await listQueueEdges(deps.db);
|
|
71821
|
+
const edges = await listQueueEdges(deps.db, { project: deps.project });
|
|
71714
71822
|
const asyncDownstreamMap = /* @__PURE__ */ new Map();
|
|
71715
71823
|
for (const edge of edges) {
|
|
71716
71824
|
if (edge.producerFile === top.filePath || edge.producerSymbol === top.name) {
|
|
@@ -72551,6 +72659,167 @@ function refinedToJSON(r, v) {
|
|
|
72551
72659
|
);
|
|
72552
72660
|
}
|
|
72553
72661
|
|
|
72662
|
+
// ../../packages/engine/src/qa.ts
|
|
72663
|
+
init_cjs_shims();
|
|
72664
|
+
var CONTRADICTS_RE = /\b(contradict|contradicts|argues?\s+against|evidence\s+against|rule\s+out|disprove|refute|weaken)\b/i;
|
|
72665
|
+
var MISSING_RE = /\b(missing|absent|gaps?|don'?t\s+have|do\s+not\s+have|lack(?:ing)?|what\s+else)\b/i;
|
|
72666
|
+
var CONFIDENCE_RE = /\b(confidence|confident|certainty|sure|why\s+(?:is\s+)?(?:it\s+)?not\s+higher)\b/i;
|
|
72667
|
+
function detectQuestion(text2) {
|
|
72668
|
+
const t = text2.toLowerCase();
|
|
72669
|
+
if (CONFIDENCE_RE.test(t)) return "confidence";
|
|
72670
|
+
if (CONTRADICTS_RE.test(t)) return "contradicts";
|
|
72671
|
+
if (MISSING_RE.test(t)) return "missing-evidence";
|
|
72672
|
+
return null;
|
|
72673
|
+
}
|
|
72674
|
+
function categoriesForTopic(text2) {
|
|
72675
|
+
const t = text2.toLowerCase();
|
|
72676
|
+
for (const [topic, entry2] of Object.entries(TOPIC_MAP)) {
|
|
72677
|
+
if (topic === t.trim()) return { topic, categories: entry2.categories };
|
|
72678
|
+
if (entry2.keywords.some((kw) => new RegExp(`\\b${kw}\\b`).test(t))) {
|
|
72679
|
+
return { topic, categories: entry2.categories };
|
|
72680
|
+
}
|
|
72681
|
+
}
|
|
72682
|
+
return null;
|
|
72683
|
+
}
|
|
72684
|
+
function evidenceById(report) {
|
|
72685
|
+
return new Map(report.evidence.map((e) => [e.id, e]));
|
|
72686
|
+
}
|
|
72687
|
+
function answerContradicts(report, question) {
|
|
72688
|
+
const matched = categoriesForTopic(question);
|
|
72689
|
+
const byId = evidenceById(report);
|
|
72690
|
+
const hyps = matched ? report.hypotheses.filter((h) => matched.categories.includes(h.category)) : [];
|
|
72691
|
+
if (matched && hyps.length === 0) {
|
|
72692
|
+
const evaluated = [...new Set(report.hypotheses.map((h) => h.category))];
|
|
72693
|
+
return {
|
|
72694
|
+
question,
|
|
72695
|
+
kind: "contradicts",
|
|
72696
|
+
headline: `"${matched.topic}" was not among the evaluated hypotheses \u2014 no evidence supports it.`,
|
|
72697
|
+
details: evaluated.length > 0 ? [`Hypotheses evaluated: ${evaluated.join(", ")}.`] : ["No hypotheses were formed for this investigation."],
|
|
72698
|
+
evidence: []
|
|
72699
|
+
};
|
|
72700
|
+
}
|
|
72701
|
+
if (hyps.length === 0) {
|
|
72702
|
+
const ids3 = [...new Set(report.hypotheses.flatMap((h) => h.contradictingEvidenceIds))];
|
|
72703
|
+
const ev2 = ids3.map((id) => byId.get(id)).filter((e) => e !== void 0);
|
|
72704
|
+
return {
|
|
72705
|
+
question,
|
|
72706
|
+
kind: "contradicts",
|
|
72707
|
+
headline: ev2.length > 0 ? `${ev2.length} item(s) contradict the leading hypotheses.` : "No contradicting evidence was recorded for any hypothesis.",
|
|
72708
|
+
details: ev2.length > 0 ? [] : ["Evidence either supports or is neutral to the hypotheses."],
|
|
72709
|
+
evidence: ev2
|
|
72710
|
+
};
|
|
72711
|
+
}
|
|
72712
|
+
const ids2 = [...new Set(hyps.flatMap((h) => h.contradictingEvidenceIds))];
|
|
72713
|
+
const ev = ids2.map((id) => byId.get(id)).filter((e) => e !== void 0);
|
|
72714
|
+
const verdicts = [...new Set(hyps.map((h) => h.verdict))].join(", ");
|
|
72715
|
+
if (ev.length === 0) {
|
|
72716
|
+
return {
|
|
72717
|
+
question,
|
|
72718
|
+
kind: "contradicts",
|
|
72719
|
+
headline: `No evidence contradicts "${matched?.topic ?? "this"}" (verdict: ${verdicts}).`,
|
|
72720
|
+
details: hyps.map((h) => `${h.category}: ${h.rationale ?? h.statement}`),
|
|
72721
|
+
evidence: []
|
|
72722
|
+
};
|
|
72723
|
+
}
|
|
72724
|
+
return {
|
|
72725
|
+
question,
|
|
72726
|
+
kind: "contradicts",
|
|
72727
|
+
headline: `${ev.length} item(s) contradict "${matched?.topic ?? "this"}" (verdict: ${verdicts}).`,
|
|
72728
|
+
details: hyps.map((h) => `${h.category}: ${h.rationale ?? h.statement}`),
|
|
72729
|
+
evidence: ev
|
|
72730
|
+
};
|
|
72731
|
+
}
|
|
72732
|
+
function answerMissing(report, question) {
|
|
72733
|
+
const { gaps, blindSpots } = report.gapAnalysis;
|
|
72734
|
+
const hypMissing = [...new Set(report.hypotheses.flatMap((h) => h.missingEvidence))];
|
|
72735
|
+
if (gaps.length === 0 && hypMissing.length === 0) {
|
|
72736
|
+
return {
|
|
72737
|
+
question,
|
|
72738
|
+
kind: "missing-evidence",
|
|
72739
|
+
headline: "No evidence gaps \u2014 all expected dimensions were collected.",
|
|
72740
|
+
details: [],
|
|
72741
|
+
evidence: []
|
|
72742
|
+
};
|
|
72743
|
+
}
|
|
72744
|
+
const details = gaps.map(
|
|
72745
|
+
(g) => `${g.dimension}: ${g.why} \u2192 ${g.nextSource} (\u2212${(g.confidenceImpact * 100).toFixed(0)}% ceiling)`
|
|
72746
|
+
);
|
|
72747
|
+
if (hypMissing.length > 0) {
|
|
72748
|
+
details.push(`To confirm hypotheses: ${hypMissing.join("; ")}.`);
|
|
72749
|
+
}
|
|
72750
|
+
for (const bs of blindSpots) details.push(`Blind spot: ${bs}`);
|
|
72751
|
+
return {
|
|
72752
|
+
question,
|
|
72753
|
+
kind: "missing-evidence",
|
|
72754
|
+
headline: `${gaps.length} evidence gap(s) limit this investigation.`,
|
|
72755
|
+
details,
|
|
72756
|
+
evidence: []
|
|
72757
|
+
};
|
|
72758
|
+
}
|
|
72759
|
+
function answerConfidence(report, question) {
|
|
72760
|
+
const { gaps, confidenceCeiling } = report.gapAnalysis;
|
|
72761
|
+
const ceilingPct = Math.round(confidenceCeiling * 100);
|
|
72762
|
+
const actualPct = Math.round(report.confidence * 100);
|
|
72763
|
+
const limiting = [...gaps].sort((a, b2) => b2.confidenceImpact - a.confidenceImpact);
|
|
72764
|
+
const details = [];
|
|
72765
|
+
if (limiting.length > 0) {
|
|
72766
|
+
details.push(`Confidence is capped at ${ceilingPct}% by missing evidence:`);
|
|
72767
|
+
for (const g of limiting) {
|
|
72768
|
+
details.push(` \u2022 ${g.dimension} (\u2212${(g.confidenceImpact * 100).toFixed(0)}%): ${g.why}`);
|
|
72769
|
+
}
|
|
72770
|
+
} else {
|
|
72771
|
+
details.push("No evidence gaps cap the ceiling.");
|
|
72772
|
+
}
|
|
72773
|
+
const weak = report.hypotheses.filter((h) => h.verdict === "weakened" || h.verdict === "unconfirmed");
|
|
72774
|
+
if (weak.length > 0) {
|
|
72775
|
+
details.push(
|
|
72776
|
+
`Unconfirmed/weakened hypotheses: ${weak.map((h) => `${h.category} (${h.verdict})`).join(", ")}.`
|
|
72777
|
+
);
|
|
72778
|
+
}
|
|
72779
|
+
return {
|
|
72780
|
+
question,
|
|
72781
|
+
kind: "confidence",
|
|
72782
|
+
headline: `Confidence is ${actualPct}% (ceiling ${ceilingPct}%). ${limiting.length > 0 ? "Missing evidence is the main limiter." : "Limited by hypothesis support, not gaps."}`,
|
|
72783
|
+
details,
|
|
72784
|
+
evidence: []
|
|
72785
|
+
};
|
|
72786
|
+
}
|
|
72787
|
+
function answerQuestion(report, question) {
|
|
72788
|
+
const kind = detectQuestion(question);
|
|
72789
|
+
if (kind === null) return null;
|
|
72790
|
+
if (kind === "contradicts") return answerContradicts(report, question);
|
|
72791
|
+
if (kind === "missing-evidence") return answerMissing(report, question);
|
|
72792
|
+
return answerConfidence(report, question);
|
|
72793
|
+
}
|
|
72794
|
+
function renderQAAnswer(a) {
|
|
72795
|
+
const lines = [];
|
|
72796
|
+
lines.push(`Q: ${a.question}`);
|
|
72797
|
+
lines.push("");
|
|
72798
|
+
lines.push(a.headline);
|
|
72799
|
+
for (const d of a.details) lines.push(d.startsWith(" ") ? d : ` ${d}`);
|
|
72800
|
+
if (a.evidence.length > 0) {
|
|
72801
|
+
lines.push("");
|
|
72802
|
+
lines.push("Evidence:");
|
|
72803
|
+
for (const e of a.evidence) {
|
|
72804
|
+
lines.push(` [${e.id.slice(0, 8)}] (${e.kind}) ${e.title}`);
|
|
72805
|
+
}
|
|
72806
|
+
}
|
|
72807
|
+
return lines.join("\n");
|
|
72808
|
+
}
|
|
72809
|
+
function qaToJSON(a) {
|
|
72810
|
+
return JSON.stringify(
|
|
72811
|
+
{
|
|
72812
|
+
question: a.question,
|
|
72813
|
+
kind: a.kind,
|
|
72814
|
+
headline: a.headline,
|
|
72815
|
+
details: a.details,
|
|
72816
|
+
evidence: a.evidence.map((e) => ({ id: e.id, kind: e.kind, title: e.title }))
|
|
72817
|
+
},
|
|
72818
|
+
null,
|
|
72819
|
+
2
|
|
72820
|
+
);
|
|
72821
|
+
}
|
|
72822
|
+
|
|
72554
72823
|
// ../../packages/engine/src/onboard.ts
|
|
72555
72824
|
init_cjs_shims();
|
|
72556
72825
|
function tokenize2(text2) {
|
|
@@ -72667,7 +72936,11 @@ function filterArchitecture(architecture, tokens) {
|
|
|
72667
72936
|
};
|
|
72668
72937
|
}
|
|
72669
72938
|
async function buildOnboarding(input, deps) {
|
|
72670
|
-
const architecture = await discoverArchitecture({
|
|
72939
|
+
const architecture = await discoverArchitecture({
|
|
72940
|
+
code: deps.code,
|
|
72941
|
+
db: deps.db,
|
|
72942
|
+
project: deps.project
|
|
72943
|
+
});
|
|
72671
72944
|
const area = input.area?.trim();
|
|
72672
72945
|
let filteredArchitecture = architecture;
|
|
72673
72946
|
let pastIncidents = [];
|
|
@@ -73394,18 +73667,76 @@ Hard rules:
|
|
|
73394
73667
|
- only include services from: ${input.knownServices.join(", ")}
|
|
73395
73668
|
- hypothesisJudgments must only reference hypothesis IDs from the hypotheses list above
|
|
73396
73669
|
- verdict must be exactly one of: supported, weakened, eliminated, unconfirmed
|
|
73397
|
-
- uncertainty must be exactly one of: low, medium, high
|
|
73670
|
+
- uncertainty must be exactly one of: low, medium, high
|
|
73671
|
+
- output raw JSON only: no markdown code fences, no text before or after the JSON`;
|
|
73398
73672
|
}
|
|
73399
|
-
function
|
|
73400
|
-
|
|
73401
|
-
|
|
73402
|
-
|
|
73403
|
-
|
|
73404
|
-
|
|
73405
|
-
|
|
73406
|
-
|
|
73673
|
+
function sliceBalancedObjects(s) {
|
|
73674
|
+
const out = [];
|
|
73675
|
+
let depth = 0;
|
|
73676
|
+
let start = -1;
|
|
73677
|
+
let inStr = false;
|
|
73678
|
+
let escaped = false;
|
|
73679
|
+
for (let i = 0; i < s.length; i++) {
|
|
73680
|
+
const ch = s[i];
|
|
73681
|
+
if (inStr) {
|
|
73682
|
+
if (escaped) escaped = false;
|
|
73683
|
+
else if (ch === "\\") escaped = true;
|
|
73684
|
+
else if (ch === '"') inStr = false;
|
|
73685
|
+
continue;
|
|
73686
|
+
}
|
|
73687
|
+
if (ch === '"') inStr = true;
|
|
73688
|
+
else if (ch === "{") {
|
|
73689
|
+
if (depth === 0) start = i;
|
|
73690
|
+
depth++;
|
|
73691
|
+
} else if (ch === "}") {
|
|
73692
|
+
if (depth > 0) {
|
|
73693
|
+
depth--;
|
|
73694
|
+
if (depth === 0 && start !== -1) out.push(s.slice(start, i + 1));
|
|
73695
|
+
}
|
|
73407
73696
|
}
|
|
73408
|
-
|
|
73697
|
+
}
|
|
73698
|
+
return out.sort((a, b2) => b2.length - a.length);
|
|
73699
|
+
}
|
|
73700
|
+
function stripTrailingCommas(s) {
|
|
73701
|
+
return s.replace(/,(\s*[}\]])/g, "$1");
|
|
73702
|
+
}
|
|
73703
|
+
function extractJson(raw) {
|
|
73704
|
+
const candidates = [];
|
|
73705
|
+
const trimmed = raw.trim();
|
|
73706
|
+
candidates.push(trimmed);
|
|
73707
|
+
const fence = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
73708
|
+
if (fence?.[1]) candidates.push(fence[1].trim());
|
|
73709
|
+
candidates.push(...sliceBalancedObjects(trimmed));
|
|
73710
|
+
for (const c of candidates) {
|
|
73711
|
+
for (const variant of [c, stripTrailingCommas(c)]) {
|
|
73712
|
+
try {
|
|
73713
|
+
const parsed = JSON.parse(variant);
|
|
73714
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
73715
|
+
return parsed;
|
|
73716
|
+
}
|
|
73717
|
+
} catch {
|
|
73718
|
+
}
|
|
73719
|
+
}
|
|
73720
|
+
}
|
|
73721
|
+
return null;
|
|
73722
|
+
}
|
|
73723
|
+
function rawNarrativeFallback(raw, input, ceiling) {
|
|
73724
|
+
const cleaned = raw.replace(/```(?:json)?/gi, "").trim();
|
|
73725
|
+
return {
|
|
73726
|
+
what: input.deterministicSummary || "AI returned an unstructured response.",
|
|
73727
|
+
why: cleaned.slice(0, 4e3),
|
|
73728
|
+
whereNext: [],
|
|
73729
|
+
citations: [],
|
|
73730
|
+
confidence: Math.min(input.reportConfidence, ceiling)
|
|
73731
|
+
};
|
|
73732
|
+
}
|
|
73733
|
+
function parseOutput(raw, input, ceiling) {
|
|
73734
|
+
const parsed = extractJson(raw);
|
|
73735
|
+
if (parsed === null) {
|
|
73736
|
+
console.error(
|
|
73737
|
+
`[ai] structured JSON parse failed (response length ${raw.length}); preserving raw narrative fallback`
|
|
73738
|
+
);
|
|
73739
|
+
return rawNarrativeFallback(raw, input, ceiling);
|
|
73409
73740
|
}
|
|
73410
73741
|
const confidence = Math.min(
|
|
73411
73742
|
typeof parsed["confidence"] === "number" ? parsed["confidence"] : input.reportConfidence,
|
|
@@ -73460,6 +73791,13 @@ init_cjs_shims();
|
|
|
73460
73791
|
|
|
73461
73792
|
// ../../packages/ai/src/local-providers.ts
|
|
73462
73793
|
init_cjs_shims();
|
|
73794
|
+
var LOCAL_PROVIDER_IDS = [
|
|
73795
|
+
"codex",
|
|
73796
|
+
"claude",
|
|
73797
|
+
"kimi",
|
|
73798
|
+
"gemini",
|
|
73799
|
+
"cursor"
|
|
73800
|
+
];
|
|
73463
73801
|
var DEFAULT_DESCRIPTORS = [
|
|
73464
73802
|
{ id: "codex", displayName: "OpenAI Codex CLI" },
|
|
73465
73803
|
{ id: "claude", displayName: "Anthropic Claude" },
|
|
@@ -73658,8 +73996,12 @@ async function runInvestigate(hint, opts) {
|
|
|
73658
73996
|
const rendered = format === "json" ? reportToJSON(report) : format === "markdown" || format === "md" ? reportToMarkdown(report) : renderReport2(report);
|
|
73659
73997
|
console.log(rendered);
|
|
73660
73998
|
if (opts.ai && format !== "json") {
|
|
73661
|
-
const
|
|
73662
|
-
const
|
|
73999
|
+
const ai = resolveAiSettings(config);
|
|
74000
|
+
const model = opts.aiModel ?? ai.model ?? "claude-opus-4-8";
|
|
74001
|
+
const provider = opts._aiProvider ?? new AnthropicNarrativeProvider({
|
|
74002
|
+
model,
|
|
74003
|
+
...ai.anthropicApiKey !== void 0 ? { apiKey: ai.anthropicApiKey } : {}
|
|
74004
|
+
});
|
|
73663
74005
|
console.log(import_picocolors5.default.dim(`[ai] model: ${model}`));
|
|
73664
74006
|
const narrativeInput = buildNarrativeInput(report);
|
|
73665
74007
|
const { output, fromProvider, validationErrors } = await renderNarrative(narrativeInput, { provider });
|
|
@@ -73714,6 +74056,12 @@ async function runChanges(base2, compare, opts) {
|
|
|
73714
74056
|
// ../../packages/cli/src/commands/timeline.ts
|
|
73715
74057
|
init_cjs_shims();
|
|
73716
74058
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
74059
|
+
var DEFAULT_SINCE = "7 days ago";
|
|
74060
|
+
function resolveTimelineWindow(opts) {
|
|
74061
|
+
const usingDefault = !opts.all && opts.since === void 0;
|
|
74062
|
+
const since = opts.all ? void 0 : opts.since ?? DEFAULT_SINCE;
|
|
74063
|
+
return { since, usingDefault };
|
|
74064
|
+
}
|
|
73717
74065
|
async function runTimeline(service, opts) {
|
|
73718
74066
|
try {
|
|
73719
74067
|
const config = await loadConfig(opts.config);
|
|
@@ -73725,11 +74073,24 @@ async function runTimeline(service, opts) {
|
|
|
73725
74073
|
return 1;
|
|
73726
74074
|
}
|
|
73727
74075
|
const { code } = createConnectors(config);
|
|
74076
|
+
const { since, usingDefault: usingDefaultWindow } = resolveTimelineWindow(opts);
|
|
73728
74077
|
const t = await reconstructChangeTimeline(
|
|
73729
|
-
{ repoPath: renv.path, since
|
|
74078
|
+
{ repoPath: renv.path, since, until: opts.until, service },
|
|
73730
74079
|
{ code }
|
|
73731
74080
|
);
|
|
73732
|
-
|
|
74081
|
+
if (opts.json) {
|
|
74082
|
+
console.log(changeTimelineToJSON(t));
|
|
74083
|
+
} else {
|
|
74084
|
+
console.log(renderChangeTimeline(t));
|
|
74085
|
+
if (usingDefaultWindow) {
|
|
74086
|
+
console.log(
|
|
74087
|
+
import_picocolors7.default.dim(
|
|
74088
|
+
`
|
|
74089
|
+
Showing the last 7 days (default). Widen with ${import_picocolors7.default.bold('--since "30 days ago"')}, pin a range with ${import_picocolors7.default.bold("--since <when> --until <when>")}, or see everything with ${import_picocolors7.default.bold("--all")}.`
|
|
74090
|
+
)
|
|
74091
|
+
);
|
|
74092
|
+
}
|
|
74093
|
+
}
|
|
73733
74094
|
return 0;
|
|
73734
74095
|
} catch (err) {
|
|
73735
74096
|
console.error(import_picocolors7.default.red(err.message));
|
|
@@ -73740,7 +74101,7 @@ async function runTimeline(service, opts) {
|
|
|
73740
74101
|
// ../../packages/cli/src/commands/what-changed.ts
|
|
73741
74102
|
init_cjs_shims();
|
|
73742
74103
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
73743
|
-
var
|
|
74104
|
+
var DEFAULT_SINCE2 = "7 days ago";
|
|
73744
74105
|
async function runWhatChanged(service, opts) {
|
|
73745
74106
|
try {
|
|
73746
74107
|
const config = await loadConfig(opts.config);
|
|
@@ -73752,7 +74113,7 @@ async function runWhatChanged(service, opts) {
|
|
|
73752
74113
|
return 1;
|
|
73753
74114
|
}
|
|
73754
74115
|
const { code } = createConnectors(config);
|
|
73755
|
-
const since = opts.since ??
|
|
74116
|
+
const since = opts.since ?? DEFAULT_SINCE2;
|
|
73756
74117
|
const r = await whatChanged(
|
|
73757
74118
|
{ repoPath: renv.path, since, until: opts.until, service },
|
|
73758
74119
|
{ code }
|
|
@@ -73779,9 +74140,14 @@ async function runArchitecture(opts) {
|
|
|
73779
74140
|
);
|
|
73780
74141
|
return 1;
|
|
73781
74142
|
}
|
|
74143
|
+
let project;
|
|
74144
|
+
try {
|
|
74145
|
+
project = resolveEnvironment(config, { project: opts.repo }).project;
|
|
74146
|
+
} catch {
|
|
74147
|
+
}
|
|
73782
74148
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
73783
74149
|
try {
|
|
73784
|
-
const m = await discoverArchitecture({ code, db });
|
|
74150
|
+
const m = await discoverArchitecture({ code, db, project });
|
|
73785
74151
|
console.log(opts.json ? architectureToJSON(m) : renderArchitecture(m));
|
|
73786
74152
|
} finally {
|
|
73787
74153
|
await sql2.end();
|
|
@@ -73805,9 +74171,14 @@ async function runBlastRadius(query, opts) {
|
|
|
73805
74171
|
console.error(import_picocolors10.default.red("Source-intelligence host unreachable \u2014 run: horus index"));
|
|
73806
74172
|
return 1;
|
|
73807
74173
|
}
|
|
74174
|
+
let project;
|
|
74175
|
+
try {
|
|
74176
|
+
project = resolveEnvironment(config, { project: opts.repo }).project;
|
|
74177
|
+
} catch {
|
|
74178
|
+
}
|
|
73808
74179
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
73809
74180
|
try {
|
|
73810
|
-
const r = await analyzeBlastRadius(query, { code, db }, opts.depth ?? 3);
|
|
74181
|
+
const r = await analyzeBlastRadius(query, { code, db, project }, opts.depth ?? 3);
|
|
73811
74182
|
if (!r) {
|
|
73812
74183
|
console.log(`No symbol found for: ${query}`);
|
|
73813
74184
|
console.log(import_picocolors10.default.dim(` Tip: use an exact class or function name, e.g. "MyService"`));
|
|
@@ -74213,6 +74584,11 @@ async function runAsk(id, directive, opts) {
|
|
|
74213
74584
|
return 1;
|
|
74214
74585
|
}
|
|
74215
74586
|
const report = migrateReport(row.report);
|
|
74587
|
+
const answer = answerQuestion(report, directive);
|
|
74588
|
+
if (answer) {
|
|
74589
|
+
console.log(opts.json ? qaToJSON(answer) : renderQAAnswer(answer));
|
|
74590
|
+
return 0;
|
|
74591
|
+
}
|
|
74216
74592
|
const v = refineInvestigation(report, directive);
|
|
74217
74593
|
console.log(opts.json ? refinedToJSON(report, v) : renderRefined(report, v));
|
|
74218
74594
|
if (!opts.json && report.aiJudgment) {
|
|
@@ -74265,7 +74641,10 @@ async function runOnboard(area, opts) {
|
|
|
74265
74641
|
}
|
|
74266
74642
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
74267
74643
|
try {
|
|
74268
|
-
const g = await buildOnboarding(
|
|
74644
|
+
const g = await buildOnboarding(
|
|
74645
|
+
{ area },
|
|
74646
|
+
{ code, db, repoPath: repo.path, project: renv.project }
|
|
74647
|
+
);
|
|
74269
74648
|
console.log(opts.json ? onboardingToJSON(g) : renderOnboarding(g));
|
|
74270
74649
|
} finally {
|
|
74271
74650
|
await sql2.end();
|
|
@@ -74341,6 +74720,10 @@ function sinceToIso(since) {
|
|
|
74341
74720
|
const msMap = { m: 6e4, h: 36e5, d: 864e5 };
|
|
74342
74721
|
return new Date(Date.now() - (msMap[unit] ?? 6e4) * amount).toISOString();
|
|
74343
74722
|
}
|
|
74723
|
+
function resolveRawLevel(opts) {
|
|
74724
|
+
if (opts.allLevels) return void 0;
|
|
74725
|
+
return opts.level ?? "error";
|
|
74726
|
+
}
|
|
74344
74727
|
function levelColor(level, text2) {
|
|
74345
74728
|
if (level === "error" || level === "fatal") return import_picocolors20.default.red(text2);
|
|
74346
74729
|
if (level === "warn") return import_picocolors20.default.yellow(text2);
|
|
@@ -74400,13 +74783,45 @@ async function runLogs(service, opts) {
|
|
|
74400
74783
|
const from = sinceToIso(opts.since) ?? new Date(Date.now() - 7 * 864e5).toISOString();
|
|
74401
74784
|
const fromDisplay = from.slice(0, 16).replace("T", " ");
|
|
74402
74785
|
if (opts.raw === true) {
|
|
74786
|
+
const rawLevel = resolveRawLevel(opts);
|
|
74787
|
+
const limit = opts.limit !== void 0 ? Math.min(Number(opts.limit), 1e3) : 20;
|
|
74403
74788
|
const records = await logs.searchLogs({
|
|
74404
74789
|
service: resolvedService,
|
|
74405
74790
|
from,
|
|
74406
|
-
level:
|
|
74791
|
+
level: rawLevel,
|
|
74407
74792
|
text: opts.grep,
|
|
74408
|
-
limit
|
|
74793
|
+
limit
|
|
74409
74794
|
});
|
|
74795
|
+
if (opts.json) {
|
|
74796
|
+
console.log(
|
|
74797
|
+
JSON.stringify(
|
|
74798
|
+
{
|
|
74799
|
+
scope: {
|
|
74800
|
+
project: renv.project,
|
|
74801
|
+
env: renv.env,
|
|
74802
|
+
service: resolvedService ?? null,
|
|
74803
|
+
index: indexPattern,
|
|
74804
|
+
from,
|
|
74805
|
+
level: rawLevel ?? "all",
|
|
74806
|
+
grep: opts.grep ?? null
|
|
74807
|
+
},
|
|
74808
|
+
records: records.map((r) => ({
|
|
74809
|
+
timestamp: r.timestamp,
|
|
74810
|
+
level: r.level,
|
|
74811
|
+
service: r.service ?? null,
|
|
74812
|
+
component: r.component ?? null,
|
|
74813
|
+
eventCode: r.eventCode ?? null,
|
|
74814
|
+
message: r.message,
|
|
74815
|
+
traceId: r.traceId ?? null,
|
|
74816
|
+
requestId: r.requestId ?? null
|
|
74817
|
+
}))
|
|
74818
|
+
},
|
|
74819
|
+
null,
|
|
74820
|
+
2
|
|
74821
|
+
)
|
|
74822
|
+
);
|
|
74823
|
+
return 0;
|
|
74824
|
+
}
|
|
74410
74825
|
if (records.length === 0) {
|
|
74411
74826
|
console.log(import_picocolors20.default.dim("No logs matched."));
|
|
74412
74827
|
return 0;
|
|
@@ -74435,6 +74850,30 @@ async function runLogs(service, opts) {
|
|
|
74435
74850
|
scopeService = void 0;
|
|
74436
74851
|
}
|
|
74437
74852
|
}
|
|
74853
|
+
if (opts.json) {
|
|
74854
|
+
console.log(
|
|
74855
|
+
JSON.stringify(
|
|
74856
|
+
{
|
|
74857
|
+
scope: {
|
|
74858
|
+
project: renv.project,
|
|
74859
|
+
env: renv.env,
|
|
74860
|
+
service: scopeService ?? null,
|
|
74861
|
+
index: indexPattern,
|
|
74862
|
+
from,
|
|
74863
|
+
severity: "error+",
|
|
74864
|
+
grep: opts.grep ?? null
|
|
74865
|
+
},
|
|
74866
|
+
totalErrors: analysis.totalErrors,
|
|
74867
|
+
signatures: analysis.signatures,
|
|
74868
|
+
newSignatures: analysis.newSignatures,
|
|
74869
|
+
affectedServices: analysis.affectedServices
|
|
74870
|
+
},
|
|
74871
|
+
null,
|
|
74872
|
+
2
|
|
74873
|
+
)
|
|
74874
|
+
);
|
|
74875
|
+
return 0;
|
|
74876
|
+
}
|
|
74438
74877
|
console.log(
|
|
74439
74878
|
import_picocolors20.default.bold(`Error analysis`) + import_picocolors20.default.dim(
|
|
74440
74879
|
` \u2014 ${renv.project}/${renv.env}` + (scopeService ? ` \xB7 service ${scopeService}` : "") + (opts.grep ? ` \xB7 grep "${opts.grep}"` : "")
|
|
@@ -74559,6 +74998,25 @@ async function runMetrics(hint, opts) {
|
|
|
74559
74998
|
const from = to - dur;
|
|
74560
74999
|
if (opts.query !== void 0 && opts.query !== "") {
|
|
74561
75000
|
const series = await metrics.rawRange(opts.query, from, to, stepNum);
|
|
75001
|
+
if (opts.json === true) {
|
|
75002
|
+
console.log(
|
|
75003
|
+
JSON.stringify(
|
|
75004
|
+
{
|
|
75005
|
+
query: opts.query,
|
|
75006
|
+
from,
|
|
75007
|
+
to,
|
|
75008
|
+
step: stepNum,
|
|
75009
|
+
series: series.map((s) => {
|
|
75010
|
+
const summary = summarize(s);
|
|
75011
|
+
return { labels: s.labels, last: summary.last, avg: summary.avg };
|
|
75012
|
+
})
|
|
75013
|
+
},
|
|
75014
|
+
null,
|
|
75015
|
+
2
|
|
75016
|
+
)
|
|
75017
|
+
);
|
|
75018
|
+
return 0;
|
|
75019
|
+
}
|
|
74562
75020
|
if (series.length === 0) {
|
|
74563
75021
|
console.log(import_picocolors21.default.dim("No series returned."));
|
|
74564
75022
|
return 0;
|
|
@@ -74669,6 +75127,23 @@ async function runState(opts) {
|
|
|
74669
75127
|
const analysis = await mongo.analyzeState(
|
|
74670
75128
|
staleHours !== void 0 ? { staleHours } : {}
|
|
74671
75129
|
);
|
|
75130
|
+
if (opts.json) {
|
|
75131
|
+
const staleSignals = analysis.collections.filter((c) => c.isStale === true).length;
|
|
75132
|
+
const anomalousSignals = analysis.collections.reduce((n, c) => n + c.anomalies.length, 0);
|
|
75133
|
+
console.log(
|
|
75134
|
+
JSON.stringify(
|
|
75135
|
+
{
|
|
75136
|
+
scope: { project: renv.project, env: renv.env, database: analysis.database },
|
|
75137
|
+
autoDiscovered: analysis.autoDiscovered,
|
|
75138
|
+
signals: { stale: staleSignals, anomalous: anomalousSignals },
|
|
75139
|
+
collections: analysis.collections
|
|
75140
|
+
},
|
|
75141
|
+
null,
|
|
75142
|
+
2
|
|
75143
|
+
)
|
|
75144
|
+
);
|
|
75145
|
+
return 0;
|
|
75146
|
+
}
|
|
74672
75147
|
const discoveryNote = analysis.autoDiscovered ? import_picocolors22.default.dim(` (${analysis.collections.length} collections, auto-discovered)`) : "";
|
|
74673
75148
|
console.log(
|
|
74674
75149
|
import_picocolors22.default.bold("State analysis") + import_picocolors22.default.dim(` \u2014 ${renv.project}/${renv.env} \xB7 db ${analysis.database}`) + discoveryNote
|
|
@@ -74914,8 +75389,8 @@ async function runSetup(opts) {
|
|
|
74914
75389
|
// ../../packages/cli/src/commands/connect.ts
|
|
74915
75390
|
init_cjs_shims();
|
|
74916
75391
|
var import_node_readline = require("readline");
|
|
74917
|
-
var
|
|
74918
|
-
var
|
|
75392
|
+
var import_node_child_process6 = require("child_process");
|
|
75393
|
+
var import_picocolors28 = __toESM(require_picocolors(), 1);
|
|
74919
75394
|
|
|
74920
75395
|
// ../../packages/cli/src/lib/tty-selector.ts
|
|
74921
75396
|
init_cjs_shims();
|
|
@@ -75095,6 +75570,122 @@ async function checkboxSearch(opts) {
|
|
|
75095
75570
|
});
|
|
75096
75571
|
}
|
|
75097
75572
|
|
|
75573
|
+
// ../../packages/cli/src/commands/connect-ai.ts
|
|
75574
|
+
init_cjs_shims();
|
|
75575
|
+
var import_node_child_process5 = require("child_process");
|
|
75576
|
+
var import_picocolors27 = __toESM(require_picocolors(), 1);
|
|
75577
|
+
function cliInstalled(id) {
|
|
75578
|
+
const r = (0, import_node_child_process5.spawnSync)(id, ["--version"], { stdio: "ignore", timeout: 2e3 });
|
|
75579
|
+
return !r.error;
|
|
75580
|
+
}
|
|
75581
|
+
async function probeAnthropic(apiKey) {
|
|
75582
|
+
try {
|
|
75583
|
+
const res = await fetch("https://api.anthropic.com/v1/models?limit=1", {
|
|
75584
|
+
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
|
|
75585
|
+
signal: AbortSignal.timeout(8e3)
|
|
75586
|
+
});
|
|
75587
|
+
if (res.ok) return { ok: true, detail: "API key valid" };
|
|
75588
|
+
if (res.status === 401) return { ok: false, detail: "invalid API key (401)" };
|
|
75589
|
+
return { ok: false, detail: `HTTP ${res.status}` };
|
|
75590
|
+
} catch (err) {
|
|
75591
|
+
return { ok: false, detail: err.message };
|
|
75592
|
+
}
|
|
75593
|
+
}
|
|
75594
|
+
function redactKey(key) {
|
|
75595
|
+
if (key.length <= 12) return "***";
|
|
75596
|
+
return `${key.slice(0, 7)}\u2026${key.slice(-4)}`;
|
|
75597
|
+
}
|
|
75598
|
+
async function runConnectAi(opts) {
|
|
75599
|
+
try {
|
|
75600
|
+
const cwd = process.cwd();
|
|
75601
|
+
const root = findRepoRoot(cwd) ?? cwd;
|
|
75602
|
+
const configPath = discoverLocalConfig(cwd) ?? localConfigPath(root);
|
|
75603
|
+
const installed = LOCAL_PROVIDER_IDS.filter((id) => cliInstalled(id));
|
|
75604
|
+
console.log(import_picocolors27.default.bold("\nAI providers"));
|
|
75605
|
+
console.log(
|
|
75606
|
+
import_picocolors27.default.dim(
|
|
75607
|
+
` Local CLIs on PATH: ${installed.length > 0 ? installed.join(", ") : "none detected"}`
|
|
75608
|
+
)
|
|
75609
|
+
);
|
|
75610
|
+
console.log(import_picocolors27.default.dim(" Cloud: anthropic (Anthropic Claude API \u2014 used by `investigate --ai`)\n"));
|
|
75611
|
+
let provider = opts.provider;
|
|
75612
|
+
if (provider === void 0) {
|
|
75613
|
+
if (isInteractive()) {
|
|
75614
|
+
const answer = (await ask("Provider", "anthropic", false)).trim().toLowerCase();
|
|
75615
|
+
provider = answer || "anthropic";
|
|
75616
|
+
} else {
|
|
75617
|
+
provider = "anthropic";
|
|
75618
|
+
}
|
|
75619
|
+
}
|
|
75620
|
+
if (!AI_PROVIDERS.includes(provider)) {
|
|
75621
|
+
console.error(import_picocolors27.default.red(`Unknown AI provider: ${provider}`) + import_picocolors27.default.dim(`
|
|
75622
|
+
supported: ${AI_PROVIDERS.join(", ")}`));
|
|
75623
|
+
return 1;
|
|
75624
|
+
}
|
|
75625
|
+
const aiBlock = { provider };
|
|
75626
|
+
let storedKey;
|
|
75627
|
+
if (provider === "anthropic") {
|
|
75628
|
+
let apiKey = opts.apiKey;
|
|
75629
|
+
if (!apiKey && isInteractive()) {
|
|
75630
|
+
apiKey = await askPassword("Anthropic API key") || void 0;
|
|
75631
|
+
}
|
|
75632
|
+
if (!apiKey) {
|
|
75633
|
+
console.error(
|
|
75634
|
+
import_picocolors27.default.red("No API key provided.") + import_picocolors27.default.dim("\n Pass --api-key sk-ant-\u2026 or set ANTHROPIC_API_KEY and re-run.")
|
|
75635
|
+
);
|
|
75636
|
+
return 1;
|
|
75637
|
+
}
|
|
75638
|
+
if (!opts.noTest) {
|
|
75639
|
+
const probe2 = await probeAnthropic(apiKey);
|
|
75640
|
+
if (!probe2.ok) {
|
|
75641
|
+
console.error(`
|
|
75642
|
+
${import_picocolors27.default.red("\u2717 Anthropic key check failed:")} ${probe2.detail}`);
|
|
75643
|
+
console.error(import_picocolors27.default.dim(" Fix the key and retry, or pass --no-test to skip."));
|
|
75644
|
+
return 1;
|
|
75645
|
+
}
|
|
75646
|
+
console.log(`
|
|
75647
|
+
${import_picocolors27.default.green("\u2713")} anthropic ${import_picocolors27.default.dim(`(${probe2.detail})`)}`);
|
|
75648
|
+
}
|
|
75649
|
+
const anthropic = { apiKey };
|
|
75650
|
+
if (opts.model) anthropic["model"] = opts.model;
|
|
75651
|
+
aiBlock["anthropic"] = anthropic;
|
|
75652
|
+
storedKey = apiKey;
|
|
75653
|
+
} else {
|
|
75654
|
+
if (!installed.includes(provider)) {
|
|
75655
|
+
console.error(
|
|
75656
|
+
import_picocolors27.default.red(`${provider} CLI not found on PATH.`) + import_picocolors27.default.dim(`
|
|
75657
|
+
Install it first, or choose anthropic.`)
|
|
75658
|
+
);
|
|
75659
|
+
return 1;
|
|
75660
|
+
}
|
|
75661
|
+
const desc2 = DEFAULT_LOCAL_PROVIDER_REGISTRY.get(provider);
|
|
75662
|
+
console.log(`
|
|
75663
|
+
${import_picocolors27.default.green("\u2713")} ${desc2?.displayName ?? provider} ${import_picocolors27.default.dim("detected on PATH")}`);
|
|
75664
|
+
console.log(
|
|
75665
|
+
import_picocolors27.default.dim(
|
|
75666
|
+
" Note: `investigate --ai` currently uses the Anthropic API for narrative.\n Local-CLI narrative generation is recorded as your preference for future use."
|
|
75667
|
+
)
|
|
75668
|
+
);
|
|
75669
|
+
}
|
|
75670
|
+
const file = discoverLocalConfig(cwd) ? readLocalConfig(configPath) : { version: 1, project: { name: root.split("/").pop() ?? "project", repositories: [{ name: root.split("/").pop() ?? "project", path: root }], environments: [{ name: "production", readOnly: true, connectors: {} }] } };
|
|
75671
|
+
file.ai = aiBlock;
|
|
75672
|
+
writeLocalConfig(root, file);
|
|
75673
|
+
if (storedKey !== void 0) {
|
|
75674
|
+
ensureCredentialGitignore(root);
|
|
75675
|
+
ensureProjectGitignore(root);
|
|
75676
|
+
}
|
|
75677
|
+
console.log(`
|
|
75678
|
+
${import_picocolors27.default.green("\u2713")} ${import_picocolors27.default.bold("ai")} provider saved \u2192 ${import_picocolors27.default.dim(configPath)}`);
|
|
75679
|
+
console.log(import_picocolors27.default.dim(` provider: ${provider}`));
|
|
75680
|
+
if (storedKey) console.log(import_picocolors27.default.dim(` anthropic key: ${redactKey(storedKey)}`));
|
|
75681
|
+
console.log(import_picocolors27.default.dim(' run: horus investigate "<hint>" --ai'));
|
|
75682
|
+
return 0;
|
|
75683
|
+
} catch (err) {
|
|
75684
|
+
console.error(import_picocolors27.default.red(err instanceof Error ? err.message : String(err)));
|
|
75685
|
+
return 1;
|
|
75686
|
+
}
|
|
75687
|
+
}
|
|
75688
|
+
|
|
75098
75689
|
// ../../packages/cli/src/commands/connect.ts
|
|
75099
75690
|
function parseDbSpec(spec) {
|
|
75100
75691
|
const [dbPart, rolesPart] = spec.split(":");
|
|
@@ -75112,10 +75703,18 @@ function parseDbSpec(spec) {
|
|
|
75112
75703
|
}
|
|
75113
75704
|
var SUPPORTED = ["elasticsearch", "mongodb", "grafana", "redis"];
|
|
75114
75705
|
async function runConnect(type, opts) {
|
|
75706
|
+
if (type === "ai") {
|
|
75707
|
+
return runConnectAi({
|
|
75708
|
+
provider: opts.provider,
|
|
75709
|
+
apiKey: opts.apiKey,
|
|
75710
|
+
model: opts.aiModel,
|
|
75711
|
+
noTest: opts.noTest
|
|
75712
|
+
});
|
|
75713
|
+
}
|
|
75115
75714
|
if (!SUPPORTED.includes(type)) {
|
|
75116
75715
|
console.error(
|
|
75117
|
-
|
|
75118
|
-
supported: ${SUPPORTED.join(", ")}`)
|
|
75716
|
+
import_picocolors28.default.red(`Unknown connector type: ${type}`) + import_picocolors28.default.dim(`
|
|
75717
|
+
supported: ${SUPPORTED.join(", ")}, ai`)
|
|
75119
75718
|
);
|
|
75120
75719
|
return 1;
|
|
75121
75720
|
}
|
|
@@ -75145,20 +75744,20 @@ async function runConnect(type, opts) {
|
|
|
75145
75744
|
if (!probeResult.ok) {
|
|
75146
75745
|
console.error(
|
|
75147
75746
|
`
|
|
75148
|
-
${
|
|
75747
|
+
${import_picocolors28.default.red(`\u2717 Could not reach ${connectorType}:`)} ${probeResult.detail}` + import_picocolors28.default.dim("\n Fix the connection and retry, or pass --no-test to skip.")
|
|
75149
75748
|
);
|
|
75150
75749
|
return 1;
|
|
75151
75750
|
}
|
|
75152
75751
|
console.log(
|
|
75153
75752
|
`
|
|
75154
|
-
${
|
|
75753
|
+
${import_picocolors28.default.green("\u2713")} ${connectorType} reachable ${import_picocolors28.default.dim(`(${probeResult.detail})`)}`
|
|
75155
75754
|
);
|
|
75156
75755
|
}
|
|
75157
75756
|
const hasLiteralCredentials = filled.url !== void 0 || filled.password !== void 0 || filled.username !== void 0;
|
|
75158
75757
|
if (hasLiteralCredentials) {
|
|
75159
75758
|
if (isGitTracked(configPath, root)) {
|
|
75160
75759
|
console.error(
|
|
75161
|
-
|
|
75760
|
+
import_picocolors28.default.red(".horus/config.json is already tracked by Git.") + "\nStoring credentials here would expose them in the repository.\n" + import_picocolors28.default.dim(
|
|
75162
75761
|
" Option A \u2014 remove from Git index then re-run:\n git rm --cached .horus/config.json\n horus connect " + type + " ...\n\n Option B \u2014 keep credentials in the environment and reference them:\n export ES_URL=https://...\n export ES_USERNAME=...\n export ES_PASSWORD=...\n Then edit .horus/config.json manually to use urlEnv/usernameEnv/passwordEnv."
|
|
75163
75762
|
)
|
|
75164
75763
|
);
|
|
@@ -75168,18 +75767,18 @@ ${import_picocolors27.default.green("\u2713")} ${connectorType} reachable ${impo
|
|
|
75168
75767
|
}
|
|
75169
75768
|
patchLocalConnector(configPath, connectorType, patch, filled.env);
|
|
75170
75769
|
console.log(
|
|
75171
|
-
`${
|
|
75770
|
+
`${import_picocolors28.default.green("\u2713")} ${import_picocolors28.default.bold(connectorType)} connector saved \u2192 ${import_picocolors28.default.dim(configPath)}`
|
|
75172
75771
|
);
|
|
75173
75772
|
printSummary(connectorType, filled);
|
|
75174
|
-
console.log(
|
|
75773
|
+
console.log(import_picocolors28.default.dim(`
|
|
75175
75774
|
run: horus investigate "<hint>"`));
|
|
75176
75775
|
return 0;
|
|
75177
75776
|
} catch (err) {
|
|
75178
75777
|
if (err instanceof ExitPromptError) {
|
|
75179
|
-
console.error(
|
|
75778
|
+
console.error(import_picocolors28.default.red("Cancelled."));
|
|
75180
75779
|
return 1;
|
|
75181
75780
|
}
|
|
75182
|
-
console.error(
|
|
75781
|
+
console.error(import_picocolors28.default.red(err.message));
|
|
75183
75782
|
return 1;
|
|
75184
75783
|
}
|
|
75185
75784
|
}
|
|
@@ -75188,7 +75787,7 @@ async function fillInteractive(type, opts) {
|
|
|
75188
75787
|
if (!needsInteraction) return opts;
|
|
75189
75788
|
console.log(
|
|
75190
75789
|
`
|
|
75191
|
-
${
|
|
75790
|
+
${import_picocolors28.default.bold(`Connect ${type}`)} ${import_picocolors28.default.dim("(press Enter to skip optional fields)")}
|
|
75192
75791
|
`
|
|
75193
75792
|
);
|
|
75194
75793
|
const filled = { ...opts };
|
|
@@ -75243,7 +75842,7 @@ ${import_picocolors27.default.bold(`Connect ${type}`)} ${import_picocolors27.def
|
|
|
75243
75842
|
break;
|
|
75244
75843
|
case "redis": {
|
|
75245
75844
|
console.log(
|
|
75246
|
-
|
|
75845
|
+
import_picocolors28.default.dim(
|
|
75247
75846
|
" Tip: embed credentials directly in the URL \u2014 redis://:password@host:6379\n or enter the URL and password separately below."
|
|
75248
75847
|
)
|
|
75249
75848
|
);
|
|
@@ -75281,8 +75880,8 @@ function missingRequired(type, opts) {
|
|
|
75281
75880
|
}
|
|
75282
75881
|
function ask(label, placeholder = "", required = true) {
|
|
75283
75882
|
return new Promise((resolve8) => {
|
|
75284
|
-
const hint = placeholder ?
|
|
75285
|
-
const suffix = required ? "" :
|
|
75883
|
+
const hint = placeholder ? import_picocolors28.default.dim(` (${placeholder})`) : "";
|
|
75884
|
+
const suffix = required ? "" : import_picocolors28.default.dim(" [optional]");
|
|
75286
75885
|
process.stdout.write(` ${label}${suffix}${hint}: `);
|
|
75287
75886
|
const rl = (0, import_node_readline.createInterface)({
|
|
75288
75887
|
input: process.stdin,
|
|
@@ -75299,7 +75898,7 @@ function askPassword(label) {
|
|
|
75299
75898
|
return new Promise((resolve8) => {
|
|
75300
75899
|
const stdin = process.stdin;
|
|
75301
75900
|
if (typeof stdin.setRawMode === "function") {
|
|
75302
|
-
process.stdout.write(` ${label}${
|
|
75901
|
+
process.stdout.write(` ${label}${import_picocolors28.default.dim(" [optional]")}: `);
|
|
75303
75902
|
stdin.setRawMode(true);
|
|
75304
75903
|
stdin.resume();
|
|
75305
75904
|
let value = "";
|
|
@@ -75399,15 +75998,15 @@ function describeProbe(p) {
|
|
|
75399
75998
|
return `${p.keyCount} keys \xB7 ${p.suggestedRoles.join("/")}${examples ? ` \xB7 ${examples}` : ""}`;
|
|
75400
75999
|
}
|
|
75401
76000
|
async function discoverAndSelectDbs(url, bullmqPrefix) {
|
|
75402
|
-
console.log(
|
|
76001
|
+
console.log(import_picocolors28.default.dim("\n Scanning DBs 0-15 (read-only, sampled)\u2026"));
|
|
75403
76002
|
const probes = await probeRedisDatabases(url, { bullmqPrefix });
|
|
75404
76003
|
const nonEmpty = probes.filter((p) => p.reachable && p.keyCount > 0);
|
|
75405
76004
|
if (nonEmpty.length === 0) {
|
|
75406
|
-
console.log(
|
|
76005
|
+
console.log(import_picocolors28.default.dim(" No populated DBs found."));
|
|
75407
76006
|
return [];
|
|
75408
76007
|
}
|
|
75409
76008
|
for (const p of nonEmpty) {
|
|
75410
|
-
console.log(` ${
|
|
76009
|
+
console.log(` ${import_picocolors28.default.bold(`DB ${p.db}`)} ${import_picocolors28.default.dim("\xB7 " + describeProbe(p))}`);
|
|
75411
76010
|
}
|
|
75412
76011
|
const byLabel = /* @__PURE__ */ new Map();
|
|
75413
76012
|
const choices = nonEmpty.map((p) => {
|
|
@@ -75518,11 +76117,11 @@ function printSummary(type, opts) {
|
|
|
75518
76117
|
} else if (opts.dashboard) {
|
|
75519
76118
|
lines.push(` dashboard: ${opts.dashboard}`);
|
|
75520
76119
|
}
|
|
75521
|
-
if (lines.length > 0) console.log(
|
|
76120
|
+
if (lines.length > 0) console.log(import_picocolors28.default.dim(lines.join("\n")));
|
|
75522
76121
|
}
|
|
75523
76122
|
function isGitTracked(filePath, cwd) {
|
|
75524
76123
|
try {
|
|
75525
|
-
(0,
|
|
76124
|
+
(0, import_node_child_process6.execFileSync)("git", ["ls-files", "--error-unmatch", filePath], {
|
|
75526
76125
|
cwd,
|
|
75527
76126
|
stdio: "pipe"
|
|
75528
76127
|
});
|
|
@@ -75557,11 +76156,11 @@ async function askIndexSelection(indices) {
|
|
|
75557
76156
|
const shown = indices.slice(0, MAX_DISPLAY);
|
|
75558
76157
|
console.log("\n Available Elasticsearch indexes/data streams:");
|
|
75559
76158
|
shown.forEach((name, i) => {
|
|
75560
|
-
console.log(` ${
|
|
76159
|
+
console.log(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${name}`);
|
|
75561
76160
|
});
|
|
75562
76161
|
if (indices.length > MAX_DISPLAY) {
|
|
75563
76162
|
console.log(
|
|
75564
|
-
|
|
76163
|
+
import_picocolors28.default.dim(
|
|
75565
76164
|
` \u2026 and ${indices.length - MAX_DISPLAY} more (type a pattern manually to match all)`
|
|
75566
76165
|
)
|
|
75567
76166
|
);
|
|
@@ -75605,11 +76204,11 @@ async function askDashboardSelection(dashboards) {
|
|
|
75605
76204
|
const shown = dashboards.slice(0, MAX_DISPLAY);
|
|
75606
76205
|
console.log("\n Available Grafana dashboards:");
|
|
75607
76206
|
shown.forEach((d, i) => {
|
|
75608
|
-
const folder = d.folderTitle ?
|
|
75609
|
-
console.log(` ${
|
|
76207
|
+
const folder = d.folderTitle ? import_picocolors28.default.dim(` (${d.folderTitle})`) : "";
|
|
76208
|
+
console.log(` ${import_picocolors28.default.dim(`[${i + 1}]`)} ${d.title}${folder}`);
|
|
75610
76209
|
});
|
|
75611
76210
|
if (dashboards.length > MAX_DISPLAY) {
|
|
75612
|
-
console.log(
|
|
76211
|
+
console.log(import_picocolors28.default.dim(` \u2026 and ${dashboards.length - MAX_DISPLAY} more`));
|
|
75613
76212
|
}
|
|
75614
76213
|
const input = (await ask(
|
|
75615
76214
|
` Select dashboards to use (e.g. 1,2 or Enter to type uid manually)`,
|
|
@@ -75659,12 +76258,12 @@ function redactUrl(raw) {
|
|
|
75659
76258
|
|
|
75660
76259
|
// ../../packages/cli/src/commands/stop.ts
|
|
75661
76260
|
init_cjs_shims();
|
|
75662
|
-
var
|
|
76261
|
+
var import_node_child_process7 = require("child_process");
|
|
75663
76262
|
var import_node_fs7 = require("fs");
|
|
75664
76263
|
var import_node_util4 = require("util");
|
|
75665
76264
|
var import_node_path9 = require("path");
|
|
75666
|
-
var
|
|
75667
|
-
var execFileAsync = (0, import_node_util4.promisify)(
|
|
76265
|
+
var import_picocolors29 = __toESM(require_picocolors(), 1);
|
|
76266
|
+
var execFileAsync = (0, import_node_util4.promisify)(import_node_child_process7.execFile);
|
|
75668
76267
|
var unlinkAsync = (0, import_node_util4.promisify)(import_node_fs7.unlink);
|
|
75669
76268
|
var SPAWNED_HOST_FILE2 = "spawned-host.json";
|
|
75670
76269
|
var START_TIME_TOLERANCE_S = 60;
|
|
@@ -75680,30 +76279,30 @@ async function runStop(opts) {
|
|
|
75680
76279
|
const root = findRepoRoot(cwd) ?? cwd;
|
|
75681
76280
|
const hostUrl = readSourceHostUrl(root);
|
|
75682
76281
|
if (!hostUrl) {
|
|
75683
|
-
console.log(
|
|
76282
|
+
console.log(import_picocolors29.default.dim("No source-intelligence host found for this repo (.horus/source/host.json absent)."));
|
|
75684
76283
|
return 0;
|
|
75685
76284
|
}
|
|
75686
76285
|
return await stopHost(root, hostUrl);
|
|
75687
76286
|
} catch (err) {
|
|
75688
|
-
console.error(
|
|
76287
|
+
console.error(import_picocolors29.default.red(err.message));
|
|
75689
76288
|
return 1;
|
|
75690
76289
|
}
|
|
75691
76290
|
}
|
|
75692
76291
|
async function stopHost(root, hostUrl) {
|
|
75693
76292
|
const alive = await isHostHealthy(hostUrl);
|
|
75694
76293
|
if (!alive) {
|
|
75695
|
-
console.log(
|
|
76294
|
+
console.log(import_picocolors29.default.dim(`Host ${hostUrl} is already stopped.`));
|
|
75696
76295
|
return 0;
|
|
75697
76296
|
}
|
|
75698
76297
|
const port = extractPort(hostUrl);
|
|
75699
76298
|
if (port === null) {
|
|
75700
|
-
console.error(
|
|
76299
|
+
console.error(import_picocolors29.default.red(`Cannot determine port from host URL: ${hostUrl}`));
|
|
75701
76300
|
return 1;
|
|
75702
76301
|
}
|
|
75703
76302
|
const spawned = readSpawnedHost(root);
|
|
75704
76303
|
if (spawned === null) {
|
|
75705
76304
|
console.error(
|
|
75706
|
-
|
|
76305
|
+
import_picocolors29.default.red(
|
|
75707
76306
|
`No ownership record found (.horus/${SPAWNED_HOST_FILE2} absent). Horus will not stop a host it did not spawn.`
|
|
75708
76307
|
)
|
|
75709
76308
|
);
|
|
@@ -75711,12 +76310,12 @@ async function stopHost(root, hostUrl) {
|
|
|
75711
76310
|
}
|
|
75712
76311
|
const recordError = validateSpawnedRecord(spawned);
|
|
75713
76312
|
if (recordError !== null) {
|
|
75714
|
-
console.error(
|
|
76313
|
+
console.error(import_picocolors29.default.red(`Ownership record is malformed: ${recordError}. Aborting for safety.`));
|
|
75715
76314
|
return 1;
|
|
75716
76315
|
}
|
|
75717
76316
|
if (spawned.port !== port) {
|
|
75718
76317
|
console.error(
|
|
75719
|
-
|
|
76318
|
+
import_picocolors29.default.red(
|
|
75720
76319
|
`Ownership record port (${spawned.port}) does not match host URL port (${port}). Record may be stale.`
|
|
75721
76320
|
)
|
|
75722
76321
|
);
|
|
@@ -75724,7 +76323,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75724
76323
|
}
|
|
75725
76324
|
if (spawned.root !== root) {
|
|
75726
76325
|
console.error(
|
|
75727
|
-
|
|
76326
|
+
import_picocolors29.default.red(
|
|
75728
76327
|
`Ownership record root (${spawned.root}) does not match resolved root (${root}). Record may be stale.`
|
|
75729
76328
|
)
|
|
75730
76329
|
);
|
|
@@ -75732,7 +76331,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75732
76331
|
}
|
|
75733
76332
|
const info = await getProcessInfo(spawned.pid);
|
|
75734
76333
|
if (info === null) {
|
|
75735
|
-
console.log(
|
|
76334
|
+
console.log(import_picocolors29.default.dim(`Process pid ${spawned.pid} is no longer running \u2014 already stopped.`));
|
|
75736
76335
|
try {
|
|
75737
76336
|
await unlinkAsync((0, import_node_path9.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
|
|
75738
76337
|
} catch {
|
|
@@ -75745,7 +76344,7 @@ async function stopHost(root, hostUrl) {
|
|
|
75745
76344
|
);
|
|
75746
76345
|
if (!hostPortRe.test(info.args)) {
|
|
75747
76346
|
console.error(
|
|
75748
|
-
|
|
76347
|
+
import_picocolors29.default.red(
|
|
75749
76348
|
`Pid ${spawned.pid} args do not match "horus-source host --port ${portStr}". Got: "${info.args.slice(0, 120)}". Aborting for safety.`
|
|
75750
76349
|
)
|
|
75751
76350
|
);
|
|
@@ -75754,12 +76353,12 @@ async function stopHost(root, hostUrl) {
|
|
|
75754
76353
|
const startTs = new Date(spawned.startedAt).getTime();
|
|
75755
76354
|
const recordedAgeS = Math.round((Date.now() - startTs) / 1e3);
|
|
75756
76355
|
if (!Number.isFinite(info.etimeSeconds)) {
|
|
75757
|
-
console.error(
|
|
76356
|
+
console.error(import_picocolors29.default.red(`Could not read elapsed time for pid ${spawned.pid}. Aborting for safety.`));
|
|
75758
76357
|
return 1;
|
|
75759
76358
|
}
|
|
75760
76359
|
if (Math.abs(info.etimeSeconds - recordedAgeS) > START_TIME_TOLERANCE_S) {
|
|
75761
76360
|
console.error(
|
|
75762
|
-
|
|
76361
|
+
import_picocolors29.default.red(
|
|
75763
76362
|
`Pid ${spawned.pid} age mismatch: record says ~${recordedAgeS}s, process reports ${info.etimeSeconds}s elapsed. Possible PID reuse \u2014 aborting for safety.`
|
|
75764
76363
|
)
|
|
75765
76364
|
);
|
|
@@ -75771,9 +76370,9 @@ async function stopHost(root, hostUrl) {
|
|
|
75771
76370
|
signaled = true;
|
|
75772
76371
|
} catch (err) {
|
|
75773
76372
|
if (err.code === "ESRCH") {
|
|
75774
|
-
console.log(
|
|
76373
|
+
console.log(import_picocolors29.default.dim(`Process pid ${spawned.pid} exited before signal \u2014 already stopped.`));
|
|
75775
76374
|
} else {
|
|
75776
|
-
console.error(
|
|
76375
|
+
console.error(import_picocolors29.default.red(`Failed to signal pid ${spawned.pid}: ${err.message}`));
|
|
75777
76376
|
return 1;
|
|
75778
76377
|
}
|
|
75779
76378
|
}
|
|
@@ -75789,14 +76388,14 @@ async function stopHost(root, hostUrl) {
|
|
|
75789
76388
|
}
|
|
75790
76389
|
if (!exited) {
|
|
75791
76390
|
console.error(
|
|
75792
|
-
|
|
76391
|
+
import_picocolors29.default.red(
|
|
75793
76392
|
`Host pid ${spawned.pid} did not exit within ${STOP_WAIT_MS / 1e3}s after SIGTERM.`
|
|
75794
76393
|
)
|
|
75795
76394
|
);
|
|
75796
76395
|
return 1;
|
|
75797
76396
|
}
|
|
75798
76397
|
console.log(
|
|
75799
|
-
`${
|
|
76398
|
+
`${import_picocolors29.default.green("\u2713")} Stopped source-intelligence host ` + import_picocolors29.default.dim(`(pid ${spawned.pid}, port ${port})`) + ` for ${root}`
|
|
75800
76399
|
);
|
|
75801
76400
|
}
|
|
75802
76401
|
try {
|
|
@@ -75809,7 +76408,7 @@ async function stopAll() {
|
|
|
75809
76408
|
const registry = readRegistry();
|
|
75810
76409
|
const projects2 = Object.entries(registry.projects);
|
|
75811
76410
|
if (projects2.length === 0) {
|
|
75812
|
-
console.log(
|
|
76411
|
+
console.log(import_picocolors29.default.dim("No registered projects."));
|
|
75813
76412
|
return 0;
|
|
75814
76413
|
}
|
|
75815
76414
|
let stopped = 0;
|
|
@@ -75819,17 +76418,17 @@ async function stopAll() {
|
|
|
75819
76418
|
if (!hostUrl) continue;
|
|
75820
76419
|
const alive = await isHostHealthy(hostUrl);
|
|
75821
76420
|
if (!alive) continue;
|
|
75822
|
-
console.log(` Stopping ${
|
|
76421
|
+
console.log(` Stopping ${import_picocolors29.default.bold(name)} ${import_picocolors29.default.dim(`(${hostUrl})`)}`);
|
|
75823
76422
|
const code = await stopHost(entry2.root, hostUrl);
|
|
75824
76423
|
if (code === 0) stopped++;
|
|
75825
76424
|
else failed++;
|
|
75826
76425
|
}
|
|
75827
76426
|
if (stopped === 0 && failed === 0) {
|
|
75828
|
-
console.log(
|
|
76427
|
+
console.log(import_picocolors29.default.dim("No running source-intelligence hosts found."));
|
|
75829
76428
|
} else {
|
|
75830
76429
|
console.log(
|
|
75831
76430
|
`
|
|
75832
|
-
Stopped ${stopped} host(s)${failed > 0 ?
|
|
76431
|
+
Stopped ${stopped} host(s)${failed > 0 ? import_picocolors29.default.red(`, ${failed} failed`) : ""}.`
|
|
75833
76432
|
);
|
|
75834
76433
|
}
|
|
75835
76434
|
return failed > 0 ? 1 : 0;
|
|
@@ -75881,12 +76480,12 @@ function extractPort(hostUrl) {
|
|
|
75881
76480
|
|
|
75882
76481
|
// ../../packages/cli/src/commands/hosts.ts
|
|
75883
76482
|
init_cjs_shims();
|
|
75884
|
-
var
|
|
76483
|
+
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
75885
76484
|
async function runHosts() {
|
|
75886
76485
|
const registry = readRegistry();
|
|
75887
76486
|
const projects2 = Object.entries(registry.projects);
|
|
75888
76487
|
if (projects2.length === 0) {
|
|
75889
|
-
console.log(
|
|
76488
|
+
console.log(import_picocolors30.default.dim("No registered projects. Run `horus index` in a repo first."));
|
|
75890
76489
|
return 0;
|
|
75891
76490
|
}
|
|
75892
76491
|
const rows = [];
|
|
@@ -75903,29 +76502,29 @@ async function runHosts() {
|
|
|
75903
76502
|
});
|
|
75904
76503
|
const anyHost = rows.some((r) => r.hostUrl !== null);
|
|
75905
76504
|
if (!anyHost) {
|
|
75906
|
-
console.log(
|
|
76505
|
+
console.log(import_picocolors30.default.dim("No source-intelligence hosts found. Run `horus index` to start one."));
|
|
75907
76506
|
return 0;
|
|
75908
76507
|
}
|
|
75909
76508
|
console.log("");
|
|
75910
76509
|
for (const row of rows) {
|
|
75911
76510
|
if (row.hostUrl === null) continue;
|
|
75912
|
-
const status = row.healthy ?
|
|
76511
|
+
const status = row.healthy ? import_picocolors30.default.green("\u25CF running") : import_picocolors30.default.red("\u25CF stopped");
|
|
75913
76512
|
const port = extractPort2(row.hostUrl) ?? "?";
|
|
75914
76513
|
console.log(
|
|
75915
|
-
` ${status} ${
|
|
76514
|
+
` ${status} ${import_picocolors30.default.bold(row.name.padEnd(24))} port ${String(port).padEnd(6)} ${import_picocolors30.default.dim(row.root)}`
|
|
75916
76515
|
);
|
|
75917
76516
|
}
|
|
75918
76517
|
console.log("");
|
|
75919
76518
|
const noHost = rows.filter((r) => r.hostUrl === null);
|
|
75920
76519
|
if (noHost.length > 0) {
|
|
75921
76520
|
for (const row of noHost) {
|
|
75922
|
-
console.log(` ${
|
|
76521
|
+
console.log(` ${import_picocolors30.default.dim("\u25CB no host")} ${import_picocolors30.default.dim(row.name)}`);
|
|
75923
76522
|
}
|
|
75924
76523
|
console.log("");
|
|
75925
76524
|
}
|
|
75926
76525
|
const running = rows.filter((r) => r.healthy).length;
|
|
75927
76526
|
console.log(
|
|
75928
|
-
|
|
76527
|
+
import_picocolors30.default.dim(
|
|
75929
76528
|
`${running} running \xB7 horus stop to reap \xB7 horus stop --all to stop everything`
|
|
75930
76529
|
)
|
|
75931
76530
|
);
|
|
@@ -75942,12 +76541,12 @@ function extractPort2(hostUrl) {
|
|
|
75942
76541
|
|
|
75943
76542
|
// ../../packages/cli/src/commands/doctor.ts
|
|
75944
76543
|
init_cjs_shims();
|
|
75945
|
-
var
|
|
76544
|
+
var import_picocolors31 = __toESM(require_picocolors(), 1);
|
|
75946
76545
|
var DEFAULT_DB_URL4 = "postgresql://horus:horus@localhost:5433/horus";
|
|
75947
76546
|
function mark2(status) {
|
|
75948
|
-
if (status === "pass") return
|
|
75949
|
-
if (status === "warn") return
|
|
75950
|
-
return
|
|
76547
|
+
if (status === "pass") return import_picocolors31.default.green("\u2713");
|
|
76548
|
+
if (status === "warn") return import_picocolors31.default.yellow("~");
|
|
76549
|
+
return import_picocolors31.default.red("\u2717");
|
|
75951
76550
|
}
|
|
75952
76551
|
async function runDoctor(opts) {
|
|
75953
76552
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -76174,11 +76773,11 @@ async function runDoctor(opts) {
|
|
|
76174
76773
|
write(JSON.stringify(output, null, 2));
|
|
76175
76774
|
return hasFailure ? 1 : 0;
|
|
76176
76775
|
}
|
|
76177
|
-
write(
|
|
76776
|
+
write(import_picocolors31.default.bold("\nHorus readiness check\n"));
|
|
76178
76777
|
for (const check of checks) {
|
|
76179
|
-
write(` ${mark2(check.status)} ${
|
|
76778
|
+
write(` ${mark2(check.status)} ${import_picocolors31.default.bold(check.label.padEnd(26))} ${import_picocolors31.default.dim(check.detail)}`);
|
|
76180
76779
|
if (check.next) {
|
|
76181
|
-
write(` ${
|
|
76780
|
+
write(` ${import_picocolors31.default.dim("\u2192 " + check.next)}`);
|
|
76182
76781
|
}
|
|
76183
76782
|
}
|
|
76184
76783
|
write("");
|
|
@@ -76187,20 +76786,20 @@ async function runDoctor(opts) {
|
|
|
76187
76786
|
|
|
76188
76787
|
// ../../packages/cli/src/commands/providers-doctor.ts
|
|
76189
76788
|
init_cjs_shims();
|
|
76190
|
-
var
|
|
76191
|
-
var
|
|
76789
|
+
var import_node_child_process8 = require("child_process");
|
|
76790
|
+
var import_picocolors32 = __toESM(require_picocolors(), 1);
|
|
76192
76791
|
function statusMark(status) {
|
|
76193
|
-
if (status === "ready") return
|
|
76194
|
-
if (status === "installed") return
|
|
76195
|
-
return
|
|
76792
|
+
if (status === "ready") return import_picocolors32.default.green("\u2713");
|
|
76793
|
+
if (status === "installed") return import_picocolors32.default.yellow("~");
|
|
76794
|
+
return import_picocolors32.default.red("\u2717");
|
|
76196
76795
|
}
|
|
76197
76796
|
function statusLabel(status) {
|
|
76198
|
-
if (status === "ready") return
|
|
76199
|
-
if (status === "installed") return
|
|
76200
|
-
return
|
|
76797
|
+
if (status === "ready") return import_picocolors32.default.green("ready");
|
|
76798
|
+
if (status === "installed") return import_picocolors32.default.yellow("installed (not configured)");
|
|
76799
|
+
return import_picocolors32.default.dim("not found on PATH");
|
|
76201
76800
|
}
|
|
76202
76801
|
function detectBinary(id) {
|
|
76203
|
-
const result = (0,
|
|
76802
|
+
const result = (0, import_node_child_process8.spawnSync)(id, ["--version"], { stdio: "pipe", timeout: 2e3 });
|
|
76204
76803
|
if (result.error) {
|
|
76205
76804
|
return { id, status: "unavailable", detail: `${id}: command not found` };
|
|
76206
76805
|
}
|
|
@@ -76214,7 +76813,7 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76214
76813
|
const registry = opts?.registry ?? DEFAULT_LOCAL_PROVIDER_REGISTRY;
|
|
76215
76814
|
const write = opts?.write ?? ((line2) => console.log(line2));
|
|
76216
76815
|
const results = buildProviderResults(registry, opts?._detect);
|
|
76217
|
-
write(
|
|
76816
|
+
write(import_picocolors32.default.bold("\nLocal AI providers\n"));
|
|
76218
76817
|
for (const result of results) {
|
|
76219
76818
|
const descriptor = registry.get(result.id);
|
|
76220
76819
|
const name = descriptor?.displayName ?? result.id;
|
|
@@ -76222,22 +76821,37 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76222
76821
|
` ${statusMark(result.status)} ${result.id.padEnd(8)} ${name.padEnd(22)} ${statusLabel(result.status)}`
|
|
76223
76822
|
);
|
|
76224
76823
|
if (result.status !== "ready" && result.detail) {
|
|
76225
|
-
write(` ${
|
|
76824
|
+
write(` ${import_picocolors32.default.dim("\u2192 " + result.detail)}`);
|
|
76226
76825
|
}
|
|
76227
76826
|
}
|
|
76228
76827
|
write("");
|
|
76229
|
-
write(
|
|
76230
|
-
|
|
76231
|
-
if (
|
|
76828
|
+
write(import_picocolors32.default.bold("Cloud AI providers\n"));
|
|
76829
|
+
let source = null;
|
|
76830
|
+
if (opts?._anthropicKey !== void 0) {
|
|
76831
|
+
source = opts._anthropicKey ? "env" : null;
|
|
76832
|
+
} else {
|
|
76833
|
+
try {
|
|
76834
|
+
const config = await loadConfig(opts?.config);
|
|
76835
|
+
const ai = resolveAiSettings(config);
|
|
76836
|
+
if (ai.anthropicApiKey) source = ai.anthropicKeyFromConfig ? "config" : "env";
|
|
76837
|
+
} catch {
|
|
76838
|
+
if (process.env["ANTHROPIC_API_KEY"]) source = "env";
|
|
76839
|
+
}
|
|
76840
|
+
}
|
|
76841
|
+
if (source === "config") {
|
|
76842
|
+
write(
|
|
76843
|
+
` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (.horus/config.json)")}`
|
|
76844
|
+
);
|
|
76845
|
+
} else if (source === "env") {
|
|
76232
76846
|
write(
|
|
76233
|
-
` ${
|
|
76847
|
+
` ${import_picocolors32.default.green("\u2713")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.green("configured (ANTHROPIC_API_KEY env)")}`
|
|
76234
76848
|
);
|
|
76235
76849
|
} else {
|
|
76236
76850
|
write(
|
|
76237
|
-
` ${
|
|
76851
|
+
` ${import_picocolors32.default.red("\u2717")} ${"anthropic".padEnd(8)} ${"Anthropic Claude API".padEnd(22)} ${import_picocolors32.default.dim("not configured")}`
|
|
76238
76852
|
);
|
|
76239
76853
|
write(
|
|
76240
|
-
` ${
|
|
76854
|
+
` ${import_picocolors32.default.dim("\u2192 run `horus connect ai` (or set ANTHROPIC_API_KEY) to enable `horus investigate --ai`")}`
|
|
76241
76855
|
);
|
|
76242
76856
|
}
|
|
76243
76857
|
write("");
|
|
@@ -76248,7 +76862,7 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
76248
76862
|
init_cjs_shims();
|
|
76249
76863
|
var import_node_fs8 = require("fs");
|
|
76250
76864
|
var import_node_path10 = require("path");
|
|
76251
|
-
var
|
|
76865
|
+
var import_picocolors33 = __toESM(require_picocolors(), 1);
|
|
76252
76866
|
function configTemplate(name, repoPath) {
|
|
76253
76867
|
return `export default {
|
|
76254
76868
|
database: {
|
|
@@ -76300,40 +76914,40 @@ async function runGenerateConfig(opts) {
|
|
|
76300
76914
|
const name = opts.name ?? defaults?.name ?? "my-project";
|
|
76301
76915
|
const repoPath = opts.repo ?? defaults?.repoPath ?? `/path/to/${name}`;
|
|
76302
76916
|
if ((0, import_node_fs8.existsSync)(outPath) && !opts.force) {
|
|
76303
|
-
log(`${
|
|
76304
|
-
log(
|
|
76917
|
+
log(`${import_picocolors33.default.red("\u2717")} ${outPath} already exists`);
|
|
76918
|
+
log(import_picocolors33.default.dim(" pass --force to overwrite"));
|
|
76305
76919
|
return 1;
|
|
76306
76920
|
}
|
|
76307
76921
|
try {
|
|
76308
76922
|
(0, import_node_fs8.mkdirSync)((0, import_node_path10.dirname)(outPath), { recursive: true });
|
|
76309
76923
|
(0, import_node_fs8.writeFileSync)(outPath, configTemplate(name, repoPath), "utf8");
|
|
76310
76924
|
} catch (err) {
|
|
76311
|
-
log(`${
|
|
76925
|
+
log(`${import_picocolors33.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
|
|
76312
76926
|
return 1;
|
|
76313
76927
|
}
|
|
76314
|
-
log(`${
|
|
76315
|
-
log(
|
|
76316
|
-
log(
|
|
76928
|
+
log(`${import_picocolors33.default.green("\u2713")} Created ${outPath}`);
|
|
76929
|
+
log(import_picocolors33.default.dim(` project: ${name}`));
|
|
76930
|
+
log(import_picocolors33.default.dim(` repo: ${repoPath}`));
|
|
76317
76931
|
if (hasLocalConfig && localConfigPath2 != null) {
|
|
76318
76932
|
log("");
|
|
76319
|
-
log(
|
|
76933
|
+
log(import_picocolors33.default.yellow("Note:") + ` an initialized Horus project config exists at ${localConfigPath2}`);
|
|
76320
76934
|
log(" \u2022 .horus/config.json \u2014 project config used by `horus investigate` from this repo");
|
|
76321
76935
|
log(" \u2022 horus.config.js \u2014 standalone/global config used with `horus doctor --config <path>`");
|
|
76322
|
-
log(
|
|
76936
|
+
log(import_picocolors33.default.dim(` next: review ${outPath} and copy/adapt it as needed`));
|
|
76323
76937
|
} else {
|
|
76324
|
-
log(
|
|
76938
|
+
log(import_picocolors33.default.dim(` next: horus doctor --config ${outPath}`));
|
|
76325
76939
|
}
|
|
76326
76940
|
return 0;
|
|
76327
76941
|
}
|
|
76328
76942
|
|
|
76329
76943
|
// ../../packages/cli/src/commands/readiness.ts
|
|
76330
76944
|
init_cjs_shims();
|
|
76331
|
-
var
|
|
76945
|
+
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
76332
76946
|
var DEFAULT_DB_URL5 = "postgresql://horus:horus@localhost:5433/horus";
|
|
76333
76947
|
function mark3(status) {
|
|
76334
|
-
if (status === "pass") return
|
|
76335
|
-
if (status === "warn") return
|
|
76336
|
-
return
|
|
76948
|
+
if (status === "pass") return import_picocolors34.default.green("\u2713");
|
|
76949
|
+
if (status === "warn") return import_picocolors34.default.yellow("~");
|
|
76950
|
+
return import_picocolors34.default.red("\u2717");
|
|
76337
76951
|
}
|
|
76338
76952
|
async function runReadiness(opts) {
|
|
76339
76953
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -76488,20 +77102,20 @@ async function runReadiness(opts) {
|
|
|
76488
77102
|
}
|
|
76489
77103
|
const blockingChecks = checks.filter((c) => c.blocking);
|
|
76490
77104
|
const optionalChecks = checks.filter((c) => !c.blocking);
|
|
76491
|
-
write(
|
|
76492
|
-
write(
|
|
77105
|
+
write(import_picocolors34.default.bold("\nHorus release readiness\n"));
|
|
77106
|
+
write(import_picocolors34.default.bold(" Blocking"));
|
|
76493
77107
|
for (const check of blockingChecks) {
|
|
76494
|
-
write(` ${mark3(check.status)} ${
|
|
77108
|
+
write(` ${mark3(check.status)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
|
|
76495
77109
|
if (check.next) {
|
|
76496
|
-
write(` ${
|
|
77110
|
+
write(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
|
|
76497
77111
|
}
|
|
76498
77112
|
}
|
|
76499
77113
|
write("");
|
|
76500
|
-
write(
|
|
77114
|
+
write(import_picocolors34.default.bold(" Optional"));
|
|
76501
77115
|
for (const check of optionalChecks) {
|
|
76502
|
-
write(` ${mark3(check.status)} ${
|
|
77116
|
+
write(` ${mark3(check.status)} ${import_picocolors34.default.bold(check.label.padEnd(22))} ${import_picocolors34.default.dim(check.detail)}`);
|
|
76503
77117
|
if (check.next) {
|
|
76504
|
-
write(` ${
|
|
77118
|
+
write(` ${import_picocolors34.default.dim("\u2192 " + check.next)}`);
|
|
76505
77119
|
}
|
|
76506
77120
|
}
|
|
76507
77121
|
write("");
|
|
@@ -76509,21 +77123,21 @@ async function runReadiness(opts) {
|
|
|
76509
77123
|
const optionalWarns = optionalChecks.filter((c) => c.status === "warn").length;
|
|
76510
77124
|
if (blockingFails.length === 0) {
|
|
76511
77125
|
if (optionalWarns === 0) {
|
|
76512
|
-
write(
|
|
77126
|
+
write(import_picocolors34.default.green(" Ready for demo/release."));
|
|
76513
77127
|
} else {
|
|
76514
77128
|
write(
|
|
76515
|
-
|
|
77129
|
+
import_picocolors34.default.yellow(
|
|
76516
77130
|
` Ready for a basic demo. ${optionalWarns} optional item(s) not configured \u2014 investigation evidence will be limited.`
|
|
76517
77131
|
)
|
|
76518
77132
|
);
|
|
76519
77133
|
}
|
|
76520
77134
|
} else {
|
|
76521
77135
|
write(
|
|
76522
|
-
|
|
77136
|
+
import_picocolors34.default.red(
|
|
76523
77137
|
` Not ready. ${blockingFails.length} blocking item(s) must be resolved before demo/release.`
|
|
76524
77138
|
)
|
|
76525
77139
|
);
|
|
76526
|
-
write(
|
|
77140
|
+
write(import_picocolors34.default.dim(" Re-run `horus readiness` after resolving the items above."));
|
|
76527
77141
|
}
|
|
76528
77142
|
write("");
|
|
76529
77143
|
return blockingFails.length > 0 ? 1 : 0;
|
|
@@ -76573,8 +77187,8 @@ Examples:
|
|
|
76573
77187
|
process.exitCode = await runReadiness({ config: opts.config });
|
|
76574
77188
|
});
|
|
76575
77189
|
program2.command("connect <type>").description(
|
|
76576
|
-
"Add or update a
|
|
76577
|
-
).option("--env <name>", "target environment (default: first environment in config)").option("--url <url>", "connector URL or connection string (Redis with auth: redis://:password@host:6379)").option("--username <user>", "username (elasticsearch / grafana)").option("--password <pass>", "password (elasticsearch / grafana; for Redis embed in --url)").option("--index-pattern <pattern>", "Elasticsearch index pattern (required for elasticsearch)").option("--service <name>", "service name scope for log queries").option("--database <name>", "database name (required for mongodb)").option("--collections <list>", "comma-separated collection allowlist (mongodb)").option("--dashboard <uid>", "default dashboard uid (grafana)").option(
|
|
77190
|
+
"Add or update a connector (elasticsearch / mongodb / grafana / redis / ai) in .horus/config.json"
|
|
77191
|
+
).option("--env <name>", "target environment (default: first environment in config)").option("--provider <name>", "AI provider for `connect ai` (anthropic / claude / codex / gemini)").option("--api-key <key>", "Anthropic API key for `connect ai`").option("--model <id>", "default model for `connect ai`").option("--url <url>", "connector URL or connection string (Redis with auth: redis://:password@host:6379)").option("--username <user>", "username (elasticsearch / grafana)").option("--password <pass>", "password (elasticsearch / grafana; for Redis embed in --url)").option("--index-pattern <pattern>", "Elasticsearch index pattern (required for elasticsearch)").option("--service <name>", "service name scope for log queries").option("--database <name>", "database name (required for mongodb)").option("--collections <list>", "comma-separated collection allowlist (mongodb)").option("--dashboard <uid>", "default dashboard uid (grafana)").option(
|
|
76578
77192
|
"--db <spec>",
|
|
76579
77193
|
"redis logical DB as db:role1,role2 (e.g. 0:cache,state or 1:bullmq,queues); repeatable",
|
|
76580
77194
|
(val, acc) => {
|
|
@@ -76597,6 +77211,9 @@ Examples:
|
|
|
76597
77211
|
db: opts.db,
|
|
76598
77212
|
bullmqPrefix: opts.bullmqPrefix,
|
|
76599
77213
|
scanDbs: opts.scanDbs,
|
|
77214
|
+
provider: opts.provider,
|
|
77215
|
+
apiKey: opts.apiKey,
|
|
77216
|
+
aiModel: opts.model,
|
|
76600
77217
|
noTest: opts.test === false
|
|
76601
77218
|
});
|
|
76602
77219
|
}
|
|
@@ -76634,13 +77251,14 @@ Examples:
|
|
|
76634
77251
|
process.exitCode = await runIndex(opts);
|
|
76635
77252
|
}
|
|
76636
77253
|
);
|
|
76637
|
-
program2.command("queues [name]").description("Show queue topology from source intelligence; --live adds real-time Redis/BullMQ state").option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via registry)").option("--project <name>", "filter edges by project").option("--live", "fetch real-time queue depths and failed-job counts from Redis/BullMQ").action(
|
|
77254
|
+
program2.command("queues [name]").description("Show queue topology from source intelligence; --live adds real-time Redis/BullMQ state").option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via registry)").option("--project <name>", "filter edges by project").option("--live", "fetch real-time queue depths and failed-job counts from Redis/BullMQ").option("--json", "output JSON").action(
|
|
76638
77255
|
async (name, opts) => {
|
|
76639
77256
|
process.exitCode = await runQueues(name, {
|
|
76640
77257
|
config: opts.config,
|
|
76641
77258
|
name: opts.name,
|
|
76642
77259
|
project: opts.project,
|
|
76643
|
-
live: opts.live
|
|
77260
|
+
live: opts.live,
|
|
77261
|
+
json: opts.json
|
|
76644
77262
|
});
|
|
76645
77263
|
}
|
|
76646
77264
|
);
|
|
@@ -76677,13 +77295,14 @@ Examples:
|
|
|
76677
77295
|
});
|
|
76678
77296
|
program2.command("timeline [service]").description(
|
|
76679
77297
|
"Reconstruct what changed in a time window (git + change-impact) \u2014 evidence, not conclusions"
|
|
76680
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--since <when>", 'git --since (e.g. "
|
|
77298
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--since <when>", 'git --since (default "7 days ago"; e.g. "30 days ago", a date)').option("--until <when>", "git --until").option("--all", "include all history instead of the default recent window").option("--json", "output JSON").action(
|
|
76681
77299
|
async (service, opts) => {
|
|
76682
77300
|
process.exitCode = await runTimeline(service, {
|
|
76683
77301
|
config: opts.config,
|
|
76684
77302
|
repo: opts.repo,
|
|
76685
77303
|
since: opts.since,
|
|
76686
77304
|
until: opts.until,
|
|
77305
|
+
all: opts.all,
|
|
76687
77306
|
json: opts.json
|
|
76688
77307
|
});
|
|
76689
77308
|
}
|
|
@@ -76781,7 +77400,7 @@ Examples:
|
|
|
76781
77400
|
process.exitCode = await runScore(id, { config: opts.config, json: opts.json });
|
|
76782
77401
|
});
|
|
76783
77402
|
program2.command("ask <id> <directive>").description(
|
|
76784
|
-
'
|
|
77403
|
+
'Ask about or refine a saved investigation \u2014 reuses evidence, no re-query.\n Questions (direct answers):\n "what evidence contradicts <topic>?" \xB7 "what evidence is missing?"\n "why is confidence not higher?"\n Topic filters (deterministic scoping):\n "focus on queue behavior" \xB7 "ignore deployment changes" \xB7 "retry"'
|
|
76785
77404
|
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").action(async (id, directive, opts) => {
|
|
76786
77405
|
process.exitCode = await runAsk(id, directive, { config: opts.config, json: opts.json });
|
|
76787
77406
|
});
|
|
@@ -76807,14 +77426,14 @@ Examples:
|
|
|
76807
77426
|
});
|
|
76808
77427
|
program2.command("logs [service]").description(
|
|
76809
77428
|
"Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
|
|
76810
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence").option("--limit <n>", "max records (with --raw)").action(
|
|
77429
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--since <when>", "time window, e.g. 24h, 7d, or an ISO date").option("--level <level>", "minimum level (with --raw): trace|debug|info|warn|error|fatal").option("--grep <text>", "match text in the message").option("--raw", "dump individual log lines instead of synthesized evidence (error+ by default)").option("--all-levels", "with --raw: show all severity levels, not just error+").option("--limit <n>", "max records (with --raw)").option("--json", "output JSON").action(
|
|
76811
77430
|
async (service, opts) => {
|
|
76812
77431
|
process.exitCode = await runLogs(service, opts);
|
|
76813
77432
|
}
|
|
76814
77433
|
);
|
|
76815
77434
|
program2.command("state").description(
|
|
76816
77435
|
"Surface application-state evidence from MongoDB (read-only, allowlisted): counts, staleness, anomalous statuses"
|
|
76817
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--stale-hours <n>", "staleness threshold in hours (default 24)").action(
|
|
77436
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--project <name>", "project name").option("--env <name>", "environment name (e.g. production)").option("--stale-hours <n>", "staleness threshold in hours (default 24)").option("--json", "output JSON").action(
|
|
76818
77437
|
async (opts) => {
|
|
76819
77438
|
process.exitCode = await runState(opts);
|
|
76820
77439
|
}
|