@merittdev/horus 0.1.1 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +100 -116
  2. package/dist/index.cjs +118 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,65 +6,78 @@
6
6
 
7
7
  **Understand what happened.**
8
8
 
9
- Horus turns logs, metrics, traces, queues, databases, and code into a deterministic incident report.
9
+ Open-source incident investigation. Horus connects Elasticsearch, Grafana, MongoDB, BullMQ, and source intelligence into deterministic reports — installable today.
10
10
 
11
11
  CLI-only. Read-only against production systems. Horus never writes to your infrastructure.
12
12
 
13
- **Website:** [horus.sh](https://horus.sh)
13
+ **Website:** [horus.sh](https://horus.sh) · **Source:** [github.com/meritt-dev/horus](https://github.com/meritt-dev/horus)
14
14
 
15
15
  ```bash
16
16
  curl -fsSL https://horus.sh/install.sh | bash
17
+ npm install -g @merittdev/horus
18
+ brew install meritt-dev/tap/horus
17
19
  ```
18
20
 
21
+ Homebrew tap is live through meritt-dev/tap.
22
+
19
23
  ---
20
24
 
21
25
  ## What Horus does
22
26
 
23
- Horus reads from your existing systems and reconstructs the incident through evidence, correlation, and reasoning.
27
+ Horus reads from your existing systems and reconstructs the incident through evidence, correlation, and ranked hypotheses.
24
28
 
25
- It does not dump thousands of logs. It connects runtime signals to source context and returns a **deterministic report** — root cause, confidence, supporting evidence, contradictions, and recommended actions. Evidence before inference. The report is the product.
29
+ It does not dump thousands of logs. It connects runtime signals to source context and returns a **deterministic report** — suspected causes (ranked), hypotheses, evidence, gaps, and next actions. Evidence before inference. Optional `--ai` adds an Anthropic narrative on top.
26
30
 
27
31
  Every incident leaves evidence.
28
32
 
29
33
  ## What Horus is not
30
34
 
31
- | | |
32
- | ----------------- | -------------------------- |
33
- | **Monitoring** | Detects problems |
34
- | **Observability** | Shows signals |
35
- | **Horus** | Reconstructs what happened |
35
+ | | |
36
+ |---|---|
37
+ | **Monitoring** | Detects problems |
38
+ | **Observability** | Shows signals |
39
+ | **Horus** | Reconstructs what happened |
36
40
 
37
41
  Horus is not another dashboard, alerting tool, or log viewer. It sits on top of the systems you already use.
38
42
 
39
43
  > Monitoring detects. Observability shows. Horus reconstructs.
40
44
 
45
+ ## Getting started
46
+
47
+ ```bash
48
+ horus setup
49
+ horus init
50
+ horus index
51
+ horus connect elasticsearch # optional runtime connectors
52
+ horus investigate "checkout latency spike"
53
+ horus investigations # list saved IDs
54
+ horus replay <id>
55
+ horus postmortem <id>
56
+ ```
57
+
58
+ Horus source intelligence requires a local code-graph host (the curl installer attempts to install it). Postgres is required for the audit store.
59
+
41
60
  ## How it works
42
61
 
43
62
  **Evidence in. Explanation out.**
44
63
 
45
- ```
46
- Runtime Evidence → Investigation Engine → Investigation Report
47
- ```
48
-
49
- | Runtime Evidence | Investigation Engine | Investigation Report |
50
- | ---------------- | -------------------- | -------------------- |
51
- | Logs | Correlation | Root Cause |
52
- | Metrics | Timeline | Confidence |
53
- | Traces | Hypotheses | Evidence |
54
- | Queues | Reconstruction | Contradictions |
55
- | Databases | | Recommended Actions |
64
+ | Runtime + Source | Investigation Engine | Investigation Report |
65
+ |---|---|---|
66
+ | Elasticsearch logs | Correlation | Suspected causes (ranked) |
67
+ | Grafana metrics | Timeline | Hypotheses + confidence |
68
+ | MongoDB state | Cause ranking | Evidence + gaps |
69
+ | BullMQ queues | | Next actions |
70
+ | Source graph + git | | |
56
71
 
57
72
  Pipeline: **Evidence → Correlation → Hypotheses → Timeline → Report**
58
73
 
59
- Signals are easy. Correlation is hard. Every system sees a piece of the incident — Horus connects evidence across sources so the sequence of events becomes visible.
60
-
61
74
  ## Sources Horus investigates
62
75
 
63
- Logs · Metrics · Traces · Redis · MongoDB · Elasticsearch · BullMQ · Git · Ownership
76
+ Elasticsearch · Grafana · MongoDB · BullMQ · Git changes · Source graph · Queue map · Ownership
64
77
 
65
- Every signal is read-only and project-scoped.
78
+ Trace reconstruction is not shipped yet. Connectors are read-only and project-scoped.
66
79
 
67
- ## Example
80
+ ## Example output (illustrative)
68
81
 
69
82
  ```bash
70
83
  horus investigate \
@@ -74,89 +87,58 @@ horus investigate \
74
87
  ```
75
88
 
76
89
  ```text
77
- Collecting evidence...
78
- Elasticsearch
79
- ✓ Redis
80
- ✓ BullMQ
81
- ✓ Git
82
- ✓ Ownership
83
-
84
- Building timeline...
85
- ✓ 14 events reconstructed
90
+ # Investigation inv-347
91
+ Hint: checkout latency spike
86
92
 
87
- Correlating signals...
88
- 23 relationships identified
93
+ ## Suspected causes (ranked)
94
+ 1. [0.82 / high] Redis connection pool exhaustion [↑ queue]
89
95
 
90
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
96
+ ## Hypotheses
97
+ [supported] [0.78] queue: backlog growth preceded latency spike
91
98
 
92
- Root Cause: Redis connection pool exhaustion
93
- Confidence: 82%
99
+ ## Evidence gaps
100
+ - queue runtime state: worker heartbeat unavailable
94
101
 
95
- Supporting Evidence:
96
- Deploy #784
97
- Queue backlog growth
98
- • Worker starvation
99
- • Request timeout increase
102
+ ## Evidence
103
+ - ev-01 [elasticsearch/error] Request timeout increase
104
+ - ev-04 [bullmq/queue] checkout-jobs backlog growth
100
105
 
101
- Owner: payments-platform
102
- Next Action: Inspect worker concurrency changes
106
+ ## Next actions
107
+ - Inspect worker concurrency changes in deploy #784
103
108
  ```
104
109
 
105
110
  ## Principles
106
111
 
107
- **Read-only** — Horus never writes to your production systems. It collects evidence and leaves everything untouched.
112
+ **Read-only** — Horus never writes to your production systems.
108
113
 
109
- **Deterministic first** — Evidence before inference. Reports are built from signals and source context, not generated explanations.
114
+ **Deterministic first** — The engine is deterministic; optional `--ai` adds an Anthropic narrative.
110
115
 
111
- **Local-first** — Runs close to your infrastructure. Connectors read from your own clusters, not a hosted black box.
116
+ **Local-first** — Connectors read from your own clusters, not a hosted black box.
112
117
 
113
- **Project-scoped** — Every investigation belongs to a specific project and environment. No global defaults, no accidental cross-talk.
118
+ **Project-scoped** — Every investigation belongs to a specific project and environment.
114
119
 
115
120
  ## Capabilities
116
121
 
117
- Under active development.
122
+ Installable today. More connectors and AI providers are in progress.
118
123
 
119
124
  **Today**
120
125
 
121
- - Elasticsearch evidence
122
- - MongoDB evidence
123
- - BullMQ evidence
124
- - Source-code investigations
126
+ - Elasticsearch logs
127
+ - Grafana metrics
128
+ - MongoDB state
129
+ - BullMQ queue evidence
130
+ - Source intelligence (code graph)
125
131
  - Timeline generation
126
132
  - Evidence correlation
133
+ - Investigation replay
134
+ - Postmortem drafts
127
135
 
128
136
  **Coming next**
129
137
 
130
138
  - Kubernetes evidence
131
- - Trace reconstruction
132
- - Incident replay
139
+ - Distributed trace reconstruction
133
140
  - Slack evidence ingestion
134
-
135
- ## Can I use Horus today?
136
-
137
- The investigation engine runs end-to-end. Some surfaces require a local setup before they work — Postgres for the audit store, a source-intelligence host, and at least one configured runtime connector for live evidence.
138
-
139
- | Feature | Status | Notes |
140
- | ------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------- |
141
- | Install (`curl` or direct download) | Partial | Builds and runs from source; public binary release not yet published |
142
- | `horus init` | Works today | Creates `.horus/config.json` and registers the project |
143
- | `horus doctor` | Works today | Checks CLI, git root, config, and source-intelligence setup |
144
- | `horus setup` | Works today | Verifies prerequisites and guides fixes |
145
- | Source indexing (`horus index`) | Partial | Command works; requires a source-intelligence host running locally |
146
- | `horus investigate` | Works today | Full deterministic report; requires Postgres + at least one connector or git history |
147
- | `horus replay` | Works today | Re-renders a saved investigation from the audit store; no re-query |
148
- | `horus postmortem` | Works today | Drafts an editable Markdown postmortem from a saved investigation |
149
- | Runtime connectors (ES / Mongo / Grafana / Redis) | Partial | Connectors exist; each requires a live instance and per-project connector config |
150
- | AI narrative (`--ai` flag on `investigate`) | Partial | Requires `ANTHROPIC_API_KEY`; falls back to deterministic output automatically on failure |
151
- | Local AI provider bridge | Partial | Provider detection works; execution requires a local model installed |
152
-
153
- **Prerequisites before Horus works end-to-end:**
154
-
155
- - Postgres 16 (audit store) — `docker compose up -d` starts it
156
- - At least one runtime connector configured via `horus connect <type>`
157
- - Source-intelligence host running locally for source-aware commands (`horus index`, `horus explain`, `horus blast-radius`, `horus architecture`, `horus search`)
158
-
159
- `horus replay` and `horus postmortem` work from the saved audit store and require neither live connectors nor a source-intelligence host.
141
+ - Local AI provider execution
160
142
 
161
143
  ---
162
144
 
@@ -166,7 +148,7 @@ Horus is organized in four layers:
166
148
 
167
149
  **Source Intelligence**
168
150
 
169
- - Built-in source-intelligence backend — code graph, semantic search, impact analysis, ownership (see below).
151
+ - Horus source intelligence backend — code graph, semantic search, impact analysis, ownership.
170
152
 
171
153
  **Runtime Evidence**
172
154
 
@@ -189,19 +171,19 @@ Horus is organized in four layers:
189
171
 
190
172
  ### Source intelligence is built into Horus
191
173
 
192
- **Source intelligence is the expected intelligence layer used by Horus** — not an optional integration. Semantic search, impact analysis, ownership signals, change detection, and the process graph live in the source-intelligence backend; Horus does not duplicate them.
174
+ **Source intelligence is the expected intelligence layer used by Horus** — not an optional integration. Semantic search, impact analysis, ownership signals, change detection, and the process graph live in the Horus source intelligence backend; Horus does not duplicate them.
193
175
 
194
176
  The **only** code-intelligence gap Horus owns is **queue-boundary stitching**: the source graph terminates around `queue.add(...)` and doesn't connect a producer to the consumer's `@Processor`. The stitcher synthesizes those producer → queue → worker edges.
195
177
 
196
- > If the source-intelligence backend is unavailable, Horus can still collect runtime evidence, but source context, impact analysis, change analysis, and queue stitching become degraded.
178
+ > If the Horus source intelligence backend is unavailable, Horus can still collect runtime evidence, but source context, impact analysis, change analysis, and queue stitching become degraded.
197
179
 
198
- Horus talks to the source-intelligence backend over **HTTP/MCP only** (no CLI shell-outs for queries). Run `horus index` in a repository to start and register its source-intelligence host.
180
+ Horus talks to the source intelligence backend over **HTTP/MCP only** (no CLI shell-outs for queries). Run `horus index` in a repository to start and register its source intelligence host.
199
181
 
200
182
  ## Configuration
201
183
 
202
184
  The config model separates **code** from **runtime**:
203
185
 
204
- - **Code belongs to the project** — `repositories[]`, each served by its own source-intelligence host.
186
+ - **Code belongs to the project** — `repositories[]`, each served by its own source intelligence host.
205
187
  - **Runtime belongs to the environment** — `environments[].connectors` (Elasticsearch, MongoDB, Grafana, Redis/BullMQ).
206
188
 
207
189
  ```ts
@@ -248,28 +230,32 @@ export default defineConfig({
248
230
 
249
231
  ## Install
250
232
 
233
+ See **[docs/install.md](./docs/install.md)** for full install, update, and uninstall instructions.
234
+
251
235
  ```bash
252
236
  curl -fsSL https://horus.sh/install.sh | bash
237
+ npm install -g @merittdev/horus
238
+ brew install meritt-dev/tap/horus
253
239
  horus --version
254
240
  horus setup
255
241
  ```
256
242
 
257
- The installer downloads the Horus CLI from GitHub Releases and installs it to your PATH. No npm step required.
243
+ The curl installer downloads the Horus CLI from GitHub Releases and attempts to install the Horus source intelligence backend. All three channels install the same `horus` binary.
258
244
 
259
245
  ### What the installer installs
260
246
 
261
- | Component | Role | Required |
262
- | ------------------------------------- | ----------------------------------------------------------------------------- | -------- |
263
- | **Horus CLI** | The `horus` command | Yes |
264
- | **Horus source-intelligence backend** | Enables `horus index`, `horus explain`, `horus changes`, `horus architecture` | Optional |
247
+ | Component | Role | Required |
248
+ | --- | --- | --- |
249
+ | **Horus CLI** | The `horus` command | Yes |
250
+ | **Horus source intelligence backend** | Enables `horus index`, `horus explain`, `horus changes`, `horus architecture` | Optional |
265
251
 
266
252
  ### Prerequisites
267
253
 
268
- | Requirement | Role |
269
- | --------------------- | -------------------------------------------------------------------------------------------- |
270
- | Node.js 22+ | Horus CLI runtime (the installed binary needs Node.js) |
271
- | Postgres 16 | Investigation audit store — run locally via `docker compose up -d` or use a managed instance |
272
- | Python 3.11+ + uv/pip | Required only for the source-intelligence backend |
254
+ | Requirement | Role |
255
+ | --- | --- |
256
+ | Node.js 22+ | Horus CLI runtime (the installed binary needs Node.js) |
257
+ | Postgres 16 | Investigation audit store — run locally via `docker compose up -d` or use a managed instance |
258
+ | Python 3.11+ + uv/pip | Required only for the Horus source intelligence backend |
273
259
 
274
260
  The installer **does not** configure Elasticsearch, MongoDB, Grafana, Redis, or any production system. Runtime connectors are added per-project after install via `horus connect`.
275
261
 
@@ -283,20 +269,18 @@ sudo mv horus /usr/local/bin/horus
283
269
  horus --version
284
270
  ```
285
271
 
286
- **Package managers:** npm (`npm install -g @merittdev/horus`) is now live. The Homebrew tap (`meritt-dev/tap`) is prepared but still pending publish approval. See [docs/install.md](./docs/install.md#package-manager-installs) for the channel comparison and commands.
287
-
288
- To **update** to a newer version, re-run the installer — it overwrites the binary and leaves your config untouched. To **uninstall**, see **[docs/install.md#uninstall](./docs/install.md#uninstall)** for what to remove and what to keep.
272
+ To **update** to a newer version, re-run the installer it overwrites the binary and leaves your config untouched. To **uninstall**, see **[docs/install.md#uninstall](./docs/install.md#uninstall)**.
289
273
 
290
- If something goes wrong after install, see **[docs/troubleshooting.md](./docs/troubleshooting.md)** for symptoms, likely causes, and exact fix commands covering: config missing, database not running, source-intelligence host unreachable, no indexed repo, connector not configured, and low-confidence reports.
274
+ If something goes wrong after install, see **[docs/troubleshooting.md](./docs/troubleshooting.md)**.
291
275
 
292
- ## Local development / Usage
276
+ ## Local development
293
277
 
294
278
  ```bash
295
279
  pnpm install
296
280
  docker compose up -d # Postgres 16 on localhost:5433
297
281
  pnpm build # builds apps/horus/dist/index.cjs
298
282
 
299
- # Per repository: start the source-intelligence host and stitch queue boundaries
283
+ # Per repository: start the source intelligence host and stitch queue boundaries
300
284
  horus index
301
285
 
302
286
  source ~/.horus.env
@@ -322,15 +306,15 @@ horus investigate --help
322
306
 
323
307
  ### Core commands
324
308
 
325
- | Command | What it does |
326
- | --------------------------------------------------------------------------- | --------------------------------------------------------------------- |
327
- | `horus status [--project --env]` | Per-project/env connector-health matrix |
328
- | `horus index --project <p> --env <e>` | Build the queue map (stitcher) for a project |
329
- | `horus investigate --project <p> --env <e> "<hint>"` | Full deterministic investigation report |
330
- | `horus logs [service] --project <p> --env <e>` | Error-signature evidence (`--raw` for lines) |
331
- | `horus state --project <p> --env <e>` | MongoDB application-state evidence (read-only) |
332
- | `horus metrics [hint] --project <p> --env <e>` | Grafana metrics evidence |
333
- | `horus explain <symbol>` · `blast-radius` · `architecture` · `what-changed` | Source-aware code intelligence (requires source-intelligence backend) |
309
+ | Command | What it does |
310
+ | --- | --- |
311
+ | `horus status [--project --env]` | Per-project/env connector-health matrix |
312
+ | `horus index --project <p> --env <e>` | Build the queue map (stitcher) for a project |
313
+ | `horus investigate --project <p> --env <e> "<hint>"` | Full deterministic investigation report |
314
+ | `horus logs [service] --project <p> --env <e>` | Error-signature evidence (`--raw` for lines) |
315
+ | `horus state --project <p> --env <e>` | MongoDB application-state evidence (read-only) |
316
+ | `horus metrics [hint] --project <p> --env <e>` | Grafana metrics evidence |
317
+ | `horus explain <symbol>` · `blast-radius` · `architecture` · `what-changed` | Source-aware code intelligence (requires source intelligence backend) |
334
318
 
335
319
  ## Local project workflow (git-style)
336
320
 
@@ -347,7 +331,7 @@ horus investigate --name atlas-payments "checkout latency spike"
347
331
  horus projects
348
332
  ```
349
333
 
350
- `horus index` reuses an already-running source-intelligence host when one is healthy. Runtime connectors are added to the env block of `.horus/config.json` afterwards.
334
+ `horus index` reuses an already-running source intelligence host when one is healthy. Runtime connectors are added to the env block of `.horus/config.json` afterwards.
351
335
 
352
336
  ## Layout
353
337
 
@@ -366,6 +350,6 @@ config/ horus.config.ts
366
350
  ## Foundation
367
351
 
368
352
  - TypeScript monorepo (pnpm + Turborepo)
369
- - Postgres + Drizzle — semantic search delegated to source-intelligence backend
370
- - Built-in **source-intelligence backend**, over HTTP/MCP only
353
+ - Postgres + Drizzle — semantic search delegated to source intelligence backend
354
+ - Built-in **Horus source intelligence backend**, over HTTP/MCP only
371
355
  - Project/environment-scoped connectors; read-only against production
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 = "0.1.1";
50317
+ var HORUS_VERSION = "0.1.2";
50318
50318
  var PINNED_AXON_VERSION = "1.0.1";
50319
50319
  var PINNED_SOURCE_VERSION = PINNED_AXON_VERSION;
50320
50320
 
@@ -67040,6 +67040,22 @@ async function runIndex(opts) {
67040
67040
  ` investigate: horus investigate --name ${name} "<hint>" (or from this repo: horus investigate "<hint>")`
67041
67041
  )
67042
67042
  );
67043
+ } else if (spawned && !configuredHost) {
67044
+ const existingPath = discoverLocalConfig(root);
67045
+ if (existingPath) {
67046
+ const file = readLocalConfig(existingPath);
67047
+ const project = file.project;
67048
+ const repos = project["repositories"];
67049
+ if (repos && repos.length > 0) {
67050
+ repos[0]["source"] = { hostUrl };
67051
+ }
67052
+ writeLocalConfig(root, file);
67053
+ registerProject(label, root, existingPath);
67054
+ console.log(`${import_picocolors3.default.green("\u2713")} Indexed ${import_picocolors3.default.bold(label)} \u2014 source host registered at ${hostUrl}`);
67055
+ console.log(import_picocolors3.default.dim(` ${existingPath}`));
67056
+ } else {
67057
+ console.log(`${import_picocolors3.default.green("\u2713")} Indexed ${import_picocolors3.default.bold(label)} ${import_picocolors3.default.dim("(queue map refreshed)")}`);
67058
+ }
67043
67059
  } else {
67044
67060
  console.log(
67045
67061
  `${import_picocolors3.default.green("\u2713")} Indexed ${import_picocolors3.default.bold(label)} ${import_picocolors3.default.dim("(queue map refreshed)")}`
@@ -67435,20 +67451,23 @@ function factorRuntimeSignals(items, now) {
67435
67451
  const timestamps = items.filter((e) => e.timestamp !== void 0).map((e) => new Date(e.timestamp).getTime()).sort((a, b2) => b2 - a);
67436
67452
  const newestTs = timestamps[0];
67437
67453
  if (newestTs !== void 0) {
67438
- const ageMs = nowMs - newestTs;
67439
- if (ageMs <= 36e5) {
67440
- recencyDelta = 0.05;
67441
- recencyReason = "Evidence from within the last hour";
67442
- } else if (ageMs <= 864e5) {
67443
- recencyDelta = 0.02;
67444
- recencyReason = "Evidence from within the last 24 hours";
67445
- } else if (ageMs <= 2592e5) {
67446
- } else if (ageMs <= 6048e5) {
67447
- recencyDelta = -0.02;
67448
- recencyReason = "Most recent evidence is 3\u20137 days old \u2014 may predate this incident";
67449
- } else {
67450
- recencyDelta = -0.05;
67451
- recencyReason = "Most recent evidence is over 7 days old \u2014 likely predates this incident";
67454
+ const rawAgeMs = nowMs - newestTs;
67455
+ const ageMs = rawAgeMs < 0 ? rawAgeMs >= -3e5 ? 0 : null : rawAgeMs;
67456
+ if (ageMs !== null) {
67457
+ if (ageMs <= 36e5) {
67458
+ recencyDelta = 0.05;
67459
+ recencyReason = "Evidence from within the last hour";
67460
+ } else if (ageMs <= 864e5) {
67461
+ recencyDelta = 0.02;
67462
+ recencyReason = "Evidence from within the last 24 hours";
67463
+ } else if (ageMs <= 2592e5) {
67464
+ } else if (ageMs <= 6048e5) {
67465
+ recencyDelta = -0.02;
67466
+ recencyReason = "Most recent evidence is 3\u20137 days old \u2014 may predate this incident";
67467
+ } else {
67468
+ recencyDelta = -0.05;
67469
+ recencyReason = "Most recent evidence is over 7 days old \u2014 likely predates this incident";
67470
+ }
67452
67471
  }
67453
67472
  }
67454
67473
  let recurrenceDelta = 0;
@@ -71906,6 +71925,36 @@ async function runReplay(id, opts) {
71906
71925
  const fmt = opts.format ?? "text";
71907
71926
  const out = fmt === "json" ? reportToJSON(report) : fmt === "markdown" || fmt === "md" ? reportToMarkdown(report) : renderReport2(report);
71908
71927
  console.log(out);
71928
+ if (opts.ai && fmt !== "json") {
71929
+ const narrativeInput = buildNarrativeInput(report);
71930
+ const provider = new AnthropicNarrativeProvider({ model: opts.aiModel });
71931
+ const { output, fromProvider, validationErrors } = await renderNarrative(narrativeInput, { provider });
71932
+ if (!fromProvider) {
71933
+ console.error(import_picocolors13.default.yellow("[ai] Provider unavailable \u2014 deterministic output shown above."));
71934
+ if (validationErrors?.length) {
71935
+ console.error(import_picocolors13.default.dim(` ${validationErrors[0]}`));
71936
+ }
71937
+ } else {
71938
+ const sep = "\u2500".repeat(60);
71939
+ console.log(`
71940
+ ${sep}`);
71941
+ console.log(import_picocolors13.default.bold("AI Narrative"));
71942
+ console.log(sep);
71943
+ console.log(import_picocolors13.default.bold("What:"), output.what);
71944
+ console.log(import_picocolors13.default.bold("Why:"), output.why);
71945
+ if (output.whereNext.length > 0) {
71946
+ console.log(import_picocolors13.default.bold("Next steps:"));
71947
+ for (const step of output.whereNext) {
71948
+ console.log(` \u2022 ${step}`);
71949
+ }
71950
+ }
71951
+ if (output.citations.length > 0) {
71952
+ console.log(import_picocolors13.default.dim(`
71953
+ Cited evidence: ${output.citations.map((c) => c.evidenceId).join(", ")}`));
71954
+ }
71955
+ console.log(import_picocolors13.default.dim(`AI confidence: ${(output.confidence * 100).toFixed(0)}%`));
71956
+ }
71957
+ }
71909
71958
  } finally {
71910
71959
  await sql2.end();
71911
71960
  }
@@ -71963,7 +72012,39 @@ async function runPostmortem(id, opts) {
71963
72012
  }
71964
72013
  report = migrateReport(row.report);
71965
72014
  }
71966
- const content = generatePostmortem(report);
72015
+ let content = generatePostmortem(report);
72016
+ if (opts.aiSummary) {
72017
+ const narrativeInput = buildNarrativeInput(report);
72018
+ const provider = new AnthropicNarrativeProvider({ model: opts.aiModel });
72019
+ const { output, fromProvider, validationErrors } = await renderNarrative(narrativeInput, { provider });
72020
+ if (!fromProvider) {
72021
+ content += `
72022
+
72023
+ ## AI Summary
72024
+
72025
+ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
72026
+ `;
72027
+ } else {
72028
+ content += "\n\n## AI Summary\n\n";
72029
+ content += `**What happened:** ${output.what}
72030
+
72031
+ `;
72032
+ content += `**Why:** ${output.why}
72033
+ `;
72034
+ if (output.whereNext.length > 0) {
72035
+ content += "\n**Next steps:**\n";
72036
+ for (const step of output.whereNext) {
72037
+ content += `- ${step}
72038
+ `;
72039
+ }
72040
+ }
72041
+ if (output.citations.length > 0) {
72042
+ content += `
72043
+ **Cited evidence:** ${output.citations.map((c) => c.evidenceId).join(", ")}
72044
+ `;
72045
+ }
72046
+ }
72047
+ }
71967
72048
  if (opts.output) {
71968
72049
  const outputPath = (0, import_node_path7.resolve)(opts.output);
71969
72050
  if ((0, import_node_fs6.existsSync)(outputPath) && !opts.force) {
@@ -72517,7 +72598,7 @@ async function runInit(opts) {
72517
72598
  const name = opts.name ?? (0, import_node_path8.basename)(root);
72518
72599
  const envName = opts.env ?? "production";
72519
72600
  const repo = { name, path: root };
72520
- if (opts.axon) repo["axon"] = { hostUrl: opts.axon };
72601
+ if (opts.source) repo["source"] = { hostUrl: opts.source };
72521
72602
  const file = {
72522
72603
  version: 1,
72523
72604
  project: {
@@ -72531,9 +72612,9 @@ async function runInit(opts) {
72531
72612
  console.log(`${import_picocolors23.default.green("\u2713")} Initialized Horus project ${import_picocolors23.default.bold(name)}`);
72532
72613
  console.log(import_picocolors23.default.dim(` config: ${configPath}`));
72533
72614
  console.log(import_picocolors23.default.dim(` registered: horus investigate --name ${name} "<hint>"`));
72534
- if (!opts.axon) {
72615
+ if (!opts.source) {
72535
72616
  console.log(
72536
- import_picocolors23.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --axon <url>")
72617
+ import_picocolors23.default.dim(" no source-intelligence host set \u2014 run `horus index` to analyze + host, or pass --source <url>")
72537
72618
  );
72538
72619
  }
72539
72620
  console.log(
@@ -73301,7 +73382,7 @@ async function runDoctor(opts) {
73301
73382
  const project = file.project;
73302
73383
  const repos = project["repositories"];
73303
73384
  const hasHost = repos?.some(
73304
- (r) => r["axon"]?.["hostUrl"]
73385
+ (r) => r["source"]?.["hostUrl"] ?? r["axon"]?.["hostUrl"]
73305
73386
  );
73306
73387
  if (hasHost) {
73307
73388
  checks.push({ label: "Source-intelligence host", status: "pass", detail: "configured" });
@@ -73310,7 +73391,7 @@ async function runDoctor(opts) {
73310
73391
  label: "Source-intelligence host",
73311
73392
  status: "warn",
73312
73393
  detail: "not configured",
73313
- next: "run `horus index` to analyze this repo and start a host, or pass --axon <url> to `horus init`"
73394
+ next: "run `horus index` to analyze this repo and start a host, or pass --source <url> to `horus init`"
73314
73395
  });
73315
73396
  }
73316
73397
  } catch {
@@ -73566,7 +73647,7 @@ function configTemplate(name, repoPath) {
73566
73647
  repositories: [{
73567
73648
  name: '${name}',
73568
73649
  path: '${repoPath}',
73569
- axon: { hostUrl: 'http://127.0.0.1:8420' },
73650
+ source: { hostUrl: 'http://127.0.0.1:8420' },
73570
73651
  }],
73571
73652
  environments: [{
73572
73653
  name: 'production',
@@ -73828,9 +73909,14 @@ Examples:
73828
73909
  program2.command("setup").description("Verify prerequisites (source-intelligence backend + Postgres) and guide any fixes").option("-c, --config <path>", "path to horus.config.ts").action(async (opts) => {
73829
73910
  process.exitCode = await runSetup(opts);
73830
73911
  });
73831
- program2.command("init").description("Create a local .horus/config.json for this repo and register it").option("--name <name>", "project name (default: repo directory name)").option("--env <name>", "environment name (default: production)").option("--axon <url>", "Source-intelligence host URL for this repo (e.g. http://127.0.0.1:8420)").option("--path <dir>", "repository root (default: nearest git root, else cwd)").action(
73912
+ program2.command("init").description("Create a local .horus/config.json for this repo and register it").option("--name <name>", "project name (default: repo directory name)").option("--env <name>", "environment name (default: production)").option("--source <url>", "source-intelligence host URL for this repo (e.g. http://127.0.0.1:8420)").addOption(new Option("--axon <url>", "deprecated alias for --source").hideHelp()).option("--path <dir>", "repository root (default: nearest git root, else cwd)").action(
73832
73913
  async (opts) => {
73833
- process.exitCode = await runInit(opts);
73914
+ process.exitCode = await runInit({
73915
+ name: opts.name,
73916
+ env: opts.env,
73917
+ source: opts.source ?? opts.axon,
73918
+ path: opts.path
73919
+ });
73834
73920
  }
73835
73921
  ).addHelpText("after", `
73836
73922
  Examples:
@@ -73995,23 +74081,27 @@ Examples:
73995
74081
  horus investigations
73996
74082
  horus investigations -n 20
73997
74083
  `);
73998
- program2.command("replay <id>").description("Re-render a saved investigation from the audit store (no re-query)").option("-c, --config <path>", "path to horus.config.ts").option("--format <fmt>", "text | markdown | json", "text").action(async (id, opts) => {
73999
- process.exitCode = await runReplay(id, { config: opts.config, format: opts.format });
74084
+ program2.command("replay <id>").description("Re-render a saved investigation from the audit store (no re-query)").option("-c, --config <path>", "path to horus.config.ts").option("--format <fmt>", "text | markdown | json", "text").option("--ai", "enrich report with AI narrative (requires ANTHROPIC_API_KEY; falls back to deterministic on failure)").option("--ai-model <model>", "AI model for --ai (default: claude-opus-4-8)").action(async (id, opts) => {
74085
+ process.exitCode = await runReplay(id, { config: opts.config, format: opts.format, ai: opts.ai, aiModel: opts.aiModel });
74000
74086
  }).addHelpText("after", `
74001
74087
  Examples:
74002
74088
  horus replay <id>
74003
74089
  horus replay <id> --format markdown
74004
74090
  horus replay <id> --format json
74091
+ horus replay <id> --ai
74092
+ horus replay <id> --ai --ai-model claude-sonnet-4-6
74005
74093
 
74006
74094
  (Use 'horus investigations' to list saved investigation ids.)
74007
74095
  `);
74008
- program2.command("postmortem <id>").description("Draft an editable incident postmortem from a saved investigation").option("-c, --config <path>", "path to horus.config.ts").option("--output <path>", "write Markdown to a file instead of printing to stdout").option("--force", "overwrite the output file if it already exists").action(async (id, opts) => {
74009
- process.exitCode = await runPostmortem(id, { config: opts.config, output: opts.output, force: opts.force });
74096
+ program2.command("postmortem <id>").description("Draft an editable incident postmortem from a saved investigation").option("-c, --config <path>", "path to horus.config.ts").option("--output <path>", "write Markdown to a file instead of printing to stdout").option("--force", "overwrite the output file if it already exists").option("--ai-summary", "append an AI-generated summary section (requires ANTHROPIC_API_KEY; falls back gracefully)").option("--ai-model <model>", "AI model for --ai-summary (default: claude-opus-4-8)").action(async (id, opts) => {
74097
+ process.exitCode = await runPostmortem(id, { config: opts.config, output: opts.output, force: opts.force, aiSummary: opts.aiSummary, aiModel: opts.aiModel });
74010
74098
  }).addHelpText("after", `
74011
74099
  Examples:
74012
74100
  horus postmortem <id>
74013
74101
  horus postmortem <id> --output ./postmortem.md
74014
74102
  horus postmortem <id> --output ./postmortem.md --force
74103
+ horus postmortem <id> --ai-summary
74104
+ horus postmortem <id> --output ./postmortem.md --ai-summary --ai-model claude-sonnet-4-6
74015
74105
 
74016
74106
  (Use 'horus investigations' to list saved investigation ids.)
74017
74107
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {