@merittdev/horus 0.1.15 → 0.1.17
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 +1107 -333
- 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.17" : "dev";
|
|
50318
50318
|
var PINNED_AXON_VERSION = "1.0.7";
|
|
50319
50319
|
var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
|
|
50320
50320
|
|
|
@@ -59087,7 +59087,7 @@ function sql(strings, ...params) {
|
|
|
59087
59087
|
return new SQL([new StringChunk(str)]);
|
|
59088
59088
|
}
|
|
59089
59089
|
sql2.raw = raw;
|
|
59090
|
-
function
|
|
59090
|
+
function join6(chunks, separator) {
|
|
59091
59091
|
const result = [];
|
|
59092
59092
|
for (const [i, chunk] of chunks.entries()) {
|
|
59093
59093
|
if (i > 0 && separator !== void 0) {
|
|
@@ -59097,7 +59097,7 @@ function sql(strings, ...params) {
|
|
|
59097
59097
|
}
|
|
59098
59098
|
return new SQL(result);
|
|
59099
59099
|
}
|
|
59100
|
-
sql2.join =
|
|
59100
|
+
sql2.join = join6;
|
|
59101
59101
|
function identifier(value) {
|
|
59102
59102
|
return new Name(value);
|
|
59103
59103
|
}
|
|
@@ -62688,7 +62688,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
62688
62688
|
return (table, on) => {
|
|
62689
62689
|
const baseTableName = this.tableName;
|
|
62690
62690
|
const tableName = getTableLikeName(table);
|
|
62691
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
62691
|
+
if (typeof tableName === "string" && this.config.joins?.some((join6) => join6.alias === tableName)) {
|
|
62692
62692
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
62693
62693
|
}
|
|
62694
62694
|
if (!this.isPartialSelect) {
|
|
@@ -63700,7 +63700,7 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
63700
63700
|
createJoin(joinType) {
|
|
63701
63701
|
return (table, on) => {
|
|
63702
63702
|
const tableName = getTableLikeName(table);
|
|
63703
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
63703
|
+
if (typeof tableName === "string" && this.config.joins.some((join6) => join6.alias === tableName)) {
|
|
63704
63704
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
63705
63705
|
}
|
|
63706
63706
|
if (typeof on === "function") {
|
|
@@ -63796,10 +63796,10 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
63796
63796
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
63797
63797
|
fields[tableName] = fromFields;
|
|
63798
63798
|
}
|
|
63799
|
-
for (const
|
|
63800
|
-
const tableName2 = getTableLikeName(
|
|
63801
|
-
if (typeof tableName2 === "string" && !is(
|
|
63802
|
-
const fromFields = this.getTableLikeFields(
|
|
63799
|
+
for (const join6 of this.config.joins) {
|
|
63800
|
+
const tableName2 = getTableLikeName(join6.table);
|
|
63801
|
+
if (typeof tableName2 === "string" && !is(join6.table, SQL)) {
|
|
63802
|
+
const fromFields = this.getTableLikeFields(join6.table);
|
|
63803
63803
|
fields[tableName2] = fromFields;
|
|
63804
63804
|
}
|
|
63805
63805
|
}
|
|
@@ -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,
|
|
@@ -72923,22 +72938,22 @@ function extractQueueGraph(input) {
|
|
|
72923
72938
|
}
|
|
72924
72939
|
}
|
|
72925
72940
|
const producers = [];
|
|
72926
|
-
for (const
|
|
72927
|
-
for (const m of
|
|
72941
|
+
for (const pc36 of input.producerClasses) {
|
|
72942
|
+
for (const m of pc36.content.matchAll(INJECT_QUEUE_RE)) {
|
|
72928
72943
|
const queue = m[1] ?? "";
|
|
72929
|
-
if (queue) producers.push({ queue, symbol:
|
|
72944
|
+
if (queue) producers.push({ queue, symbol: pc36.name, file: pc36.filePath });
|
|
72930
72945
|
}
|
|
72931
|
-
for (const m of
|
|
72946
|
+
for (const m of pc36.content.matchAll(NEW_BULL_RE)) {
|
|
72932
72947
|
if (m[2] !== "Queue") continue;
|
|
72933
72948
|
const queue = resolveArg(m[3] ?? "", constMap);
|
|
72934
72949
|
if (queue) {
|
|
72935
|
-
producers.push({ queue, symbol: (m[1] ??
|
|
72950
|
+
producers.push({ queue, symbol: (m[1] ?? pc36.name) || baseName(pc36.filePath), file: pc36.filePath });
|
|
72936
72951
|
}
|
|
72937
72952
|
}
|
|
72938
|
-
for (const m of
|
|
72953
|
+
for (const m of pc36.content.matchAll(QUEUE_NAME_CONST_RE)) {
|
|
72939
72954
|
const ident = m[1] ?? "";
|
|
72940
72955
|
const queue = m[2] ?? "";
|
|
72941
|
-
if (queue) producers.push({ queue, symbol: ident || baseName(
|
|
72956
|
+
if (queue) producers.push({ queue, symbol: ident || baseName(pc36.filePath), file: pc36.filePath });
|
|
72942
72957
|
}
|
|
72943
72958
|
}
|
|
72944
72959
|
const workers = [];
|
|
@@ -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",
|
|
@@ -73905,25 +73682,462 @@ function createLocalProviderRegistry(descriptors = DEFAULT_DESCRIPTORS) {
|
|
|
73905
73682
|
if (map2.has(d.id)) {
|
|
73906
73683
|
throw new Error(`Duplicate provider ID: ${d.id}`);
|
|
73907
73684
|
}
|
|
73908
|
-
map2.set(d.id, d);
|
|
73909
|
-
list.push(d);
|
|
73685
|
+
map2.set(d.id, d);
|
|
73686
|
+
list.push(d);
|
|
73687
|
+
}
|
|
73688
|
+
return {
|
|
73689
|
+
providers: list,
|
|
73690
|
+
get(id) {
|
|
73691
|
+
return map2.get(id);
|
|
73692
|
+
}
|
|
73693
|
+
};
|
|
73694
|
+
}
|
|
73695
|
+
var DEFAULT_LOCAL_PROVIDER_REGISTRY = createLocalProviderRegistry();
|
|
73696
|
+
|
|
73697
|
+
// ../../packages/ai/src/detection-fixtures.ts
|
|
73698
|
+
init_cjs_shims();
|
|
73699
|
+
|
|
73700
|
+
// ../../packages/ai/src/provider-execution.ts
|
|
73701
|
+
init_cjs_shims();
|
|
73702
|
+
|
|
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
|
+
});
|
|
73910
74103
|
}
|
|
73911
|
-
|
|
73912
|
-
|
|
73913
|
-
|
|
73914
|
-
|
|
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"));
|
|
73915
74118
|
}
|
|
73916
|
-
|
|
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`;
|
|
73917
74136
|
}
|
|
73918
|
-
var DEFAULT_LOCAL_PROVIDER_REGISTRY = createLocalProviderRegistry();
|
|
73919
|
-
|
|
73920
|
-
// ../../packages/ai/src/detection-fixtures.ts
|
|
73921
|
-
init_cjs_shims();
|
|
73922
|
-
|
|
73923
|
-
// ../../packages/ai/src/provider-execution.ts
|
|
73924
|
-
init_cjs_shims();
|
|
73925
74137
|
|
|
73926
74138
|
// ../../packages/cli/src/commands/investigate.ts
|
|
74139
|
+
init_cjs_shims();
|
|
74140
|
+
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
73927
74141
|
function extractEvidenceExcerpt(e) {
|
|
73928
74142
|
if (!e.payload || typeof e.payload !== "object") return void 0;
|
|
73929
74143
|
const p = e.payload;
|
|
@@ -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,9 +77881,171 @@ 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
|
|
|
77902
|
+
// ../../packages/cli/src/commands/skill.ts
|
|
77903
|
+
init_cjs_shims();
|
|
77904
|
+
var import_node_fs9 = require("fs");
|
|
77905
|
+
var import_node_path11 = require("path");
|
|
77906
|
+
var import_node_os2 = require("os");
|
|
77907
|
+
var import_picocolors35 = __toESM(require_picocolors(), 1);
|
|
77908
|
+
var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
|
|
77909
|
+
function isValidTarget(target) {
|
|
77910
|
+
return SUPPORTED_TARGETS.includes(target);
|
|
77911
|
+
}
|
|
77912
|
+
function getSkillInstallPath(target, opts) {
|
|
77913
|
+
const base2 = opts.global ? (0, import_node_os2.homedir)() : opts.cwd ?? process.cwd();
|
|
77914
|
+
if (target === "claude") {
|
|
77915
|
+
return (0, import_node_path11.join)(base2, ".claude", "skills", "horus.md");
|
|
77916
|
+
}
|
|
77917
|
+
return (0, import_node_path11.join)(base2, ".horus", "skills", `horus-${target}.md`);
|
|
77918
|
+
}
|
|
77919
|
+
var BASE_SKILL = `# Horus Incident Investigation Skill
|
|
77920
|
+
|
|
77921
|
+
When investigating a production issue in a repo with Horus configured, use Horus before guessing.
|
|
77922
|
+
|
|
77923
|
+
## When to use Horus
|
|
77924
|
+
|
|
77925
|
+
Use Horus when the user asks:
|
|
77926
|
+
- why something is broken, slow, or failing
|
|
77927
|
+
- about a production incident or outage
|
|
77928
|
+
- what changed recently
|
|
77929
|
+
- who owns a component or service
|
|
77930
|
+
- about blast radius or impact of a change
|
|
77931
|
+
- about queues, workers, Redis/BullMQ, logs, metrics, MongoDB state, or source paths
|
|
77932
|
+
- to verify a fix or check for regressions after a change
|
|
77933
|
+
|
|
77934
|
+
## Getting started
|
|
77935
|
+
|
|
77936
|
+
\`\`\`bash
|
|
77937
|
+
horus status
|
|
77938
|
+
horus explain <symbol-or-hint>
|
|
77939
|
+
horus investigate "<incident hint>"
|
|
77940
|
+
\`\`\`
|
|
77941
|
+
|
|
77942
|
+
## Runtime evidence
|
|
77943
|
+
|
|
77944
|
+
\`\`\`bash
|
|
77945
|
+
horus logs [service]
|
|
77946
|
+
horus state
|
|
77947
|
+
horus metrics "<hint>"
|
|
77948
|
+
horus queues --live
|
|
77949
|
+
\`\`\`
|
|
77950
|
+
|
|
77951
|
+
## Source reasoning
|
|
77952
|
+
|
|
77953
|
+
\`\`\`bash
|
|
77954
|
+
horus blast-radius <symbol>
|
|
77955
|
+
horus owner <symbol>
|
|
77956
|
+
horus changes <base> [compare]
|
|
77957
|
+
horus timeline
|
|
77958
|
+
horus what-changed
|
|
77959
|
+
\`\`\`
|
|
77960
|
+
|
|
77961
|
+
## Saved investigations
|
|
77962
|
+
|
|
77963
|
+
\`\`\`bash
|
|
77964
|
+
horus replay <id>
|
|
77965
|
+
horus ask <id> "what evidence is missing?"
|
|
77966
|
+
horus ask <id> "what evidence contradicts <hypothesis>?"
|
|
77967
|
+
horus postmortem <id>
|
|
77968
|
+
horus score <id>
|
|
77969
|
+
\`\`\`
|
|
77970
|
+
|
|
77971
|
+
## Rules
|
|
77972
|
+
|
|
77973
|
+
- Treat Horus evidence as grounded but not infallible.
|
|
77974
|
+
- Mention when Horus evidence is missing, stale, ambient, or not structurally linked.
|
|
77975
|
+
- Do not invent runtime evidence Horus did not collect.
|
|
77976
|
+
- Prefer exact source locations from Horus over guesses.
|
|
77977
|
+
- If Horus output conflicts with filesystem or source inspection, report the discrepancy.
|
|
77978
|
+
- For write or fix tasks, use Horus to validate blast radius and rerun relevant checks after changes.
|
|
77979
|
+
- Horus is read-only evidence collection \u2014 never use Horus to mutate production systems.
|
|
77980
|
+
- Do not hallucinate evidence. If Horus returns nothing, say so explicitly.
|
|
77981
|
+
`;
|
|
77982
|
+
var CLAUDE_SUFFIX = `## Claude Code notes
|
|
77983
|
+
|
|
77984
|
+
This skill is loaded automatically when present at \`.claude/skills/horus.md\`.
|
|
77985
|
+
|
|
77986
|
+
Useful starting points:
|
|
77987
|
+
- \`horus investigations\` \u2014 list saved investigation IDs
|
|
77988
|
+
- \`horus doctor\` \u2014 check system health
|
|
77989
|
+
- \`horus onboard [area]\` \u2014 get oriented in a new codebase area
|
|
77990
|
+
`;
|
|
77991
|
+
function generateSkillContent(target) {
|
|
77992
|
+
if (target === "claude") {
|
|
77993
|
+
return BASE_SKILL + "\n" + CLAUDE_SUFFIX;
|
|
77994
|
+
}
|
|
77995
|
+
return BASE_SKILL;
|
|
77996
|
+
}
|
|
77997
|
+
async function runSkillInstall(target, opts) {
|
|
77998
|
+
const log = opts.write ?? ((line2) => console.log(line2));
|
|
77999
|
+
if (!isValidTarget(target)) {
|
|
78000
|
+
log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
|
|
78001
|
+
log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78002
|
+
return 1;
|
|
78003
|
+
}
|
|
78004
|
+
const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
|
|
78005
|
+
const content = generateSkillContent(target);
|
|
78006
|
+
if ((0, import_node_fs9.existsSync)(outPath) && !opts.force) {
|
|
78007
|
+
log(`${import_picocolors35.default.red("\u2717")} ${outPath} already exists`);
|
|
78008
|
+
log(import_picocolors35.default.dim(" pass --force to overwrite"));
|
|
78009
|
+
return 1;
|
|
78010
|
+
}
|
|
78011
|
+
try {
|
|
78012
|
+
(0, import_node_fs9.mkdirSync)((0, import_node_path11.dirname)(outPath), { recursive: true });
|
|
78013
|
+
(0, import_node_fs9.writeFileSync)(outPath, content, "utf8");
|
|
78014
|
+
} catch (err) {
|
|
78015
|
+
log(`${import_picocolors35.default.red("\u2717")} Could not write ${outPath}: ${err.message}`);
|
|
78016
|
+
return 1;
|
|
78017
|
+
}
|
|
78018
|
+
log(`${import_picocolors35.default.green("\u2713")} Horus skill installed \u2192 ${outPath}`);
|
|
78019
|
+
if (target === "claude") {
|
|
78020
|
+
log(import_picocolors35.default.dim(" Claude Code will load this skill automatically."));
|
|
78021
|
+
log(import_picocolors35.default.dim(" Invoke it with: /horus"));
|
|
78022
|
+
} else {
|
|
78023
|
+
log(import_picocolors35.default.dim(` Copy or reference this file in your ${target} agent instructions.`));
|
|
78024
|
+
}
|
|
78025
|
+
return 0;
|
|
78026
|
+
}
|
|
78027
|
+
async function runSkillPrint(target, opts) {
|
|
78028
|
+
const log = opts.write ?? ((line2) => console.log(line2));
|
|
78029
|
+
if (!isValidTarget(target)) {
|
|
78030
|
+
log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
|
|
78031
|
+
log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78032
|
+
return 1;
|
|
78033
|
+
}
|
|
78034
|
+
process.stdout.write(generateSkillContent(target));
|
|
78035
|
+
return 0;
|
|
78036
|
+
}
|
|
78037
|
+
async function runSkillPath(target, opts) {
|
|
78038
|
+
const log = opts.write ?? ((line2) => console.log(line2));
|
|
78039
|
+
if (!isValidTarget(target)) {
|
|
78040
|
+
log(`${import_picocolors35.default.red("\u2717")} Unknown target: ${import_picocolors35.default.bold(target)}`);
|
|
78041
|
+
log(import_picocolors35.default.dim(` Supported targets: ${SUPPORTED_TARGETS.join(", ")}`));
|
|
78042
|
+
return 1;
|
|
78043
|
+
}
|
|
78044
|
+
const outPath = getSkillInstallPath(target, { global: opts.global, cwd: opts.cwd });
|
|
78045
|
+
log(outPath);
|
|
78046
|
+
return 0;
|
|
78047
|
+
}
|
|
78048
|
+
|
|
77315
78049
|
// ../../packages/cli/src/index.ts
|
|
77316
78050
|
function buildProgram() {
|
|
77317
78051
|
const program2 = new Command();
|
|
@@ -77352,8 +78086,8 @@ Examples:
|
|
|
77352
78086
|
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
78087
|
process.exitCode = await runGenerateConfig(opts);
|
|
77354
78088
|
});
|
|
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 });
|
|
78089
|
+
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) => {
|
|
78090
|
+
process.exitCode = await runReadiness({ config: opts.config, ai: opts.ai, aiModel: opts.aiModel });
|
|
77357
78091
|
});
|
|
77358
78092
|
program2.command("connect <type>").description(
|
|
77359
78093
|
"Add or update a connector (elasticsearch / mongodb / grafana / redis / ai) in .horus/config.json"
|
|
@@ -77420,14 +78154,16 @@ Examples:
|
|
|
77420
78154
|
process.exitCode = await runIndex(opts);
|
|
77421
78155
|
}
|
|
77422
78156
|
);
|
|
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(
|
|
78157
|
+
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
78158
|
async (name, opts) => {
|
|
77425
78159
|
process.exitCode = await runQueues(name, {
|
|
77426
78160
|
config: opts.config,
|
|
77427
78161
|
name: opts.name,
|
|
77428
78162
|
project: opts.project,
|
|
77429
78163
|
live: opts.live,
|
|
77430
|
-
json: opts.json
|
|
78164
|
+
json: opts.json,
|
|
78165
|
+
ai: opts.ai,
|
|
78166
|
+
aiModel: opts.aiModel
|
|
77431
78167
|
});
|
|
77432
78168
|
}
|
|
77433
78169
|
);
|
|
@@ -77459,12 +78195,12 @@ Examples:
|
|
|
77459
78195
|
`);
|
|
77460
78196
|
program2.command("changes <base> [compare]").description(
|
|
77461
78197
|
"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 });
|
|
78198
|
+
).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) => {
|
|
78199
|
+
process.exitCode = await runChanges(base2, compare, { config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77464
78200
|
});
|
|
77465
78201
|
program2.command("timeline [service]").description(
|
|
77466
78202
|
"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(
|
|
78203
|
+
).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
78204
|
async (service, opts) => {
|
|
77469
78205
|
process.exitCode = await runTimeline(service, {
|
|
77470
78206
|
config: opts.config,
|
|
@@ -77472,36 +78208,42 @@ Examples:
|
|
|
77472
78208
|
since: opts.since,
|
|
77473
78209
|
until: opts.until,
|
|
77474
78210
|
all: opts.all,
|
|
77475
|
-
json: opts.json
|
|
78211
|
+
json: opts.json,
|
|
78212
|
+
ai: opts.ai,
|
|
78213
|
+
aiModel: opts.aiModel
|
|
77476
78214
|
});
|
|
77477
78215
|
}
|
|
77478
78216
|
);
|
|
77479
78217
|
program2.command("what-changed [service]").description(
|
|
77480
78218
|
"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(
|
|
78219
|
+
).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
78220
|
async (service, opts) => {
|
|
77483
78221
|
process.exitCode = await runWhatChanged(service, {
|
|
77484
78222
|
config: opts.config,
|
|
77485
78223
|
repo: opts.repo,
|
|
77486
78224
|
since: opts.since,
|
|
77487
78225
|
until: opts.until,
|
|
77488
|
-
json: opts.json
|
|
78226
|
+
json: opts.json,
|
|
78227
|
+
ai: opts.ai,
|
|
78228
|
+
aiModel: opts.aiModel
|
|
77489
78229
|
});
|
|
77490
78230
|
}
|
|
77491
78231
|
);
|
|
77492
78232
|
program2.command("architecture").description(
|
|
77493
78233
|
"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 });
|
|
78234
|
+
).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) => {
|
|
78235
|
+
process.exitCode = await runArchitecture({ config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77496
78236
|
});
|
|
77497
78237
|
program2.command("blast-radius <query>").description(
|
|
77498
78238
|
"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(
|
|
78239
|
+
).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
78240
|
async (query, opts) => {
|
|
77501
78241
|
process.exitCode = await runBlastRadius(query, {
|
|
77502
78242
|
config: opts.config,
|
|
77503
78243
|
depth: opts.depth,
|
|
77504
|
-
json: opts.json
|
|
78244
|
+
json: opts.json,
|
|
78245
|
+
ai: opts.ai,
|
|
78246
|
+
aiModel: opts.aiModel
|
|
77505
78247
|
});
|
|
77506
78248
|
}
|
|
77507
78249
|
);
|
|
@@ -77565,8 +78307,8 @@ Examples:
|
|
|
77565
78307
|
);
|
|
77566
78308
|
program2.command("score <id>").description(
|
|
77567
78309
|
"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 });
|
|
78310
|
+
).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) => {
|
|
78311
|
+
process.exitCode = await runScore(id, { config: opts.config, json: opts.json, ai: opts.ai, aiModel: opts.aiModel });
|
|
77570
78312
|
});
|
|
77571
78313
|
program2.command("ask <id> <directive>").description(
|
|
77572
78314
|
'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 +78320,13 @@ Examples:
|
|
|
77578
78320
|
});
|
|
77579
78321
|
program2.command("onboard [area]").description(
|
|
77580
78322
|
"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) => {
|
|
78323
|
+
).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
78324
|
process.exitCode = await runOnboard(area, {
|
|
77583
78325
|
config: opts.config,
|
|
77584
78326
|
repo: opts.repo,
|
|
77585
|
-
json: opts.json
|
|
78327
|
+
json: opts.json,
|
|
78328
|
+
ai: opts.ai,
|
|
78329
|
+
aiModel: opts.aiModel
|
|
77586
78330
|
});
|
|
77587
78331
|
});
|
|
77588
78332
|
program2.command("simulate [scenario]").description(
|
|
@@ -77595,25 +78339,55 @@ Examples:
|
|
|
77595
78339
|
});
|
|
77596
78340
|
program2.command("logs [service]").description(
|
|
77597
78341
|
"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(
|
|
78342
|
+
).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
78343
|
async (service, opts) => {
|
|
77600
78344
|
process.exitCode = await runLogs(service, opts);
|
|
77601
78345
|
}
|
|
77602
78346
|
);
|
|
77603
78347
|
program2.command("state").description(
|
|
77604
78348
|
"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(
|
|
78349
|
+
).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
78350
|
async (opts) => {
|
|
77607
78351
|
process.exitCode = await runState(opts);
|
|
77608
78352
|
}
|
|
77609
78353
|
);
|
|
77610
78354
|
program2.command("metrics [hint]").description(
|
|
77611
78355
|
"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(
|
|
78356
|
+
).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
78357
|
async (hint, opts) => {
|
|
77614
78358
|
process.exitCode = await runMetrics(hint, opts);
|
|
77615
78359
|
}
|
|
77616
78360
|
);
|
|
78361
|
+
const skill = program2.command("skill").description("Manage the Horus agent skill \u2014 install playbooks that teach agents to use Horus as their evidence layer");
|
|
78362
|
+
skill.command("install <target>").description("Install the Horus skill for a coding agent (claude | codex | gemini | cursor | generic)").option("--force", "overwrite an existing skill file").option("--global", "install into the home directory instead of the current repo").action(async (target, opts) => {
|
|
78363
|
+
process.exitCode = await runSkillInstall(target, { force: opts.force, global: opts.global });
|
|
78364
|
+
}).addHelpText("after", `
|
|
78365
|
+
Targets:
|
|
78366
|
+
claude \u2192 .claude/skills/horus.md (Claude Code \u2014 loaded automatically)
|
|
78367
|
+
codex \u2192 .horus/skills/horus-codex.md
|
|
78368
|
+
gemini \u2192 .horus/skills/horus-gemini.md
|
|
78369
|
+
cursor \u2192 .horus/skills/horus-cursor.md
|
|
78370
|
+
generic \u2192 .horus/skills/horus-generic.md
|
|
78371
|
+
|
|
78372
|
+
Examples:
|
|
78373
|
+
horus skill install claude
|
|
78374
|
+
horus skill install generic --force
|
|
78375
|
+
horus skill install claude --global
|
|
78376
|
+
`);
|
|
78377
|
+
skill.command("print <target>").description("Print the Horus skill to stdout \u2014 copy it into any agent setup").action(async (target) => {
|
|
78378
|
+
process.exitCode = await runSkillPrint(target, {});
|
|
78379
|
+
}).addHelpText("after", `
|
|
78380
|
+
Examples:
|
|
78381
|
+
horus skill print generic
|
|
78382
|
+
horus skill print claude
|
|
78383
|
+
`);
|
|
78384
|
+
skill.command("path <target>").description("Print the install path for the given target").option("--global", "show the global (home directory) path").action(async (target, opts) => {
|
|
78385
|
+
process.exitCode = await runSkillPath(target, { global: opts.global });
|
|
78386
|
+
}).addHelpText("after", `
|
|
78387
|
+
Examples:
|
|
78388
|
+
horus skill path claude
|
|
78389
|
+
horus skill path generic --global
|
|
78390
|
+
`);
|
|
77617
78391
|
return program2;
|
|
77618
78392
|
}
|
|
77619
78393
|
async function run(argv = process.argv) {
|