@merittdev/horus 0.1.15 → 0.1.16
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 +903 -306
- 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.16" : "dev";
|
|
50318
50318
|
var PINNED_AXON_VERSION = "1.0.7";
|
|
50319
50319
|
var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
|
|
50320
50320
|
|
|
@@ -69193,7 +69193,7 @@ function buildRuntimeSourceStatus(evidence2, connectors) {
|
|
|
69193
69193
|
const metricsFailed = metricsConfigured && !connectors.metricsCollected;
|
|
69194
69194
|
const stateCount = evidence2.filter((e) => e.source === "state").length;
|
|
69195
69195
|
const stateConfigured = !!(connectors.redis || connectors.mongodb);
|
|
69196
|
-
const queueConfigured = evidence2.some((e) => e.source === "queue");
|
|
69196
|
+
const queueConfigured = connectors.queue ?? evidence2.some((e) => e.source === "queue");
|
|
69197
69197
|
const queueCount = evidence2.filter((e) => e.kind === "queue-state").length;
|
|
69198
69198
|
return {
|
|
69199
69199
|
sources: [
|
|
@@ -70120,11 +70120,15 @@ async function investigate(input, deps) {
|
|
|
70120
70120
|
const queueRuntimeEvIdsByQueue = /* @__PURE__ */ new Map();
|
|
70121
70121
|
const queueBacklogEvIdsByQueue = /* @__PURE__ */ new Map();
|
|
70122
70122
|
const queueStarvationEvIdsByQueue = /* @__PURE__ */ new Map();
|
|
70123
|
-
if (deps.queue
|
|
70123
|
+
if (deps.queue) {
|
|
70124
70124
|
try {
|
|
70125
|
-
const
|
|
70126
|
-
|
|
70127
|
-
|
|
70125
|
+
const staticNames = [...new Set(queueHits.map((e) => e.queueName))];
|
|
70126
|
+
const discovered = await deps.queue.discoverQueues().catch(() => []);
|
|
70127
|
+
const queueNames2 = [.../* @__PURE__ */ new Set([...staticNames, ...discovered])];
|
|
70128
|
+
if (queueNames2.length > 0) {
|
|
70129
|
+
queueRuntimeState = await deps.queue.analyzeQueues({ queueNames: queueNames2 });
|
|
70130
|
+
}
|
|
70131
|
+
for (const s of analyzeQueueRuntime(queueRuntimeState ?? { prefix: "", collectedAt: "", queues: [] })) {
|
|
70128
70132
|
const ev = mkEv("queue-state", s.title, s.payload, { queueName: s.queueName }, s.timestamp, s.relevance);
|
|
70129
70133
|
queueRuntimeEvIds.push(ev.id);
|
|
70130
70134
|
const perQueue = queueRuntimeEvIdsByQueue.get(s.queueName) ?? [];
|
|
@@ -70571,10 +70575,21 @@ async function investigate(input, deps) {
|
|
|
70571
70575
|
causeChains: causeChains.length > 0 ? causeChains : void 0,
|
|
70572
70576
|
...recentChanges !== void 0 ? { recentChanges } : {}
|
|
70573
70577
|
};
|
|
70574
|
-
const connectorFlags = deps.connectors ? {
|
|
70578
|
+
const connectorFlags = deps.connectors ? {
|
|
70579
|
+
...deps.connectors,
|
|
70580
|
+
// The queue connector is configured iff a BullMQ provider was constructed
|
|
70581
|
+
// (queueForEnv returns null without a bullmq/queues Redis DB) — HOR-205.
|
|
70582
|
+
queue: deps.queue != null,
|
|
70583
|
+
metricsCollected,
|
|
70584
|
+
metricsFailureReason,
|
|
70585
|
+
logsCollected,
|
|
70586
|
+
logsCompatibilityError,
|
|
70587
|
+
sinceProvided: input.since !== void 0
|
|
70588
|
+
} : {
|
|
70575
70589
|
elasticsearch: deps.logs != null,
|
|
70576
70590
|
mongodb: deps.mongo != null,
|
|
70577
70591
|
grafana: deps.metrics != null,
|
|
70592
|
+
queue: deps.queue != null,
|
|
70578
70593
|
metricsCollected,
|
|
70579
70594
|
metricsFailureReason,
|
|
70580
70595
|
logsCollected,
|
|
@@ -73198,253 +73213,6 @@ async function runIndex(opts) {
|
|
|
73198
73213
|
// ../../packages/cli/src/commands/queues.ts
|
|
73199
73214
|
init_cjs_shims();
|
|
73200
73215
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
73201
|
-
async function runQueues(name, opts) {
|
|
73202
|
-
try {
|
|
73203
|
-
const config = await loadConfig(opts.config, { name: opts.name });
|
|
73204
|
-
const { db, sql: sql2 } = createDb(config.database.url);
|
|
73205
|
-
try {
|
|
73206
|
-
let project = opts.project;
|
|
73207
|
-
if (project === void 0) {
|
|
73208
|
-
try {
|
|
73209
|
-
project = resolveEnvironment(config, { project: opts.project }).project;
|
|
73210
|
-
} catch {
|
|
73211
|
-
}
|
|
73212
|
-
}
|
|
73213
|
-
const rows = await listQueueEdges(db, { project, queueName: name });
|
|
73214
|
-
if (opts.json) {
|
|
73215
|
-
const topology = topologyToJson(buildQueueMap(rows));
|
|
73216
|
-
const out = { project: project ?? null, topology };
|
|
73217
|
-
if (opts.live) out["live"] = await gatherLiveState(config, rows, name);
|
|
73218
|
-
console.log(JSON.stringify(out, null, 2));
|
|
73219
|
-
return 0;
|
|
73220
|
-
}
|
|
73221
|
-
console.log(
|
|
73222
|
-
import_picocolors4.default.bold("Queue topology") + import_picocolors4.default.dim(" \xB7 source: code / source intelligence \xB7 static (run horus index to refresh)")
|
|
73223
|
-
);
|
|
73224
|
-
console.log("");
|
|
73225
|
-
if (rows.length === 0) {
|
|
73226
|
-
console.log(import_picocolors4.default.dim(" No queue edges indexed. Run: horus index"));
|
|
73227
|
-
} else {
|
|
73228
|
-
const byQueue = buildQueueMap(rows);
|
|
73229
|
-
printTopology(byQueue);
|
|
73230
|
-
}
|
|
73231
|
-
console.log("");
|
|
73232
|
-
if (opts.live) {
|
|
73233
|
-
await runLiveMode(config, rows, name);
|
|
73234
|
-
} else {
|
|
73235
|
-
console.log(
|
|
73236
|
-
import_picocolors4.default.dim(
|
|
73237
|
-
" Tip: run horus queues --live to show real-time Redis/BullMQ depths and failed-job counts."
|
|
73238
|
-
)
|
|
73239
|
-
);
|
|
73240
|
-
}
|
|
73241
|
-
} finally {
|
|
73242
|
-
await sql2.end();
|
|
73243
|
-
}
|
|
73244
|
-
return 0;
|
|
73245
|
-
} catch (err) {
|
|
73246
|
-
console.error(import_picocolors4.default.red(err.message));
|
|
73247
|
-
return 1;
|
|
73248
|
-
}
|
|
73249
|
-
}
|
|
73250
|
-
function buildQueueMap(rows) {
|
|
73251
|
-
const byQueue = /* @__PURE__ */ new Map();
|
|
73252
|
-
for (const row of rows) {
|
|
73253
|
-
const existing = byQueue.get(row.queueName);
|
|
73254
|
-
if (existing) {
|
|
73255
|
-
existing.push(row);
|
|
73256
|
-
} else {
|
|
73257
|
-
byQueue.set(row.queueName, [row]);
|
|
73258
|
-
}
|
|
73259
|
-
}
|
|
73260
|
-
return byQueue;
|
|
73261
|
-
}
|
|
73262
|
-
function endpoints(edges, symKey, fileKey) {
|
|
73263
|
-
const seen = /* @__PURE__ */ new Map();
|
|
73264
|
-
for (const e of edges) {
|
|
73265
|
-
const symbol = e[symKey];
|
|
73266
|
-
if (!symbol) continue;
|
|
73267
|
-
if (!seen.has(symbol)) seen.set(symbol, { symbol, file: e[fileKey] ?? null });
|
|
73268
|
-
}
|
|
73269
|
-
return [...seen.values()];
|
|
73270
|
-
}
|
|
73271
|
-
function topologyToJson(byQueue) {
|
|
73272
|
-
return [...byQueue.entries()].map(([queueName, edges]) => ({
|
|
73273
|
-
queueName,
|
|
73274
|
-
producers: endpoints(edges, "producerSymbol", "producerFile"),
|
|
73275
|
-
workers: endpoints(edges, "workerSymbol", "workerFile")
|
|
73276
|
-
}));
|
|
73277
|
-
}
|
|
73278
|
-
async function gatherLiveState(config, rows, nameFilter) {
|
|
73279
|
-
let renv;
|
|
73280
|
-
try {
|
|
73281
|
-
renv = resolveEnvironment(config);
|
|
73282
|
-
} catch (err) {
|
|
73283
|
-
return { ok: false, error: err.message };
|
|
73284
|
-
}
|
|
73285
|
-
const queueProvider = queueForEnv(renv);
|
|
73286
|
-
if (!queueProvider) return { ok: false, error: "Redis not configured" };
|
|
73287
|
-
try {
|
|
73288
|
-
const health = await queueProvider.health();
|
|
73289
|
-
if (!health.ok) return { ok: false, error: health.detail };
|
|
73290
|
-
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
73291
|
-
let queueNames;
|
|
73292
|
-
if (nameFilter !== void 0) {
|
|
73293
|
-
queueNames = [nameFilter];
|
|
73294
|
-
} else {
|
|
73295
|
-
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
73296
|
-
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
73297
|
-
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
73298
|
-
}
|
|
73299
|
-
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
73300
|
-
return {
|
|
73301
|
-
ok: true,
|
|
73302
|
-
prefix: state.prefix,
|
|
73303
|
-
collectedAt: state.collectedAt,
|
|
73304
|
-
queues: state.queues.map((q) => ({ ...q, runtimeOnly: !staticNames.has(q.queueName) }))
|
|
73305
|
-
};
|
|
73306
|
-
} finally {
|
|
73307
|
-
await queueProvider.close().catch(() => {
|
|
73308
|
-
});
|
|
73309
|
-
}
|
|
73310
|
-
}
|
|
73311
|
-
function printTopology(byQueue) {
|
|
73312
|
-
for (const [queueName, edges] of byQueue) {
|
|
73313
|
-
console.log(import_picocolors4.default.bold(queueName));
|
|
73314
|
-
const producerSet = /* @__PURE__ */ new Set();
|
|
73315
|
-
const producerDetails = /* @__PURE__ */ new Map();
|
|
73316
|
-
for (const edge of edges) {
|
|
73317
|
-
if (edge.producerSymbol) {
|
|
73318
|
-
producerSet.add(edge.producerSymbol);
|
|
73319
|
-
if (edge.producerFile) producerDetails.set(edge.producerSymbol, edge.producerFile);
|
|
73320
|
-
}
|
|
73321
|
-
}
|
|
73322
|
-
if (producerSet.size === 0) {
|
|
73323
|
-
console.log(" producers: " + import_picocolors4.default.dim("none"));
|
|
73324
|
-
} else {
|
|
73325
|
-
const list = Array.from(producerSet).map((sym) => {
|
|
73326
|
-
const file = producerDetails.get(sym);
|
|
73327
|
-
return file ? `${sym} (${file})` : sym;
|
|
73328
|
-
}).join(", ");
|
|
73329
|
-
console.log(" producers: " + list);
|
|
73330
|
-
}
|
|
73331
|
-
const workerSet = /* @__PURE__ */ new Set();
|
|
73332
|
-
const workerDetails = /* @__PURE__ */ new Map();
|
|
73333
|
-
for (const edge of edges) {
|
|
73334
|
-
if (edge.workerSymbol) {
|
|
73335
|
-
workerSet.add(edge.workerSymbol);
|
|
73336
|
-
if (edge.workerFile) workerDetails.set(edge.workerSymbol, edge.workerFile);
|
|
73337
|
-
}
|
|
73338
|
-
}
|
|
73339
|
-
if (workerSet.size === 0) {
|
|
73340
|
-
console.log(" workers: " + import_picocolors4.default.dim("none"));
|
|
73341
|
-
} else {
|
|
73342
|
-
const list = Array.from(workerSet).map((sym) => {
|
|
73343
|
-
const file = workerDetails.get(sym);
|
|
73344
|
-
return file ? `${sym} (${file})` : sym;
|
|
73345
|
-
}).join(", ");
|
|
73346
|
-
console.log(" workers: " + list);
|
|
73347
|
-
}
|
|
73348
|
-
console.log("");
|
|
73349
|
-
}
|
|
73350
|
-
}
|
|
73351
|
-
async function runLiveMode(config, rows, nameFilter) {
|
|
73352
|
-
let renv;
|
|
73353
|
-
try {
|
|
73354
|
-
renv = resolveEnvironment(config);
|
|
73355
|
-
} catch (err) {
|
|
73356
|
-
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
73357
|
-
console.log(import_picocolors4.default.yellow(` \u26A0 Cannot resolve environment: ${err.message}`));
|
|
73358
|
-
return;
|
|
73359
|
-
}
|
|
73360
|
-
const queueProvider = queueForEnv(renv);
|
|
73361
|
-
if (!queueProvider) {
|
|
73362
|
-
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
73363
|
-
console.log(
|
|
73364
|
-
import_picocolors4.default.yellow(" \u25CB Redis not configured \u2014 run: ") + import_picocolors4.default.bold("horus connect redis")
|
|
73365
|
-
);
|
|
73366
|
-
return;
|
|
73367
|
-
}
|
|
73368
|
-
let headerPrinted = false;
|
|
73369
|
-
try {
|
|
73370
|
-
const health = await queueProvider.health();
|
|
73371
|
-
if (!health.ok) {
|
|
73372
|
-
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
73373
|
-
console.log(import_picocolors4.default.red(` \u2717 Redis unreachable: ${health.detail}`));
|
|
73374
|
-
return;
|
|
73375
|
-
}
|
|
73376
|
-
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
73377
|
-
let queueNames;
|
|
73378
|
-
if (nameFilter !== void 0) {
|
|
73379
|
-
queueNames = [nameFilter];
|
|
73380
|
-
} else {
|
|
73381
|
-
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
73382
|
-
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
73383
|
-
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
73384
|
-
}
|
|
73385
|
-
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
73386
|
-
const collectedAt = new Date(state.collectedAt).toLocaleTimeString();
|
|
73387
|
-
console.log(
|
|
73388
|
-
import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(` \xB7 source: Redis/BullMQ (prefix: ${state.prefix}) \xB7 collected: ${collectedAt}`)
|
|
73389
|
-
);
|
|
73390
|
-
headerPrinted = true;
|
|
73391
|
-
console.log("");
|
|
73392
|
-
if (state.queues.length === 0) {
|
|
73393
|
-
console.log(import_picocolors4.default.dim(" No queues found in Redis."));
|
|
73394
|
-
console.log(
|
|
73395
|
-
import_picocolors4.default.dim(
|
|
73396
|
-
" If queues exist under a custom prefix, set the BullMQ prefix in your connector config."
|
|
73397
|
-
)
|
|
73398
|
-
);
|
|
73399
|
-
return;
|
|
73400
|
-
}
|
|
73401
|
-
printLiveTable(state.queues, staticNames);
|
|
73402
|
-
} catch (err) {
|
|
73403
|
-
if (!headerPrinted) {
|
|
73404
|
-
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
73405
|
-
}
|
|
73406
|
-
console.log(import_picocolors4.default.red(` \u2717 ${err.message}`));
|
|
73407
|
-
} finally {
|
|
73408
|
-
await queueProvider.close().catch(() => {
|
|
73409
|
-
});
|
|
73410
|
-
}
|
|
73411
|
-
}
|
|
73412
|
-
function printLiveTable(queues, staticNames = /* @__PURE__ */ new Set()) {
|
|
73413
|
-
const nameWidth = Math.max(10, ...queues.map((q) => q.queueName.length));
|
|
73414
|
-
const numWidth = 7;
|
|
73415
|
-
const header = " " + "queue".padEnd(nameWidth) + " " + "waiting".padStart(numWidth) + " " + "active".padStart(numWidth) + " " + "failed".padStart(numWidth) + " " + "delayed".padStart(numWidth) + " " + "paused".padStart(numWidth);
|
|
73416
|
-
console.log(import_picocolors4.default.dim(header));
|
|
73417
|
-
console.log(import_picocolors4.default.dim(" " + "\u2500".repeat(header.length - 2)));
|
|
73418
|
-
for (const q of queues) {
|
|
73419
|
-
const hasIssue = q.failed > 0 || q.waiting > 100 || q.delayed > 50 || q.isPaused;
|
|
73420
|
-
const color = hasIssue ? import_picocolors4.default.yellow : (s) => s;
|
|
73421
|
-
const row = " " + q.queueName.padEnd(nameWidth) + " " + String(q.waiting).padStart(numWidth) + " " + String(q.active).padStart(numWidth) + " " + String(q.failed).padStart(numWidth) + " " + String(q.delayed).padStart(numWidth) + " " + (q.isPaused ? import_picocolors4.default.yellow("paused") : String(q.paused).padStart(numWidth));
|
|
73422
|
-
console.log(color(row));
|
|
73423
|
-
if (!staticNames.has(q.queueName)) {
|
|
73424
|
-
console.log(import_picocolors4.default.dim(" runtime-only \xB7 no static producer/worker mapping"));
|
|
73425
|
-
}
|
|
73426
|
-
if (q.oldestWaitingMs !== void 0) {
|
|
73427
|
-
const age = formatAge(q.oldestWaitingMs);
|
|
73428
|
-
console.log(import_picocolors4.default.dim(` oldest waiting: ${age}`));
|
|
73429
|
-
}
|
|
73430
|
-
if (q.failedBreakdown && q.failedBreakdown.length > 0) {
|
|
73431
|
-
for (const { reason, count } of q.failedBreakdown.slice(0, 3)) {
|
|
73432
|
-
console.log(import_picocolors4.default.red(` \u2717 [${count}x] ${reason}`));
|
|
73433
|
-
}
|
|
73434
|
-
}
|
|
73435
|
-
}
|
|
73436
|
-
console.log("");
|
|
73437
|
-
}
|
|
73438
|
-
function formatAge(ms) {
|
|
73439
|
-
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
73440
|
-
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
73441
|
-
if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
|
|
73442
|
-
return `${Math.round(ms / 864e5)}d`;
|
|
73443
|
-
}
|
|
73444
|
-
|
|
73445
|
-
// ../../packages/cli/src/commands/investigate.ts
|
|
73446
|
-
init_cjs_shims();
|
|
73447
|
-
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
73448
73216
|
|
|
73449
73217
|
// ../../packages/ai/src/index.ts
|
|
73450
73218
|
init_cjs_shims();
|
|
@@ -73681,6 +73449,15 @@ var AnthropicNarrativeProvider = class {
|
|
|
73681
73449
|
const raw = await this.callApi(prompt);
|
|
73682
73450
|
return parseOutput(raw, input, ceiling);
|
|
73683
73451
|
}
|
|
73452
|
+
/**
|
|
73453
|
+
* HOR-211 — generic command-level interpretation. Reuses the same Messages API
|
|
73454
|
+
* call as render(), but with a caller-supplied command prompt (built by
|
|
73455
|
+
* buildInterpretationPrompt) instead of the investigation narrative prompt.
|
|
73456
|
+
* Returns the raw model text; the shared helper handles parsing/rendering.
|
|
73457
|
+
*/
|
|
73458
|
+
async interpret(prompt) {
|
|
73459
|
+
return this.callApi(prompt);
|
|
73460
|
+
}
|
|
73684
73461
|
async callApi(prompt) {
|
|
73685
73462
|
const response = await fetch(`${this.baseUrl}/v1/messages`, {
|
|
73686
73463
|
method: "POST",
|
|
@@ -73923,8 +73700,445 @@ init_cjs_shims();
|
|
|
73923
73700
|
// ../../packages/ai/src/provider-execution.ts
|
|
73924
73701
|
init_cjs_shims();
|
|
73925
73702
|
|
|
73926
|
-
// ../../packages/
|
|
73927
|
-
|
|
73703
|
+
// ../../packages/ai/src/interpretation.ts
|
|
73704
|
+
init_cjs_shims();
|
|
73705
|
+
var INTERPRETATION_GROUNDING_RULES = [
|
|
73706
|
+
"You are interpreting Horus evidence, not gathering new evidence.",
|
|
73707
|
+
"Do not invent files, commits, services, queues, logs, metrics, dashboards, owners, or timestamps.",
|
|
73708
|
+
"Use only the evidence provided above \u2014 any fact not present in it is a hallucination.",
|
|
73709
|
+
'Clearly separate "Evidence used" from "Interpretation".',
|
|
73710
|
+
"State your confidence and call out any missing evidence that would change the conclusion.",
|
|
73711
|
+
"When suggesting next checks, prefer exact follow-up Horus commands."
|
|
73712
|
+
];
|
|
73713
|
+
var PROMPT_KIND_GUIDANCE = {
|
|
73714
|
+
"change-risk": "Assess which changes carry the most risk and why, ranking by likely impact, grounded only in the evidence.",
|
|
73715
|
+
"timeline-narrative": "Narrate the ordering of changes and evidence into phases; flag suspicious ordering and gaps in coverage.",
|
|
73716
|
+
"blast-radius": "Explain severity, likely user impact, containment, and safe mitigations for the affected dependencies.",
|
|
73717
|
+
"system-explanation": "Explain how this system/subsystem works at onboarding quality, grounded in the evidence.",
|
|
73718
|
+
readiness: "Summarize readiness and risk, then prioritize what to do next before proceeding.",
|
|
73719
|
+
"evidence-summary": "Summarize what the evidence shows. Do not assert a root cause the evidence does not support."
|
|
73720
|
+
};
|
|
73721
|
+
function stringifyEvidence(evidence2) {
|
|
73722
|
+
if (typeof evidence2 === "string") return evidence2;
|
|
73723
|
+
try {
|
|
73724
|
+
return JSON.stringify(evidence2, null, 2);
|
|
73725
|
+
} catch {
|
|
73726
|
+
return String(evidence2);
|
|
73727
|
+
}
|
|
73728
|
+
}
|
|
73729
|
+
function buildInterpretationPrompt(req) {
|
|
73730
|
+
const guidance = PROMPT_KIND_GUIDANCE[req.promptKind] ?? PROMPT_KIND_GUIDANCE["evidence-summary"];
|
|
73731
|
+
const evidenceBlock = stringifyEvidence(req.evidence);
|
|
73732
|
+
const rules = INTERPRETATION_GROUNDING_RULES.map((r) => `- ${r}`).join("\n");
|
|
73733
|
+
return `You are the Horus AI interpretation layer for the \`${req.command}\` command.
|
|
73734
|
+
Task kind: ${req.promptKind}. ${guidance}
|
|
73735
|
+
${req.userIntent ? `
|
|
73736
|
+
User intent: ${req.userIntent}
|
|
73737
|
+
` : ""}
|
|
73738
|
+
Evidence used (this is the ONLY ground truth \u2014 interpret it, do not extend it):
|
|
73739
|
+
${evidenceBlock}
|
|
73740
|
+
|
|
73741
|
+
Output contract:
|
|
73742
|
+
${req.outputContract}
|
|
73743
|
+
|
|
73744
|
+
Rules:
|
|
73745
|
+
${rules}
|
|
73746
|
+
- End with a "Confidence:" line (low | medium | high) and a "Next checks:" list of concrete Horus commands.`;
|
|
73747
|
+
}
|
|
73748
|
+
async function generateInterpretation(req, provider, opts) {
|
|
73749
|
+
const base2 = {
|
|
73750
|
+
ok: false,
|
|
73751
|
+
command: req.command,
|
|
73752
|
+
promptKind: req.promptKind,
|
|
73753
|
+
...opts?.model ? { model: opts.model } : {}
|
|
73754
|
+
};
|
|
73755
|
+
if (!provider) {
|
|
73756
|
+
return {
|
|
73757
|
+
...base2,
|
|
73758
|
+
warning: "No AI provider configured. Run `horus connect ai` or set ANTHROPIC_API_KEY to enable --ai."
|
|
73759
|
+
};
|
|
73760
|
+
}
|
|
73761
|
+
const prompt = buildInterpretationPrompt(req);
|
|
73762
|
+
try {
|
|
73763
|
+
const text2 = (await provider.interpret(prompt))?.trim() ?? "";
|
|
73764
|
+
if (!text2) {
|
|
73765
|
+
return { ...base2, provider: provider.name, warning: "AI provider returned an empty response." };
|
|
73766
|
+
}
|
|
73767
|
+
return {
|
|
73768
|
+
ok: true,
|
|
73769
|
+
command: req.command,
|
|
73770
|
+
promptKind: req.promptKind,
|
|
73771
|
+
provider: provider.name,
|
|
73772
|
+
...opts?.model ? { model: opts.model } : {},
|
|
73773
|
+
text: text2
|
|
73774
|
+
};
|
|
73775
|
+
} catch (err) {
|
|
73776
|
+
return {
|
|
73777
|
+
...base2,
|
|
73778
|
+
provider: provider.name,
|
|
73779
|
+
warning: `AI interpretation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
73780
|
+
};
|
|
73781
|
+
}
|
|
73782
|
+
}
|
|
73783
|
+
function renderInterpretation(result) {
|
|
73784
|
+
const attribution = result.model ? ` (${result.provider ?? "ai"}, ${result.model})` : result.provider ? ` (${result.provider})` : "";
|
|
73785
|
+
const header = `\u2500\u2500 AI Interpretation${attribution} \u2500\u2500`;
|
|
73786
|
+
if (!result.ok) {
|
|
73787
|
+
return `${header}
|
|
73788
|
+
${result.warning ?? "AI interpretation unavailable."}`;
|
|
73789
|
+
}
|
|
73790
|
+
return `${header}
|
|
73791
|
+
${result.text ?? ""}`;
|
|
73792
|
+
}
|
|
73793
|
+
|
|
73794
|
+
// ../../packages/cli/src/lib/ai-provider.ts
|
|
73795
|
+
init_cjs_shims();
|
|
73796
|
+
var DEFAULT_MODEL = "claude-opus-4-8";
|
|
73797
|
+
async function resolveAiCredentials(opts) {
|
|
73798
|
+
let apiKey;
|
|
73799
|
+
let savedModel;
|
|
73800
|
+
try {
|
|
73801
|
+
const config = await loadConfig(opts.config);
|
|
73802
|
+
const ai = resolveAiSettings(config);
|
|
73803
|
+
apiKey = ai.anthropicApiKey;
|
|
73804
|
+
savedModel = ai.model;
|
|
73805
|
+
} catch {
|
|
73806
|
+
}
|
|
73807
|
+
apiKey = apiKey ?? process.env["ANTHROPIC_API_KEY"] ?? void 0;
|
|
73808
|
+
return { apiKey, model: opts.modelOverride ?? savedModel ?? DEFAULT_MODEL };
|
|
73809
|
+
}
|
|
73810
|
+
async function buildNarrativeProvider(opts) {
|
|
73811
|
+
let apiKey;
|
|
73812
|
+
let savedModel;
|
|
73813
|
+
try {
|
|
73814
|
+
const config = await loadConfig(opts.config);
|
|
73815
|
+
const ai = resolveAiSettings(config);
|
|
73816
|
+
apiKey = ai.anthropicApiKey;
|
|
73817
|
+
savedModel = ai.model;
|
|
73818
|
+
} catch {
|
|
73819
|
+
}
|
|
73820
|
+
const model = opts.modelOverride ?? savedModel ?? DEFAULT_MODEL;
|
|
73821
|
+
const provider = new AnthropicNarrativeProvider({
|
|
73822
|
+
model,
|
|
73823
|
+
...apiKey !== void 0 ? { apiKey } : {}
|
|
73824
|
+
});
|
|
73825
|
+
return { provider, model };
|
|
73826
|
+
}
|
|
73827
|
+
async function renderAiInterpretation(opts) {
|
|
73828
|
+
const { config, modelOverride, provider: injected, command, evidence: evidence2, promptKind, outputContract, userIntent } = opts;
|
|
73829
|
+
const request = {
|
|
73830
|
+
command,
|
|
73831
|
+
evidence: evidence2,
|
|
73832
|
+
promptKind,
|
|
73833
|
+
outputContract,
|
|
73834
|
+
...userIntent ? { userIntent } : {}
|
|
73835
|
+
};
|
|
73836
|
+
if (injected) {
|
|
73837
|
+
return generateInterpretation(request, injected);
|
|
73838
|
+
}
|
|
73839
|
+
const { apiKey, model } = await resolveAiCredentials({ config, modelOverride });
|
|
73840
|
+
if (!apiKey) {
|
|
73841
|
+
return generateInterpretation(request, null, { model });
|
|
73842
|
+
}
|
|
73843
|
+
const provider = new AnthropicNarrativeProvider({ model, apiKey });
|
|
73844
|
+
return generateInterpretation(request, provider, { model });
|
|
73845
|
+
}
|
|
73846
|
+
|
|
73847
|
+
// ../../packages/cli/src/commands/queues.ts
|
|
73848
|
+
var QUEUES_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
73849
|
+
|
|
73850
|
+
Evidence used
|
|
73851
|
+
- Queue names, waiting/active/failed/delayed counts, and producer/worker mappings Horus found
|
|
73852
|
+
|
|
73853
|
+
What stands out
|
|
73854
|
+
- Queues with high failed counts, unexpected backlog, or paused state
|
|
73855
|
+
- Runtime-only queues (live in Redis, no static producer/worker mapping)
|
|
73856
|
+
- Producer/worker mismatches or orphaned queues
|
|
73857
|
+
|
|
73858
|
+
What this may indicate
|
|
73859
|
+
- Use "may suggest", "is consistent with", or "could indicate" \u2014 never "proves"
|
|
73860
|
+
- Do not invent queue names, Redis keys, or job data not in the evidence
|
|
73861
|
+
|
|
73862
|
+
What is not proven
|
|
73863
|
+
- Claims about job contents, specific error messages, or data inside failed jobs
|
|
73864
|
+
|
|
73865
|
+
Next checks
|
|
73866
|
+
- Exact Horus commands or Redis/BullMQ checks to inspect next`;
|
|
73867
|
+
async function runQueues(name, opts) {
|
|
73868
|
+
try {
|
|
73869
|
+
const config = await loadConfig(opts.config, { name: opts.name });
|
|
73870
|
+
const { db, sql: sql2 } = createDb(config.database.url);
|
|
73871
|
+
try {
|
|
73872
|
+
let project = opts.project;
|
|
73873
|
+
if (project === void 0) {
|
|
73874
|
+
try {
|
|
73875
|
+
project = resolveEnvironment(config, { project: opts.project }).project;
|
|
73876
|
+
} catch {
|
|
73877
|
+
}
|
|
73878
|
+
}
|
|
73879
|
+
const rows = await listQueueEdges(db, { project, queueName: name });
|
|
73880
|
+
if (opts.json) {
|
|
73881
|
+
const topology = topologyToJson(buildQueueMap(rows));
|
|
73882
|
+
const out = { project: project ?? null, topology };
|
|
73883
|
+
if (opts.live) out["live"] = await gatherLiveState(config, rows, name);
|
|
73884
|
+
console.log(JSON.stringify(out, null, 2));
|
|
73885
|
+
return 0;
|
|
73886
|
+
}
|
|
73887
|
+
console.log(
|
|
73888
|
+
import_picocolors4.default.bold("Queue topology") + import_picocolors4.default.dim(" \xB7 source: code / source intelligence \xB7 static (run horus index to refresh)")
|
|
73889
|
+
);
|
|
73890
|
+
console.log("");
|
|
73891
|
+
if (rows.length === 0) {
|
|
73892
|
+
console.log(import_picocolors4.default.dim(" No queue edges indexed. Run: horus index"));
|
|
73893
|
+
} else {
|
|
73894
|
+
const byQueue = buildQueueMap(rows);
|
|
73895
|
+
printTopology(byQueue);
|
|
73896
|
+
}
|
|
73897
|
+
console.log("");
|
|
73898
|
+
if (opts.live) {
|
|
73899
|
+
await runLiveMode(config, rows, name, opts.ai ? {
|
|
73900
|
+
config: opts.config,
|
|
73901
|
+
aiModel: opts.aiModel,
|
|
73902
|
+
_aiProvider: opts._aiProvider
|
|
73903
|
+
} : void 0);
|
|
73904
|
+
} else {
|
|
73905
|
+
console.log(
|
|
73906
|
+
import_picocolors4.default.dim(
|
|
73907
|
+
" Tip: run horus queues --live to show real-time Redis/BullMQ depths and failed-job counts."
|
|
73908
|
+
)
|
|
73909
|
+
);
|
|
73910
|
+
if (opts.ai) {
|
|
73911
|
+
console.log(import_picocolors4.default.dim(" Tip: --ai is most useful with --live (horus queues --live --ai)."));
|
|
73912
|
+
}
|
|
73913
|
+
}
|
|
73914
|
+
} finally {
|
|
73915
|
+
await sql2.end();
|
|
73916
|
+
}
|
|
73917
|
+
return 0;
|
|
73918
|
+
} catch (err) {
|
|
73919
|
+
console.error(import_picocolors4.default.red(err.message));
|
|
73920
|
+
return 1;
|
|
73921
|
+
}
|
|
73922
|
+
}
|
|
73923
|
+
function buildQueueMap(rows) {
|
|
73924
|
+
const byQueue = /* @__PURE__ */ new Map();
|
|
73925
|
+
for (const row of rows) {
|
|
73926
|
+
const existing = byQueue.get(row.queueName);
|
|
73927
|
+
if (existing) {
|
|
73928
|
+
existing.push(row);
|
|
73929
|
+
} else {
|
|
73930
|
+
byQueue.set(row.queueName, [row]);
|
|
73931
|
+
}
|
|
73932
|
+
}
|
|
73933
|
+
return byQueue;
|
|
73934
|
+
}
|
|
73935
|
+
function endpoints(edges, symKey, fileKey) {
|
|
73936
|
+
const seen = /* @__PURE__ */ new Map();
|
|
73937
|
+
for (const e of edges) {
|
|
73938
|
+
const symbol = e[symKey];
|
|
73939
|
+
if (!symbol) continue;
|
|
73940
|
+
if (!seen.has(symbol)) seen.set(symbol, { symbol, file: e[fileKey] ?? null });
|
|
73941
|
+
}
|
|
73942
|
+
return [...seen.values()];
|
|
73943
|
+
}
|
|
73944
|
+
function topologyToJson(byQueue) {
|
|
73945
|
+
return [...byQueue.entries()].map(([queueName, edges]) => ({
|
|
73946
|
+
queueName,
|
|
73947
|
+
producers: endpoints(edges, "producerSymbol", "producerFile"),
|
|
73948
|
+
workers: endpoints(edges, "workerSymbol", "workerFile")
|
|
73949
|
+
}));
|
|
73950
|
+
}
|
|
73951
|
+
async function gatherLiveState(config, rows, nameFilter) {
|
|
73952
|
+
let renv;
|
|
73953
|
+
try {
|
|
73954
|
+
renv = resolveEnvironment(config);
|
|
73955
|
+
} catch (err) {
|
|
73956
|
+
return { ok: false, error: err.message };
|
|
73957
|
+
}
|
|
73958
|
+
const queueProvider = queueForEnv(renv);
|
|
73959
|
+
if (!queueProvider) return { ok: false, error: "Redis not configured" };
|
|
73960
|
+
try {
|
|
73961
|
+
const health = await queueProvider.health();
|
|
73962
|
+
if (!health.ok) return { ok: false, error: health.detail };
|
|
73963
|
+
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
73964
|
+
let queueNames;
|
|
73965
|
+
if (nameFilter !== void 0) {
|
|
73966
|
+
queueNames = [nameFilter];
|
|
73967
|
+
} else {
|
|
73968
|
+
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
73969
|
+
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
73970
|
+
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
73971
|
+
}
|
|
73972
|
+
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
73973
|
+
return {
|
|
73974
|
+
ok: true,
|
|
73975
|
+
prefix: state.prefix,
|
|
73976
|
+
collectedAt: state.collectedAt,
|
|
73977
|
+
queues: state.queues.map((q) => ({ ...q, runtimeOnly: !staticNames.has(q.queueName) }))
|
|
73978
|
+
};
|
|
73979
|
+
} finally {
|
|
73980
|
+
await queueProvider.close().catch(() => {
|
|
73981
|
+
});
|
|
73982
|
+
}
|
|
73983
|
+
}
|
|
73984
|
+
function printTopology(byQueue) {
|
|
73985
|
+
for (const [queueName, edges] of byQueue) {
|
|
73986
|
+
console.log(import_picocolors4.default.bold(queueName));
|
|
73987
|
+
const producerSet = /* @__PURE__ */ new Set();
|
|
73988
|
+
const producerDetails = /* @__PURE__ */ new Map();
|
|
73989
|
+
for (const edge of edges) {
|
|
73990
|
+
if (edge.producerSymbol) {
|
|
73991
|
+
producerSet.add(edge.producerSymbol);
|
|
73992
|
+
if (edge.producerFile) producerDetails.set(edge.producerSymbol, edge.producerFile);
|
|
73993
|
+
}
|
|
73994
|
+
}
|
|
73995
|
+
if (producerSet.size === 0) {
|
|
73996
|
+
console.log(" producers: " + import_picocolors4.default.dim("none"));
|
|
73997
|
+
} else {
|
|
73998
|
+
const list = Array.from(producerSet).map((sym) => {
|
|
73999
|
+
const file = producerDetails.get(sym);
|
|
74000
|
+
return file ? `${sym} (${file})` : sym;
|
|
74001
|
+
}).join(", ");
|
|
74002
|
+
console.log(" producers: " + list);
|
|
74003
|
+
}
|
|
74004
|
+
const workerSet = /* @__PURE__ */ new Set();
|
|
74005
|
+
const workerDetails = /* @__PURE__ */ new Map();
|
|
74006
|
+
for (const edge of edges) {
|
|
74007
|
+
if (edge.workerSymbol) {
|
|
74008
|
+
workerSet.add(edge.workerSymbol);
|
|
74009
|
+
if (edge.workerFile) workerDetails.set(edge.workerSymbol, edge.workerFile);
|
|
74010
|
+
}
|
|
74011
|
+
}
|
|
74012
|
+
if (workerSet.size === 0) {
|
|
74013
|
+
console.log(" workers: " + import_picocolors4.default.dim("none"));
|
|
74014
|
+
} else {
|
|
74015
|
+
const list = Array.from(workerSet).map((sym) => {
|
|
74016
|
+
const file = workerDetails.get(sym);
|
|
74017
|
+
return file ? `${sym} (${file})` : sym;
|
|
74018
|
+
}).join(", ");
|
|
74019
|
+
console.log(" workers: " + list);
|
|
74020
|
+
}
|
|
74021
|
+
console.log("");
|
|
74022
|
+
}
|
|
74023
|
+
}
|
|
74024
|
+
async function runLiveMode(config, rows, nameFilter, aiOpts) {
|
|
74025
|
+
let renv;
|
|
74026
|
+
try {
|
|
74027
|
+
renv = resolveEnvironment(config);
|
|
74028
|
+
} catch (err) {
|
|
74029
|
+
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
74030
|
+
console.log(import_picocolors4.default.yellow(` \u26A0 Cannot resolve environment: ${err.message}`));
|
|
74031
|
+
return;
|
|
74032
|
+
}
|
|
74033
|
+
const queueProvider = queueForEnv(renv);
|
|
74034
|
+
if (!queueProvider) {
|
|
74035
|
+
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
74036
|
+
console.log(
|
|
74037
|
+
import_picocolors4.default.yellow(" \u25CB Redis not configured \u2014 run: ") + import_picocolors4.default.bold("horus connect redis")
|
|
74038
|
+
);
|
|
74039
|
+
return;
|
|
74040
|
+
}
|
|
74041
|
+
let headerPrinted = false;
|
|
74042
|
+
try {
|
|
74043
|
+
const health = await queueProvider.health();
|
|
74044
|
+
if (!health.ok) {
|
|
74045
|
+
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
74046
|
+
console.log(import_picocolors4.default.red(` \u2717 Redis unreachable: ${health.detail}`));
|
|
74047
|
+
return;
|
|
74048
|
+
}
|
|
74049
|
+
const staticNames = new Set(buildQueueMap(rows).keys());
|
|
74050
|
+
let queueNames;
|
|
74051
|
+
if (nameFilter !== void 0) {
|
|
74052
|
+
queueNames = [nameFilter];
|
|
74053
|
+
} else {
|
|
74054
|
+
const discovered = await queueProvider.discoverQueues().catch(() => []);
|
|
74055
|
+
const union2 = /* @__PURE__ */ new Set([...staticNames, ...discovered]);
|
|
74056
|
+
queueNames = union2.size > 0 ? [...union2] : void 0;
|
|
74057
|
+
}
|
|
74058
|
+
const state = await queueProvider.analyzeQueues({ queueNames });
|
|
74059
|
+
const collectedAt = new Date(state.collectedAt).toLocaleTimeString();
|
|
74060
|
+
console.log(
|
|
74061
|
+
import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(` \xB7 source: Redis/BullMQ (prefix: ${state.prefix}) \xB7 collected: ${collectedAt}`)
|
|
74062
|
+
);
|
|
74063
|
+
headerPrinted = true;
|
|
74064
|
+
console.log("");
|
|
74065
|
+
if (state.queues.length === 0) {
|
|
74066
|
+
console.log(import_picocolors4.default.dim(" No queues found in Redis."));
|
|
74067
|
+
console.log(
|
|
74068
|
+
import_picocolors4.default.dim(
|
|
74069
|
+
" If queues exist under a custom prefix, set the BullMQ prefix in your connector config."
|
|
74070
|
+
)
|
|
74071
|
+
);
|
|
74072
|
+
return;
|
|
74073
|
+
}
|
|
74074
|
+
printLiveTable(state.queues, staticNames);
|
|
74075
|
+
if (aiOpts) {
|
|
74076
|
+
const evidence2 = {
|
|
74077
|
+
prefix: state.prefix,
|
|
74078
|
+
collectedAt: state.collectedAt,
|
|
74079
|
+
queues: state.queues.map((q) => ({ ...q, runtimeOnly: !staticNames.has(q.queueName) }))
|
|
74080
|
+
};
|
|
74081
|
+
const result = await renderAiInterpretation({
|
|
74082
|
+
command: "queues",
|
|
74083
|
+
evidence: evidence2,
|
|
74084
|
+
promptKind: "evidence-summary",
|
|
74085
|
+
outputContract: QUEUES_AI_CONTRACT,
|
|
74086
|
+
config: aiOpts.config,
|
|
74087
|
+
modelOverride: aiOpts.aiModel,
|
|
74088
|
+
provider: aiOpts._aiProvider
|
|
74089
|
+
});
|
|
74090
|
+
console.log("\n" + renderInterpretation(result));
|
|
74091
|
+
if (!result.ok) {
|
|
74092
|
+
console.error(import_picocolors4.default.yellow(`[ai] ${result.warning}`));
|
|
74093
|
+
}
|
|
74094
|
+
}
|
|
74095
|
+
} catch (err) {
|
|
74096
|
+
if (!headerPrinted) {
|
|
74097
|
+
console.log(import_picocolors4.default.bold("Live queue state") + import_picocolors4.default.dim(" \xB7 source: Redis/BullMQ"));
|
|
74098
|
+
}
|
|
74099
|
+
console.log(import_picocolors4.default.red(` \u2717 ${err.message}`));
|
|
74100
|
+
} finally {
|
|
74101
|
+
await queueProvider.close().catch(() => {
|
|
74102
|
+
});
|
|
74103
|
+
}
|
|
74104
|
+
}
|
|
74105
|
+
function printLiveTable(queues, staticNames = /* @__PURE__ */ new Set()) {
|
|
74106
|
+
const nameWidth = Math.max(10, ...queues.map((q) => q.queueName.length));
|
|
74107
|
+
const numWidth = 7;
|
|
74108
|
+
const header = " " + "queue".padEnd(nameWidth) + " " + "waiting".padStart(numWidth) + " " + "active".padStart(numWidth) + " " + "failed".padStart(numWidth) + " " + "delayed".padStart(numWidth) + " " + "paused".padStart(numWidth);
|
|
74109
|
+
console.log(import_picocolors4.default.dim(header));
|
|
74110
|
+
console.log(import_picocolors4.default.dim(" " + "\u2500".repeat(header.length - 2)));
|
|
74111
|
+
for (const q of queues) {
|
|
74112
|
+
const hasIssue = q.failed > 0 || q.waiting > 100 || q.delayed > 50 || q.isPaused;
|
|
74113
|
+
const color = hasIssue ? import_picocolors4.default.yellow : (s) => s;
|
|
74114
|
+
const row = " " + q.queueName.padEnd(nameWidth) + " " + String(q.waiting).padStart(numWidth) + " " + String(q.active).padStart(numWidth) + " " + String(q.failed).padStart(numWidth) + " " + String(q.delayed).padStart(numWidth) + " " + (q.isPaused ? import_picocolors4.default.yellow("paused") : String(q.paused).padStart(numWidth));
|
|
74115
|
+
console.log(color(row));
|
|
74116
|
+
if (!staticNames.has(q.queueName)) {
|
|
74117
|
+
console.log(import_picocolors4.default.dim(" runtime-only \xB7 no static producer/worker mapping"));
|
|
74118
|
+
}
|
|
74119
|
+
if (q.oldestWaitingMs !== void 0) {
|
|
74120
|
+
const age = formatAge(q.oldestWaitingMs);
|
|
74121
|
+
console.log(import_picocolors4.default.dim(` oldest waiting: ${age}`));
|
|
74122
|
+
}
|
|
74123
|
+
if (q.failedBreakdown && q.failedBreakdown.length > 0) {
|
|
74124
|
+
for (const { reason, count } of q.failedBreakdown.slice(0, 3)) {
|
|
74125
|
+
console.log(import_picocolors4.default.red(` \u2717 [${count}x] ${reason}`));
|
|
74126
|
+
}
|
|
74127
|
+
}
|
|
74128
|
+
}
|
|
74129
|
+
console.log("");
|
|
74130
|
+
}
|
|
74131
|
+
function formatAge(ms) {
|
|
74132
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
74133
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
74134
|
+
if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
|
|
74135
|
+
return `${Math.round(ms / 864e5)}d`;
|
|
74136
|
+
}
|
|
74137
|
+
|
|
74138
|
+
// ../../packages/cli/src/commands/investigate.ts
|
|
74139
|
+
init_cjs_shims();
|
|
74140
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
74141
|
+
function extractEvidenceExcerpt(e) {
|
|
73928
74142
|
if (!e.payload || typeof e.payload !== "object") return void 0;
|
|
73929
74143
|
const p = e.payload;
|
|
73930
74144
|
const candidate = (typeof p["pattern"] === "string" ? p["pattern"] : null) ?? (typeof p["message"] === "string" ? p["message"] : null) ?? (typeof p["label"] === "string" ? p["label"] : null) ?? (typeof p["summary"] === "string" ? p["summary"] : null) ?? (typeof p["valueLabel"] === "string" ? p["valueLabel"] : null);
|
|
@@ -74081,7 +74295,9 @@ async function runInvestigate(hint, opts) {
|
|
|
74081
74295
|
elasticsearch: !!renv.connectors.elasticsearch?.url,
|
|
74082
74296
|
grafana: !!renv.connectors.grafana?.url,
|
|
74083
74297
|
mongodb: !!renv.connectors.mongodb?.url,
|
|
74084
|
-
redis: !!renv.connectors.redis?.url
|
|
74298
|
+
redis: !!renv.connectors.redis?.url,
|
|
74299
|
+
// Queue runtime is configured iff a BullMQ provider was built (HOR-205).
|
|
74300
|
+
queue: !!queue
|
|
74085
74301
|
}
|
|
74086
74302
|
}
|
|
74087
74303
|
);
|
|
@@ -74146,6 +74362,23 @@ async function runInvestigate(hint, opts) {
|
|
|
74146
74362
|
// ../../packages/cli/src/commands/changes.ts
|
|
74147
74363
|
init_cjs_shims();
|
|
74148
74364
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
74365
|
+
var CHANGES_AI_CONTRACT = `Provide a clearly separated AI change-impact review with:
|
|
74366
|
+
|
|
74367
|
+
Highest-risk changes
|
|
74368
|
+
- Files, symbols, or flows most likely to matter for correctness or stability
|
|
74369
|
+
- Explain why based on affected flows and change types (added/removed/modified)
|
|
74370
|
+
|
|
74371
|
+
Review focus
|
|
74372
|
+
- What the reviewer should inspect first
|
|
74373
|
+
- Specific symbols or files worth extra attention
|
|
74374
|
+
|
|
74375
|
+
Testing suggestions
|
|
74376
|
+
- Specific smoke tests or checks based on affected flows
|
|
74377
|
+
- Only reference flows and symbols Horus found
|
|
74378
|
+
|
|
74379
|
+
Confidence / gaps
|
|
74380
|
+
- Where Horus evidence is strong (full flow coverage)
|
|
74381
|
+
- What is missing (e.g. no affected flows found \u2014 no source index, or no matching flows)`;
|
|
74149
74382
|
async function runChanges(base2, compare, opts) {
|
|
74150
74383
|
try {
|
|
74151
74384
|
const config = await loadConfig(opts.config);
|
|
@@ -74158,7 +74391,27 @@ async function runChanges(base2, compare, opts) {
|
|
|
74158
74391
|
return 1;
|
|
74159
74392
|
}
|
|
74160
74393
|
const report = await changeImpact({ base: base2, compare }, { code });
|
|
74161
|
-
|
|
74394
|
+
if (opts.json) {
|
|
74395
|
+
console.log(changeImpactToJSON(report));
|
|
74396
|
+
} else {
|
|
74397
|
+
console.log(renderChangeImpact(report));
|
|
74398
|
+
if (opts.ai) {
|
|
74399
|
+
const result = await renderAiInterpretation({
|
|
74400
|
+
command: "changes",
|
|
74401
|
+
userIntent: `base: ${base2}, compare: ${compare ?? "HEAD"}`,
|
|
74402
|
+
evidence: report,
|
|
74403
|
+
promptKind: "change-risk",
|
|
74404
|
+
outputContract: CHANGES_AI_CONTRACT,
|
|
74405
|
+
config: opts.config,
|
|
74406
|
+
modelOverride: opts.aiModel,
|
|
74407
|
+
provider: opts._aiProvider
|
|
74408
|
+
});
|
|
74409
|
+
console.log("\n" + renderInterpretation(result));
|
|
74410
|
+
if (!result.ok) {
|
|
74411
|
+
console.error(import_picocolors6.default.yellow(`[ai] ${result.warning}`));
|
|
74412
|
+
}
|
|
74413
|
+
}
|
|
74414
|
+
}
|
|
74162
74415
|
return 0;
|
|
74163
74416
|
} catch (err) {
|
|
74164
74417
|
console.error(import_picocolors6.default.red(err.message));
|
|
@@ -74175,6 +74428,22 @@ function resolveTimelineWindow(opts) {
|
|
|
74175
74428
|
const since = opts.all ? void 0 : opts.since ?? DEFAULT_SINCE;
|
|
74176
74429
|
return { since, usingDefault };
|
|
74177
74430
|
}
|
|
74431
|
+
var TIMELINE_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74432
|
+
|
|
74433
|
+
Evidence used
|
|
74434
|
+
- List the timeline events, commits, and change-impact signals Horus found
|
|
74435
|
+
|
|
74436
|
+
Narrative
|
|
74437
|
+
- Group events into phases (e.g. pre-change, change window, symptom onset, recovery)
|
|
74438
|
+
- Highlight any suspicious ordering (e.g. a change immediately before first error)
|
|
74439
|
+
- Note what happened before vs. after the first symptom if identifiable
|
|
74440
|
+
|
|
74441
|
+
Confidence
|
|
74442
|
+
- High / Medium / Low with a one-line reason
|
|
74443
|
+
|
|
74444
|
+
Gaps / Next checks
|
|
74445
|
+
- Missing evidence dimensions (logs, metrics, traces, etc.)
|
|
74446
|
+
- Exact Horus commands to fill the gaps`;
|
|
74178
74447
|
async function runTimeline(service, opts) {
|
|
74179
74448
|
try {
|
|
74180
74449
|
const config = await loadConfig(opts.config);
|
|
@@ -74203,6 +74472,22 @@ async function runTimeline(service, opts) {
|
|
|
74203
74472
|
)
|
|
74204
74473
|
);
|
|
74205
74474
|
}
|
|
74475
|
+
if (opts.ai) {
|
|
74476
|
+
const result = await renderAiInterpretation({
|
|
74477
|
+
command: "timeline",
|
|
74478
|
+
userIntent: service ? `service: ${service}` : void 0,
|
|
74479
|
+
evidence: t,
|
|
74480
|
+
promptKind: "timeline-narrative",
|
|
74481
|
+
outputContract: TIMELINE_AI_CONTRACT,
|
|
74482
|
+
config: opts.config,
|
|
74483
|
+
modelOverride: opts.aiModel,
|
|
74484
|
+
provider: opts._aiProvider
|
|
74485
|
+
});
|
|
74486
|
+
console.log("\n" + renderInterpretation(result));
|
|
74487
|
+
if (!result.ok) {
|
|
74488
|
+
console.error(import_picocolors7.default.yellow(`[ai] ${result.warning}`));
|
|
74489
|
+
}
|
|
74490
|
+
}
|
|
74206
74491
|
}
|
|
74207
74492
|
return 0;
|
|
74208
74493
|
} catch (err) {
|
|
@@ -74215,6 +74500,21 @@ async function runTimeline(service, opts) {
|
|
|
74215
74500
|
init_cjs_shims();
|
|
74216
74501
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
74217
74502
|
var DEFAULT_SINCE2 = "7 days ago";
|
|
74503
|
+
var WHAT_CHANGED_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74504
|
+
|
|
74505
|
+
Evidence used
|
|
74506
|
+
- List the concrete changes/files/commits/timestamps Horus found
|
|
74507
|
+
|
|
74508
|
+
Interpretation
|
|
74509
|
+
- Which change(s) look most incident-relevant and why
|
|
74510
|
+
- Which changes are likely noise and why
|
|
74511
|
+
- Any suspicious ordering or coverage gaps
|
|
74512
|
+
|
|
74513
|
+
Confidence
|
|
74514
|
+
- High / Medium / Low with a one-line reason
|
|
74515
|
+
|
|
74516
|
+
Next checks
|
|
74517
|
+
- Exact Horus commands or files to inspect next`;
|
|
74218
74518
|
async function runWhatChanged(service, opts) {
|
|
74219
74519
|
try {
|
|
74220
74520
|
const config = await loadConfig(opts.config);
|
|
@@ -74232,6 +74532,22 @@ async function runWhatChanged(service, opts) {
|
|
|
74232
74532
|
{ code }
|
|
74233
74533
|
);
|
|
74234
74534
|
console.log(opts.json ? whatChangedToJSON(r) : renderWhatChanged(r));
|
|
74535
|
+
if (opts.ai && !opts.json) {
|
|
74536
|
+
const result = await renderAiInterpretation({
|
|
74537
|
+
command: "what-changed",
|
|
74538
|
+
userIntent: service ? `service: ${service}` : void 0,
|
|
74539
|
+
evidence: r,
|
|
74540
|
+
promptKind: "change-risk",
|
|
74541
|
+
outputContract: WHAT_CHANGED_AI_CONTRACT,
|
|
74542
|
+
config: opts.config,
|
|
74543
|
+
modelOverride: opts.aiModel,
|
|
74544
|
+
provider: opts._aiProvider
|
|
74545
|
+
});
|
|
74546
|
+
console.log("\n" + renderInterpretation(result));
|
|
74547
|
+
if (!result.ok) {
|
|
74548
|
+
console.error(import_picocolors8.default.yellow(`[ai] ${result.warning}`));
|
|
74549
|
+
}
|
|
74550
|
+
}
|
|
74235
74551
|
return 0;
|
|
74236
74552
|
} catch (err) {
|
|
74237
74553
|
console.error(import_picocolors8.default.red(err.message));
|
|
@@ -74242,6 +74558,27 @@ async function runWhatChanged(service, opts) {
|
|
|
74242
74558
|
// ../../packages/cli/src/commands/architecture.ts
|
|
74243
74559
|
init_cjs_shims();
|
|
74244
74560
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
74561
|
+
var ARCHITECTURE_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74562
|
+
|
|
74563
|
+
System summary
|
|
74564
|
+
- What the system appears to do based on subsystems, external systems, and key flows Horus found
|
|
74565
|
+
- Major subsystems and their responsibilities
|
|
74566
|
+
|
|
74567
|
+
Critical paths
|
|
74568
|
+
- Important flows and async boundaries found by Horus
|
|
74569
|
+
- Queues, workers, and external API touch points
|
|
74570
|
+
|
|
74571
|
+
Fragility / risk points
|
|
74572
|
+
- Areas where coupling, queues, external systems, or unclear ownership create risk
|
|
74573
|
+
- Refer only to components and boundaries Horus discovered
|
|
74574
|
+
|
|
74575
|
+
How to investigate this system
|
|
74576
|
+
- Useful Horus commands to explore further (blast-radius, investigate, timeline, etc.)
|
|
74577
|
+
- Specific files or symbols worth inspecting
|
|
74578
|
+
|
|
74579
|
+
Confidence / gaps
|
|
74580
|
+
- Where Horus evidence was strong (e.g. indexed source, queue edges)
|
|
74581
|
+
- What evidence is missing and what to do to fill those gaps`;
|
|
74245
74582
|
async function runArchitecture(opts) {
|
|
74246
74583
|
try {
|
|
74247
74584
|
const config = await loadConfig(opts.config);
|
|
@@ -74261,7 +74598,26 @@ async function runArchitecture(opts) {
|
|
|
74261
74598
|
const { db, sql: sql2 } = createDb(config.database.url);
|
|
74262
74599
|
try {
|
|
74263
74600
|
const m = await discoverArchitecture({ code, db, project });
|
|
74264
|
-
|
|
74601
|
+
if (opts.json) {
|
|
74602
|
+
console.log(architectureToJSON(m));
|
|
74603
|
+
} else {
|
|
74604
|
+
console.log(renderArchitecture(m));
|
|
74605
|
+
if (opts.ai) {
|
|
74606
|
+
const result = await renderAiInterpretation({
|
|
74607
|
+
command: "architecture",
|
|
74608
|
+
evidence: m,
|
|
74609
|
+
promptKind: "system-explanation",
|
|
74610
|
+
outputContract: ARCHITECTURE_AI_CONTRACT,
|
|
74611
|
+
config: opts.config,
|
|
74612
|
+
modelOverride: opts.aiModel,
|
|
74613
|
+
provider: opts._aiProvider
|
|
74614
|
+
});
|
|
74615
|
+
console.log("\n" + renderInterpretation(result));
|
|
74616
|
+
if (!result.ok) {
|
|
74617
|
+
console.error(import_picocolors9.default.yellow(`[ai] ${result.warning}`));
|
|
74618
|
+
}
|
|
74619
|
+
}
|
|
74620
|
+
}
|
|
74265
74621
|
} finally {
|
|
74266
74622
|
await sql2.end();
|
|
74267
74623
|
}
|
|
@@ -74275,6 +74631,26 @@ async function runArchitecture(opts) {
|
|
|
74275
74631
|
// ../../packages/cli/src/commands/blast-radius.ts
|
|
74276
74632
|
init_cjs_shims();
|
|
74277
74633
|
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
74634
|
+
var BLAST_RADIUS_AI_CONTRACT = `Provide a clearly separated AI interpretation section with:
|
|
74635
|
+
|
|
74636
|
+
Evidence used
|
|
74637
|
+
- The matched component(s), upstream dependencies, downstream callers, and async boundaries Horus found
|
|
74638
|
+
|
|
74639
|
+
Likely impact
|
|
74640
|
+
- What users or operators would notice if this component fails
|
|
74641
|
+
- Which services/flows are inside the blast radius and at direct risk
|
|
74642
|
+
- Which services/flows appear outside the blast radius
|
|
74643
|
+
|
|
74644
|
+
Containment ideas
|
|
74645
|
+
- Safe mitigations grounded only in the dependencies Horus found
|
|
74646
|
+
- Rollback or disable suggestions only when supported by the evidence (prefix with "verify before doing this")
|
|
74647
|
+
- Async boundaries to isolate (queues, workers, cron jobs, webhooks, external APIs)
|
|
74648
|
+
|
|
74649
|
+
Confidence
|
|
74650
|
+
- High / Medium / Low with a one-line reason
|
|
74651
|
+
|
|
74652
|
+
Next checks
|
|
74653
|
+
- Exact Horus commands or files to inspect next`;
|
|
74278
74654
|
async function runBlastRadius(query, opts) {
|
|
74279
74655
|
try {
|
|
74280
74656
|
const config = await loadConfig(opts.config);
|
|
@@ -74302,7 +74678,27 @@ async function runBlastRadius(query, opts) {
|
|
|
74302
74678
|
import_picocolors10.default.yellow(` No exact match for "${query}"`) + import_picocolors10.default.dim(` \u2014 showing closest: "${r.seed.name}" (fuzzy match)`)
|
|
74303
74679
|
);
|
|
74304
74680
|
}
|
|
74305
|
-
|
|
74681
|
+
if (opts.json) {
|
|
74682
|
+
console.log(blastRadiusToJSON(r));
|
|
74683
|
+
} else {
|
|
74684
|
+
console.log(renderBlastRadius(r));
|
|
74685
|
+
if (opts.ai) {
|
|
74686
|
+
const result = await renderAiInterpretation({
|
|
74687
|
+
command: "blast-radius",
|
|
74688
|
+
userIntent: `query: ${query}`,
|
|
74689
|
+
evidence: r,
|
|
74690
|
+
promptKind: "blast-radius",
|
|
74691
|
+
outputContract: BLAST_RADIUS_AI_CONTRACT,
|
|
74692
|
+
config: opts.config,
|
|
74693
|
+
modelOverride: opts.aiModel,
|
|
74694
|
+
provider: opts._aiProvider
|
|
74695
|
+
});
|
|
74696
|
+
console.log("\n" + renderInterpretation(result));
|
|
74697
|
+
if (!result.ok) {
|
|
74698
|
+
console.error(import_picocolors10.default.yellow(`[ai] ${result.warning}`));
|
|
74699
|
+
}
|
|
74700
|
+
}
|
|
74701
|
+
}
|
|
74306
74702
|
} finally {
|
|
74307
74703
|
await sql2.end();
|
|
74308
74704
|
}
|
|
@@ -74428,29 +74824,6 @@ async function runInvestigations(opts) {
|
|
|
74428
74824
|
// ../../packages/cli/src/commands/replay.ts
|
|
74429
74825
|
init_cjs_shims();
|
|
74430
74826
|
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
74431
|
-
|
|
74432
|
-
// ../../packages/cli/src/lib/ai-provider.ts
|
|
74433
|
-
init_cjs_shims();
|
|
74434
|
-
var DEFAULT_MODEL = "claude-opus-4-8";
|
|
74435
|
-
async function buildNarrativeProvider(opts) {
|
|
74436
|
-
let apiKey;
|
|
74437
|
-
let savedModel;
|
|
74438
|
-
try {
|
|
74439
|
-
const config = await loadConfig(opts.config);
|
|
74440
|
-
const ai = resolveAiSettings(config);
|
|
74441
|
-
apiKey = ai.anthropicApiKey;
|
|
74442
|
-
savedModel = ai.model;
|
|
74443
|
-
} catch {
|
|
74444
|
-
}
|
|
74445
|
-
const model = opts.modelOverride ?? savedModel ?? DEFAULT_MODEL;
|
|
74446
|
-
const provider = new AnthropicNarrativeProvider({
|
|
74447
|
-
model,
|
|
74448
|
-
...apiKey !== void 0 ? { apiKey } : {}
|
|
74449
|
-
});
|
|
74450
|
-
return { provider, model };
|
|
74451
|
-
}
|
|
74452
|
-
|
|
74453
|
-
// ../../packages/cli/src/commands/replay.ts
|
|
74454
74827
|
async function runReplay(id, opts) {
|
|
74455
74828
|
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
74456
74829
|
try {
|
|
@@ -74684,6 +75057,19 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
|
|
|
74684
75057
|
// ../../packages/cli/src/commands/score.ts
|
|
74685
75058
|
init_cjs_shims();
|
|
74686
75059
|
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
75060
|
+
var SCORE_AI_CONTRACT = `Provide a clearly separated AI score explanation with:
|
|
75061
|
+
|
|
75062
|
+
Why this scored this way
|
|
75063
|
+
- Explain each weak dimension (low value, low weight contribution)
|
|
75064
|
+
- Ground each explanation in what Horus found or failed to find
|
|
75065
|
+
|
|
75066
|
+
Biggest improvement lever
|
|
75067
|
+
- The single dimension where better evidence would move the score most
|
|
75068
|
+
- Exact investigation step or command to gather that evidence
|
|
75069
|
+
|
|
75070
|
+
Suggested improvements
|
|
75071
|
+
- Specific ideas for how Horus could gather better evidence next time
|
|
75072
|
+
- Be concrete: what connector, query, or signal is missing`;
|
|
74687
75073
|
async function runScore(id, opts) {
|
|
74688
75074
|
const { db, sql: sql2 } = createDb(await resolveDbUrl(opts.config));
|
|
74689
75075
|
try {
|
|
@@ -74697,7 +75083,27 @@ async function runScore(id, opts) {
|
|
|
74697
75083
|
return 1;
|
|
74698
75084
|
}
|
|
74699
75085
|
const s = scoreInvestigation(migrateReport(row.report));
|
|
74700
|
-
|
|
75086
|
+
if (opts.json) {
|
|
75087
|
+
console.log(scoreToJSON(s));
|
|
75088
|
+
} else {
|
|
75089
|
+
console.log(renderScore(s));
|
|
75090
|
+
if (opts.ai) {
|
|
75091
|
+
const result = await renderAiInterpretation({
|
|
75092
|
+
command: "score",
|
|
75093
|
+
userIntent: `investigation: ${id}`,
|
|
75094
|
+
evidence: s,
|
|
75095
|
+
promptKind: "evidence-summary",
|
|
75096
|
+
outputContract: SCORE_AI_CONTRACT,
|
|
75097
|
+
config: opts.config,
|
|
75098
|
+
modelOverride: opts.aiModel,
|
|
75099
|
+
provider: opts._aiProvider
|
|
75100
|
+
});
|
|
75101
|
+
console.log("\n" + renderInterpretation(result));
|
|
75102
|
+
if (!result.ok) {
|
|
75103
|
+
console.error(import_picocolors16.default.yellow(`[ai] ${result.warning}`));
|
|
75104
|
+
}
|
|
75105
|
+
}
|
|
75106
|
+
}
|
|
74701
75107
|
} finally {
|
|
74702
75108
|
await sql2.end();
|
|
74703
75109
|
}
|
|
@@ -74784,6 +75190,26 @@ async function runAsk(id, directive, opts) {
|
|
|
74784
75190
|
// ../../packages/cli/src/commands/onboard.ts
|
|
74785
75191
|
init_cjs_shims();
|
|
74786
75192
|
var import_picocolors18 = __toESM(require_picocolors(), 1);
|
|
75193
|
+
var ONBOARD_AI_CONTRACT = `Provide a clearly separated AI onboarding guide with:
|
|
75194
|
+
|
|
75195
|
+
Start here
|
|
75196
|
+
- Files and components a new engineer should read first (only those Horus found)
|
|
75197
|
+
- Entry points, controllers, workers, or queue producers most relevant to the area
|
|
75198
|
+
|
|
75199
|
+
Mental model
|
|
75200
|
+
- How this area works in plain language, grounded in what Horus discovered
|
|
75201
|
+
- Key data flows and async handoffs
|
|
75202
|
+
|
|
75203
|
+
What breaks here
|
|
75204
|
+
- Common failure modes based on discovered components, async boundaries, and past incidents
|
|
75205
|
+
- Frame each as "if X fails, then Y" using only evidence Horus found
|
|
75206
|
+
|
|
75207
|
+
Useful commands
|
|
75208
|
+
- Exact Horus commands to continue exploring this area
|
|
75209
|
+
|
|
75210
|
+
Confidence / gaps
|
|
75211
|
+
- What Horus knows confidently (indexed source, queue edges, past incidents)
|
|
75212
|
+
- What is missing and would require manual inspection or a broader index`;
|
|
74787
75213
|
async function runOnboard(area, opts) {
|
|
74788
75214
|
try {
|
|
74789
75215
|
const config = await loadConfig(opts.config);
|
|
@@ -74809,7 +75235,27 @@ async function runOnboard(area, opts) {
|
|
|
74809
75235
|
{ area },
|
|
74810
75236
|
{ code, db, repoPath: repo.path, project: renv.project }
|
|
74811
75237
|
);
|
|
74812
|
-
|
|
75238
|
+
if (opts.json) {
|
|
75239
|
+
console.log(onboardingToJSON(g));
|
|
75240
|
+
} else {
|
|
75241
|
+
console.log(renderOnboarding(g));
|
|
75242
|
+
if (opts.ai) {
|
|
75243
|
+
const result = await renderAiInterpretation({
|
|
75244
|
+
command: "onboard",
|
|
75245
|
+
userIntent: area ? `area: ${area}` : void 0,
|
|
75246
|
+
evidence: g,
|
|
75247
|
+
promptKind: "system-explanation",
|
|
75248
|
+
outputContract: ONBOARD_AI_CONTRACT,
|
|
75249
|
+
config: opts.config,
|
|
75250
|
+
modelOverride: opts.aiModel,
|
|
75251
|
+
provider: opts._aiProvider
|
|
75252
|
+
});
|
|
75253
|
+
console.log("\n" + renderInterpretation(result));
|
|
75254
|
+
if (!result.ok) {
|
|
75255
|
+
console.error(import_picocolors18.default.yellow(`[ai] ${result.warning}`));
|
|
75256
|
+
}
|
|
75257
|
+
}
|
|
75258
|
+
}
|
|
74813
75259
|
} finally {
|
|
74814
75260
|
await sql2.end();
|
|
74815
75261
|
}
|
|
@@ -74875,6 +75321,24 @@ async function runSimulate(scenarioId, opts) {
|
|
|
74875
75321
|
// ../../packages/cli/src/commands/logs.ts
|
|
74876
75322
|
init_cjs_shims();
|
|
74877
75323
|
var import_picocolors20 = __toESM(require_picocolors(), 1);
|
|
75324
|
+
var LOGS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75325
|
+
|
|
75326
|
+
Evidence used
|
|
75327
|
+
- Exact error signatures, counts, first/last timestamps, and affected services Horus found
|
|
75328
|
+
|
|
75329
|
+
What stands out
|
|
75330
|
+
- The most frequent or newest signatures
|
|
75331
|
+
- Services appearing disproportionately across signatures
|
|
75332
|
+
|
|
75333
|
+
What this may indicate
|
|
75334
|
+
- Use "may suggest", "is consistent with", or "could indicate" \u2014 not "proves" or "caused by"
|
|
75335
|
+
- Correlation hints only (e.g. "the spike overlaps with the deployment window")
|
|
75336
|
+
|
|
75337
|
+
What is not proven
|
|
75338
|
+
- Claims that cannot be made from error logs alone (root cause, user impact, fix)
|
|
75339
|
+
|
|
75340
|
+
Next checks
|
|
75341
|
+
- Exact Horus commands, dashboards, or files to inspect next`;
|
|
74878
75342
|
function sinceToIso(since) {
|
|
74879
75343
|
if (since === void 0) return void 0;
|
|
74880
75344
|
const match = /^(\d+)(m|h|d)$/.exec(since);
|
|
@@ -75079,6 +75543,27 @@ async function runLogs(service, opts) {
|
|
|
75079
75543
|
}
|
|
75080
75544
|
console.log("");
|
|
75081
75545
|
console.log(import_picocolors20.default.dim(" (use --raw to see individual log lines)"));
|
|
75546
|
+
if (opts.ai) {
|
|
75547
|
+
const result = await renderAiInterpretation({
|
|
75548
|
+
command: "logs",
|
|
75549
|
+
userIntent: scopeService ? `service: ${scopeService}` : void 0,
|
|
75550
|
+
evidence: {
|
|
75551
|
+
totalErrors: analysis.totalErrors,
|
|
75552
|
+
signatures: analysis.signatures,
|
|
75553
|
+
newSignatures: analysis.newSignatures,
|
|
75554
|
+
affectedServices: analysis.affectedServices
|
|
75555
|
+
},
|
|
75556
|
+
promptKind: "evidence-summary",
|
|
75557
|
+
outputContract: LOGS_AI_CONTRACT,
|
|
75558
|
+
config: opts.config,
|
|
75559
|
+
modelOverride: opts.aiModel,
|
|
75560
|
+
provider: opts._aiProvider
|
|
75561
|
+
});
|
|
75562
|
+
console.log("\n" + renderInterpretation(result));
|
|
75563
|
+
if (!result.ok) {
|
|
75564
|
+
console.error(import_picocolors20.default.yellow(`[ai] ${result.warning}`));
|
|
75565
|
+
}
|
|
75566
|
+
}
|
|
75082
75567
|
return 0;
|
|
75083
75568
|
} catch (err) {
|
|
75084
75569
|
console.error(import_picocolors20.default.red(err.message));
|
|
@@ -75089,6 +75574,24 @@ async function runLogs(service, opts) {
|
|
|
75089
75574
|
// ../../packages/cli/src/commands/metrics.ts
|
|
75090
75575
|
init_cjs_shims();
|
|
75091
75576
|
var import_picocolors21 = __toESM(require_picocolors(), 1);
|
|
75577
|
+
var METRICS_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75578
|
+
|
|
75579
|
+
Evidence used
|
|
75580
|
+
- Exact panel names, anomaly types, baseline vs. current values, and ratios Horus found
|
|
75581
|
+
|
|
75582
|
+
What stands out
|
|
75583
|
+
- The most severe or highest-ratio anomalies
|
|
75584
|
+
- Any metric dimensions with correlated movement (e.g. error rate + latency both spiking)
|
|
75585
|
+
|
|
75586
|
+
What this may indicate
|
|
75587
|
+
- Use "may suggest", "is consistent with", or "could indicate" \u2014 never "proves"
|
|
75588
|
+
- Do not invent dashboard panels or panel names not in the evidence
|
|
75589
|
+
|
|
75590
|
+
What is not proven
|
|
75591
|
+
- Claims that cannot be made from metric anomalies alone (root cause, data loss, user scope)
|
|
75592
|
+
|
|
75593
|
+
Next checks
|
|
75594
|
+
- Exact Horus commands or Grafana dashboards to inspect next`;
|
|
75092
75595
|
function nowSecs() {
|
|
75093
75596
|
return Math.floor(Date.now() / 1e3);
|
|
75094
75597
|
}
|
|
@@ -75251,6 +75754,22 @@ async function runMetrics(hint, opts) {
|
|
|
75251
75754
|
console.log("");
|
|
75252
75755
|
console.log(import_picocolors21.default.dim(` ${ok.length} panel series with no anomaly.`));
|
|
75253
75756
|
}
|
|
75757
|
+
if (opts.ai) {
|
|
75758
|
+
const result = await renderAiInterpretation({
|
|
75759
|
+
command: "metrics",
|
|
75760
|
+
userIntent: hint ? `hint: ${hint}` : void 0,
|
|
75761
|
+
evidence: findings2,
|
|
75762
|
+
promptKind: "evidence-summary",
|
|
75763
|
+
outputContract: METRICS_AI_CONTRACT,
|
|
75764
|
+
config: opts.config,
|
|
75765
|
+
modelOverride: opts.aiModel,
|
|
75766
|
+
provider: opts._aiProvider
|
|
75767
|
+
});
|
|
75768
|
+
console.log("\n" + renderInterpretation(result));
|
|
75769
|
+
if (!result.ok) {
|
|
75770
|
+
console.error(import_picocolors21.default.yellow(`[ai] ${result.warning}`));
|
|
75771
|
+
}
|
|
75772
|
+
}
|
|
75254
75773
|
return 0;
|
|
75255
75774
|
} catch (err) {
|
|
75256
75775
|
console.error(import_picocolors21.default.red(err.message));
|
|
@@ -75261,6 +75780,24 @@ async function runMetrics(hint, opts) {
|
|
|
75261
75780
|
// ../../packages/cli/src/commands/state.ts
|
|
75262
75781
|
init_cjs_shims();
|
|
75263
75782
|
var import_picocolors22 = __toESM(require_picocolors(), 1);
|
|
75783
|
+
var STATE_AI_CONTRACT = `Provide a clearly separated AI evidence narration with:
|
|
75784
|
+
|
|
75785
|
+
Evidence used
|
|
75786
|
+
- Exact collection names, document counts, staleness hours, and anomalous status values Horus found
|
|
75787
|
+
|
|
75788
|
+
What stands out
|
|
75789
|
+
- Collections with stale records or anomalous status distributions
|
|
75790
|
+
- Any counts that seem unusually low or high
|
|
75791
|
+
|
|
75792
|
+
What this may indicate
|
|
75793
|
+
- Use "may suggest", "is consistent with", or "could indicate" \u2014 never "proves"
|
|
75794
|
+
- Do not infer private data or claim access to document contents
|
|
75795
|
+
|
|
75796
|
+
What is not proven
|
|
75797
|
+
- Claims that require reading actual document content or non-allowlisted collections
|
|
75798
|
+
|
|
75799
|
+
Next checks
|
|
75800
|
+
- Exact Horus commands or database queries to inspect next`;
|
|
75264
75801
|
async function runState(opts) {
|
|
75265
75802
|
try {
|
|
75266
75803
|
const config = await loadConfig(opts.config, { name: opts.name });
|
|
@@ -75345,6 +75882,21 @@ async function runState(opts) {
|
|
|
75345
75882
|
);
|
|
75346
75883
|
}
|
|
75347
75884
|
}
|
|
75885
|
+
if (opts.ai) {
|
|
75886
|
+
const result = await renderAiInterpretation({
|
|
75887
|
+
command: "state",
|
|
75888
|
+
evidence: analysis,
|
|
75889
|
+
promptKind: "evidence-summary",
|
|
75890
|
+
outputContract: STATE_AI_CONTRACT,
|
|
75891
|
+
config: opts.config,
|
|
75892
|
+
modelOverride: opts.aiModel,
|
|
75893
|
+
provider: opts._aiProvider
|
|
75894
|
+
});
|
|
75895
|
+
console.log("\n" + renderInterpretation(result));
|
|
75896
|
+
if (!result.ok) {
|
|
75897
|
+
console.error(import_picocolors22.default.yellow(`[ai] ${result.warning}`));
|
|
75898
|
+
}
|
|
75899
|
+
}
|
|
75348
75900
|
return 0;
|
|
75349
75901
|
} finally {
|
|
75350
75902
|
await mongo.close();
|
|
@@ -77112,6 +77664,26 @@ async function runGenerateConfig(opts) {
|
|
|
77112
77664
|
// ../../packages/cli/src/commands/readiness.ts
|
|
77113
77665
|
init_cjs_shims();
|
|
77114
77666
|
var import_picocolors34 = __toESM(require_picocolors(), 1);
|
|
77667
|
+
var READINESS_AI_CONTRACT = `Provide a clearly separated AI readiness summary with:
|
|
77668
|
+
|
|
77669
|
+
Overall assessment
|
|
77670
|
+
- Release/demo readiness in plain language (Ready / Partially ready / Not ready)
|
|
77671
|
+
- One sentence explaining why
|
|
77672
|
+
|
|
77673
|
+
Blockers
|
|
77674
|
+
- Each failing blocking check and what it means operationally
|
|
77675
|
+
- Only reference checks that Horus found failing
|
|
77676
|
+
|
|
77677
|
+
Risks
|
|
77678
|
+
- Non-blocking warnings that could limit investigation depth or demo quality
|
|
77679
|
+
- Prioritize by likely impact
|
|
77680
|
+
|
|
77681
|
+
Recommended next action
|
|
77682
|
+
- The single most impactful step to take right now
|
|
77683
|
+
- Exact command or config change based on the checks above
|
|
77684
|
+
|
|
77685
|
+
Confidence / gaps
|
|
77686
|
+
- Which checks Horus could verify vs. which require manual confirmation`;
|
|
77115
77687
|
var DEFAULT_DB_URL5 = "postgresql://horus:horus@localhost:5433/horus";
|
|
77116
77688
|
function mark3(status) {
|
|
77117
77689
|
if (status === "pass") return import_picocolors34.default.green("\u2713");
|
|
@@ -77309,6 +77881,21 @@ async function runReadiness(opts) {
|
|
|
77309
77881
|
write(import_picocolors34.default.dim(" Re-run `horus readiness` after resolving the items above."));
|
|
77310
77882
|
}
|
|
77311
77883
|
write("");
|
|
77884
|
+
if (opts?.ai) {
|
|
77885
|
+
const result = await renderAiInterpretation({
|
|
77886
|
+
command: "readiness",
|
|
77887
|
+
evidence: checks,
|
|
77888
|
+
promptKind: "readiness",
|
|
77889
|
+
outputContract: READINESS_AI_CONTRACT,
|
|
77890
|
+
config: opts.config,
|
|
77891
|
+
modelOverride: opts.aiModel,
|
|
77892
|
+
provider: opts._aiProvider
|
|
77893
|
+
});
|
|
77894
|
+
console.log("\n" + renderInterpretation(result));
|
|
77895
|
+
if (!result.ok) {
|
|
77896
|
+
console.error(import_picocolors34.default.yellow(`[ai] ${result.warning}`));
|
|
77897
|
+
}
|
|
77898
|
+
}
|
|
77312
77899
|
return blockingFails.length > 0 ? 1 : 0;
|
|
77313
77900
|
}
|
|
77314
77901
|
|
|
@@ -77352,8 +77939,8 @@ Examples:
|
|
|
77352
77939
|
program2.command("generate-config").description("Create a starter horus.config.js with placeholders (HOR-90)").option("--out <path>", "output path (default: horus.config.js in cwd)").option("--name <name>", "project name placeholder (default: my-project)").option("--repo <path>", "repository path placeholder (default: /path/to/<name>)").option("--force", "overwrite an existing config file").action(async (opts) => {
|
|
77353
77940
|
process.exitCode = await runGenerateConfig(opts);
|
|
77354
77941
|
});
|
|
77355
|
-
program2.command("readiness").description("Summarize release/demo readiness: DB, source intelligence, connectors, and local config (HOR-97)").option("-c, --config <path>", "path to horus.config.js").action(async (opts) => {
|
|
77356
|
-
process.exitCode = await runReadiness({ config: opts.config });
|
|
77942
|
+
program2.command("readiness").description("Summarize release/demo readiness: DB, source intelligence, connectors, and local config (HOR-97)").option("-c, --config <path>", "path to horus.config.js").option("--ai", "append AI readiness summary and recommended next action").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(async (opts) => {
|
|
77943
|
+
process.exitCode = await runReadiness({ config: opts.config, ai: opts.ai, aiModel: opts.aiModel });
|
|
77357
77944
|
});
|
|
77358
77945
|
program2.command("connect <type>").description(
|
|
77359
77946
|
"Add or update a connector (elasticsearch / mongodb / grafana / redis / ai) in .horus/config.json"
|
|
@@ -77420,14 +78007,16 @@ Examples:
|
|
|
77420
78007
|
process.exitCode = await runIndex(opts);
|
|
77421
78008
|
}
|
|
77422
78009
|
);
|
|
77423
|
-
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(
|
|
78010
|
+
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").option("--ai", "append AI narration of queue topology and live state (most useful with --live)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77424
78011
|
async (name, opts) => {
|
|
77425
78012
|
process.exitCode = await runQueues(name, {
|
|
77426
78013
|
config: opts.config,
|
|
77427
78014
|
name: opts.name,
|
|
77428
78015
|
project: opts.project,
|
|
77429
78016
|
live: opts.live,
|
|
77430
|
-
json: opts.json
|
|
78017
|
+
json: opts.json,
|
|
78018
|
+
ai: opts.ai,
|
|
78019
|
+
aiModel: opts.aiModel
|
|
77431
78020
|
});
|
|
77432
78021
|
}
|
|
77433
78022
|
);
|
|
@@ -77459,12 +78048,12 @@ Examples:
|
|
|
77459
78048
|
`);
|
|
77460
78049
|
program2.command("changes <base> [compare]").description(
|
|
77461
78050
|
"Show what changed between two git refs and which flows are affected (source change-impact)"
|
|
77462
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").action(async (base2, compare, opts) => {
|
|
77463
|
-
process.exitCode = await runChanges(base2, compare, { config: opts.config, json: opts.json });
|
|
78051
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").option("--ai", "append AI change-impact review").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(async (base2, compare, opts) => {
|
|
78052
|
+
process.exitCode = await runChanges(base2, compare, { config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77464
78053
|
});
|
|
77465
78054
|
program2.command("timeline [service]").description(
|
|
77466
78055
|
"Reconstruct what changed in a time window (git + change-impact) \u2014 evidence, not conclusions"
|
|
77467
|
-
).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(
|
|
78056
|
+
).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").option("--ai", "append AI narrative interpretation of the timeline").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77468
78057
|
async (service, opts) => {
|
|
77469
78058
|
process.exitCode = await runTimeline(service, {
|
|
77470
78059
|
config: opts.config,
|
|
@@ -77472,36 +78061,42 @@ Examples:
|
|
|
77472
78061
|
since: opts.since,
|
|
77473
78062
|
until: opts.until,
|
|
77474
78063
|
all: opts.all,
|
|
77475
|
-
json: opts.json
|
|
78064
|
+
json: opts.json,
|
|
78065
|
+
ai: opts.ai,
|
|
78066
|
+
aiModel: opts.aiModel
|
|
77476
78067
|
});
|
|
77477
78068
|
}
|
|
77478
78069
|
);
|
|
77479
78070
|
program2.command("what-changed [service]").description(
|
|
77480
78071
|
"Concise, evidence-backed summary of what changed for a service in a time window"
|
|
77481
|
-
).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")').option("--until <when>", "git --until").option("--json", "output JSON").action(
|
|
78072
|
+
).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")').option("--until <when>", "git --until").option("--json", "output JSON").option("--ai", "append AI interpretation of the changes").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77482
78073
|
async (service, opts) => {
|
|
77483
78074
|
process.exitCode = await runWhatChanged(service, {
|
|
77484
78075
|
config: opts.config,
|
|
77485
78076
|
repo: opts.repo,
|
|
77486
78077
|
since: opts.since,
|
|
77487
78078
|
until: opts.until,
|
|
77488
|
-
json: opts.json
|
|
78079
|
+
json: opts.json,
|
|
78080
|
+
ai: opts.ai,
|
|
78081
|
+
aiModel: opts.aiModel
|
|
77489
78082
|
});
|
|
77490
78083
|
}
|
|
77491
78084
|
);
|
|
77492
78085
|
program2.command("architecture").description(
|
|
77493
78086
|
"Discover the living architecture (subsystems, async boundaries, external systems, fragility)"
|
|
77494
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").action(async (opts) => {
|
|
77495
|
-
process.exitCode = await runArchitecture({ config: opts.config, json: opts.json });
|
|
78087
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").option("--ai", "append AI system explanation grounded in discovered architecture").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(async (opts) => {
|
|
78088
|
+
process.exitCode = await runArchitecture({ config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77496
78089
|
});
|
|
77497
78090
|
program2.command("blast-radius <query>").description(
|
|
77498
78091
|
"Failure-propagation analysis: upstream/downstream dependencies + blast radius across async boundaries"
|
|
77499
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("-d, --depth <n>", "traversal depth", (v) => parseInt(v, 10)).option("--json", "output JSON").action(
|
|
78092
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("-d, --depth <n>", "traversal depth", (v) => parseInt(v, 10)).option("--json", "output JSON").option("--ai", "append AI severity and containment interpretation").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77500
78093
|
async (query, opts) => {
|
|
77501
78094
|
process.exitCode = await runBlastRadius(query, {
|
|
77502
78095
|
config: opts.config,
|
|
77503
78096
|
depth: opts.depth,
|
|
77504
|
-
json: opts.json
|
|
78097
|
+
json: opts.json,
|
|
78098
|
+
ai: opts.ai,
|
|
78099
|
+
aiModel: opts.aiModel
|
|
77505
78100
|
});
|
|
77506
78101
|
}
|
|
77507
78102
|
);
|
|
@@ -77565,8 +78160,8 @@ Examples:
|
|
|
77565
78160
|
);
|
|
77566
78161
|
program2.command("score <id>").description(
|
|
77567
78162
|
"Score a saved investigation's quality (a feedback loop for Horus, not engineers)"
|
|
77568
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").action(async (id, opts) => {
|
|
77569
|
-
process.exitCode = await runScore(id, { config: opts.config, json: opts.json });
|
|
78163
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--json", "output JSON").option("--ai", "append AI score explanation and improvement suggestions").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(async (id, opts) => {
|
|
78164
|
+
process.exitCode = await runScore(id, { config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77570
78165
|
});
|
|
77571
78166
|
program2.command("ask <id> <directive>").description(
|
|
77572
78167
|
'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"'
|
|
@@ -77578,11 +78173,13 @@ Examples:
|
|
|
77578
78173
|
});
|
|
77579
78174
|
program2.command("onboard [area]").description(
|
|
77580
78175
|
"Understand a system fast: architecture, critical paths, what breaks, ownership, past incidents"
|
|
77581
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--json", "output JSON").action(async (area, opts) => {
|
|
78176
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--repo <name>", "repository name from config").option("--json", "output JSON").option("--ai", "append AI onboarding guide grounded in discovered system evidence").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(async (area, opts) => {
|
|
77582
78177
|
process.exitCode = await runOnboard(area, {
|
|
77583
78178
|
config: opts.config,
|
|
77584
78179
|
repo: opts.repo,
|
|
77585
|
-
json: opts.json
|
|
78180
|
+
json: opts.json,
|
|
78181
|
+
ai: opts.ai,
|
|
78182
|
+
aiModel: opts.aiModel
|
|
77586
78183
|
});
|
|
77587
78184
|
});
|
|
77588
78185
|
program2.command("simulate [scenario]").description(
|
|
@@ -77595,21 +78192,21 @@ Examples:
|
|
|
77595
78192
|
});
|
|
77596
78193
|
program2.command("logs [service]").description(
|
|
77597
78194
|
"Synthesize error evidence from logs (signatures, first/last, affected services); --raw for lines"
|
|
77598
|
-
).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(
|
|
78195
|
+
).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").option("--ai", "append AI narration of error signatures (default mode only, not --raw or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77599
78196
|
async (service, opts) => {
|
|
77600
78197
|
process.exitCode = await runLogs(service, opts);
|
|
77601
78198
|
}
|
|
77602
78199
|
);
|
|
77603
78200
|
program2.command("state").description(
|
|
77604
78201
|
"Surface application-state evidence from MongoDB (read-only, allowlisted): counts, staleness, anomalous statuses"
|
|
77605
|
-
).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(
|
|
78202
|
+
).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").option("--ai", "append AI narration of MongoDB state evidence").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77606
78203
|
async (opts) => {
|
|
77607
78204
|
process.exitCode = await runState(opts);
|
|
77608
78205
|
}
|
|
77609
78206
|
);
|
|
77610
78207
|
program2.command("metrics [hint]").description(
|
|
77611
78208
|
"Grafana metrics evidence: find dashboards/panels for a hint and detect latency spikes, error-rate changes, throughput drops, queue growth"
|
|
77612
|
-
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--since <when>", "window, e.g. 1h, 6h, 24h").option("--step <secs>", "range step seconds").option("--dashboard <uid>", "restrict to a dashboard uid").option("--query <promql>", "raw datasource query escape hatch").option("--json", "JSON output").action(
|
|
78209
|
+
).option("-c, --config <path>", "path to horus.config.ts").option("--name <name>", "registered project name (resolves via the registry)").option("--since <when>", "window, e.g. 1h, 6h, 24h").option("--step <secs>", "range step seconds").option("--dashboard <uid>", "restrict to a dashboard uid").option("--query <promql>", "raw datasource query escape hatch").option("--json", "JSON output").option("--ai", "append AI narration of metric findings (default mode only, not --query or --json)").option("--ai-model <model>", "override the AI model (default: claude-opus-4-8)").action(
|
|
77613
78210
|
async (hint, opts) => {
|
|
77614
78211
|
process.exitCode = await runMetrics(hint, opts);
|
|
77615
78212
|
}
|