@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.
- package/README.md +100 -116
- package/dist/index.cjs +118 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,65 +6,78 @@
|
|
|
6
6
|
|
|
7
7
|
**Understand what happened.**
|
|
8
8
|
|
|
9
|
-
Horus
|
|
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
|
|
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** —
|
|
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**
|
|
34
|
-
| **Observability** | Shows signals
|
|
35
|
-
| **Horus**
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
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
|
-
|
|
76
|
+
Elasticsearch · Grafana · MongoDB · BullMQ · Git changes · Source graph · Queue map · Ownership
|
|
64
77
|
|
|
65
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
93
|
-
|
|
99
|
+
## Evidence gaps
|
|
100
|
+
- queue runtime state: worker heartbeat unavailable
|
|
94
101
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
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.
|
|
112
|
+
**Read-only** — Horus never writes to your production systems.
|
|
108
113
|
|
|
109
|
-
**Deterministic first** —
|
|
114
|
+
**Deterministic first** — The engine is deterministic; optional `--ai` adds an Anthropic narrative.
|
|
110
115
|
|
|
111
|
-
**Local-first** —
|
|
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.
|
|
118
|
+
**Project-scoped** — Every investigation belongs to a specific project and environment.
|
|
114
119
|
|
|
115
120
|
## Capabilities
|
|
116
121
|
|
|
117
|
-
|
|
122
|
+
Installable today. More connectors and AI providers are in progress.
|
|
118
123
|
|
|
119
124
|
**Today**
|
|
120
125
|
|
|
121
|
-
- Elasticsearch
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
262
|
-
|
|
|
263
|
-
| **Horus CLI**
|
|
264
|
-
| **Horus source
|
|
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
|
|
269
|
-
|
|
|
270
|
-
| Node.js 22+
|
|
271
|
-
| Postgres 16
|
|
272
|
-
| Python 3.11+ + uv/pip | Required only for the source
|
|
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
|
-
**
|
|
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)
|
|
274
|
+
If something goes wrong after install, see **[docs/troubleshooting.md](./docs/troubleshooting.md)**.
|
|
291
275
|
|
|
292
|
-
## Local development
|
|
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
|
|
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
|
|
326
|
-
|
|
|
327
|
-
| `horus status [--project --env]`
|
|
328
|
-
| `horus index --project <p> --env <e>`
|
|
329
|
-
| `horus investigate --project <p> --env <e> "<hint>"`
|
|
330
|
-
| `horus logs [service] --project <p> --env <e>`
|
|
331
|
-
| `horus state --project <p> --env <e>`
|
|
332
|
-
| `horus metrics [hint] --project <p> --env <e>`
|
|
333
|
-
| `horus explain <symbol>` · `blast-radius` · `architecture` · `what-changed` | Source-aware code intelligence (requires source
|
|
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
|
|
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
|
|
370
|
-
- Built-in **source
|
|
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.
|
|
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
|
|
67439
|
-
|
|
67440
|
-
|
|
67441
|
-
|
|
67442
|
-
|
|
67443
|
-
|
|
67444
|
-
|
|
67445
|
-
|
|
67446
|
-
|
|
67447
|
-
|
|
67448
|
-
|
|
67449
|
-
|
|
67450
|
-
|
|
67451
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 --
|
|
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 --
|
|
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
|
-
|
|
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("--
|
|
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(
|
|
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
|
`);
|