engramx 1.0.1 → 2.0.1

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 (32) hide show
  1. package/CHANGELOG.md +150 -0
  2. package/README.md +67 -7
  3. package/dist/{aider-context-TNGSXMVY.js → aider-context-BC5R2ZTA.js} +1 -1
  4. package/dist/cache-AK6CF3BC.js +10 -0
  5. package/dist/chunk-22INHMKB.js +31 -0
  6. package/dist/chunk-533LR4I7.js +220 -0
  7. package/dist/{chunk-QOG4K427.js → chunk-C6GBUOAL.js} +1 -1
  8. package/dist/chunk-CIQQ5Y3S.js +338 -0
  9. package/dist/chunk-KL6NSPVA.js +59 -0
  10. package/dist/{chunk-SBHGK5WA.js → chunk-PEH54LYC.js} +85 -3
  11. package/dist/{chunk-6SFMVYUN.js → chunk-SJT7VS2G.js} +127 -23
  12. package/dist/cli.js +383 -258
  13. package/dist/{core-77MHT3QV.js → core-6IY5L6II.js} +2 -2
  14. package/dist/{cursor-mdc-HWVUZUZH.js → cursor-mdc-GJ7E5LDD.js} +1 -1
  15. package/dist/{exporter-A3VSLS4U.js → exporter-GWU2GF23.js} +1 -1
  16. package/dist/grammars/tree-sitter-go.wasm +0 -0
  17. package/dist/grammars/tree-sitter-javascript.wasm +0 -0
  18. package/dist/grammars/tree-sitter-python.wasm +0 -0
  19. package/dist/grammars/tree-sitter-rust.wasm +0 -0
  20. package/dist/grammars/tree-sitter-tsx.wasm +0 -0
  21. package/dist/grammars/tree-sitter-typescript.wasm +0 -0
  22. package/dist/{importer-LU2YFZDY.js → importer-V62NGZRK.js} +1 -1
  23. package/dist/index.js +3 -3
  24. package/dist/{migrate-5ZJWF2HD.js → migrate-UKCO6BUU.js} +3 -1
  25. package/dist/plugin-loader-STTGYIL5.js +106 -0
  26. package/dist/serve.js +2 -2
  27. package/dist/server-6AOI7NQP.js +1370 -0
  28. package/dist/{tuner-2LVIEE5V.js → tuner-KFNNGKG3.js} +4 -2
  29. package/dist/windsurf-rules-C7SVDHBL.js +59 -0
  30. package/package.json +4 -3
  31. package/dist/chunk-CEAANHHX.js +0 -88
  32. package/dist/server-I3C74ZLB.js +0 -193
package/CHANGELOG.md CHANGED
@@ -4,6 +4,156 @@ All notable changes to engram are documented here. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.0.1] — 2026-04-17 — Windows CI + favicon route
8
+
9
+ Patch release fixing two issues caught immediately after v2.0.0 shipped.
10
+
11
+ ### Fixed
12
+
13
+ - **Windows cross-platform bug in the plugin loader.** `PLUGINS_DIR` was a
14
+ module-load-time constant that baked in `homedir()` at import time. Windows
15
+ uses `USERPROFILE` while Unix uses `HOME`, and a frozen constant meant any
16
+ runtime override (tests, future `--plugins-dir` flag, programmatic use)
17
+ couldn't take effect without a module reload. Windows CI failed on the
18
+ plugin-loader tests because `process.env.HOME` mutation had no effect.
19
+ Fixed by introducing `getPluginsDir()` that resolves on every call, and
20
+ accepting an optional `dir` parameter on `loadPlugins()`,
21
+ `getLoadedPlugins()`, and `ensurePluginsDir()`. The `PLUGINS_DIR` constant
22
+ is retained for back-compat but runtime paths now go through the getter.
23
+ - **`/favicon.ico` returning 404 for clients that ignore `<link rel="icon">`.**
24
+ Added an explicit `GET /favicon.ico` route to the HTTP server that serves
25
+ a 238-byte inline SVG favicon with `Cache-Control: public, max-age=86400`.
26
+ The dashboard HTML still inlines the same favicon via `<link>` so modern
27
+ browsers avoid the request entirely.
28
+
29
+ ### Changed
30
+
31
+ - Test count: 640 → 641 (+1 for the "plugins directory does not exist"
32
+ branch of `loadPlugins()`).
33
+
34
+ ### CI
35
+
36
+ - Verified green on GitHub Actions matrix: Ubuntu + Windows × Node 20 + 22.
37
+ Commit `7c6001c`.
38
+
39
+ ## [2.0.0] — 2026-04-17 — "Ecosystem"
40
+
41
+ The biggest release since v1.0.0. Completes the v2.0 roadmap Phases 1–4:
42
+ Foundation, Web Dashboard, Integration Expansion, and Solidification.
43
+ 640 tests (up from 579). All changes backward-compatible with v1.x graph
44
+ files — existing `.engram/graph.db` files auto-migrate to schema v7.
45
+
46
+ ### Added — Phase 3 (Integration Expansion)
47
+
48
+ - **`engram gen-windsurfrules`** — generate `.windsurfrules` for Windsurf
49
+ (Codeium) IDE. Plain markdown (no frontmatter), auto-picked-up by Windsurf
50
+ on every chat session. Supports `--watch` for live regeneration on graph
51
+ changes. See `docs/integrations/README.md`.
52
+ - **Integration docs**: `docs/integrations/neovim.md` (codecompanion +
53
+ avante.nvim via mcphub), `docs/integrations/cursor-mcp.md` (Cursor's
54
+ native MCP support alongside the MDC path), `docs/integrations/emacs.md`
55
+ (gptel + gptel-mcp), plus a new `docs/integrations/README.md` index that
56
+ maps every supported IDE to its mechanism.
57
+
58
+ ### Added — Phase 4 (Solidification)
59
+
60
+ - **Provider plugin system** — third-party context providers installable
61
+ at `~/.engram/plugins/*.mjs`. Each plugin default-exports a
62
+ `ContextProviderPlugin` object and is dynamically loaded by the resolver.
63
+ Validation-before-install refuses malformed plugins; duplicate names
64
+ can't shadow built-ins. New CLI:
65
+ - `engram plugin list` — show installed plugins with tier/budget/version
66
+ - `engram plugin install <file.mjs>` — validate + copy into plugins dir
67
+ - `engram plugin remove <filename>`
68
+ - **`engram cache` CLI namespace** — inspect and manage the context cache:
69
+ - `engram cache stats` — hit rate, entries per layer, hot file count
70
+ - `engram cache clear` — flush all layers
71
+ - `engram cache warm` — pre-warm hot files from access frequency
72
+ - **Schema rollback** — `engram db rollback --to <version>` reverts the
73
+ schema with automatic backup at `<dbPath>.bak-v<fromVersion>`. Requires
74
+ explicit `--yes` confirmation (data loss). Rollback is in-session only;
75
+ reopen auto-migrates forward again by design — the backup is the
76
+ recovery path for pinning an older version.
77
+ - **Schema v7** — adds `query_cache` and `pattern_cache` tables with
78
+ `idx_query_cache_file` index. Retroactive DOWN migrations defined for
79
+ versions 1–7 so any version is rollback-target-safe.
80
+
81
+ ### Added — Phase 1 (Foundation)
82
+
83
+ - **Tree-sitter grammar bundling** — 6 WASM grammar files (TypeScript, TSX,
84
+ JavaScript, Python, Go, Rust) now ship inside the npm package at
85
+ `dist/grammars/`. The `engram:ast` provider works out of the box for npm
86
+ users without needing local `node_modules` tree-sitter packages.
87
+ New: `scripts/bundle-grammars.mjs` (pure Node ESM, no tsx dep).
88
+ `npm run build` bundles them automatically; `build:nogrammars` as escape hatch.
89
+ - **Incremental indexing** — `init()` accepts `{ incremental: true }` and the
90
+ CLI accepts `--incremental` to skip files whose mtime hasn't changed since
91
+ last index. File mtimes persisted as JSON in the stats table. On engram's
92
+ own source (117 TS files): 53ms vs 244ms full init — **78% faster**.
93
+ - **`.engramignore` support** — gitignore-like syntax for excluding directories
94
+ and files from indexing. Loaded from project root.
95
+ - **Memory cache system** (`src/intelligence/cache.ts`, ~330 LOC) — 3-layer
96
+ compound savings engine:
97
+ - **Query result cache** — resolved context packets per file, SQLite-backed
98
+ + in-memory LRU (100 entries). Invalidated on file mtime change.
99
+ Benchmarked at 23μs/op, 99% hit rate under 10k random reads.
100
+ - **Pattern cache** — structural query answers memoized with graph version
101
+ tracking. LRU (50 entries). Auto-invalidates on graph mutation.
102
+ - **Hot file cache** — `warmHotFiles()` pre-loads top-N most-accessed files
103
+ at SessionStart for zero first-hit latency.
104
+ - `engram cache` CLI namespace (stats/clear/warm) — planned for Phase 4.
105
+ - **9 new HTTP API endpoints** serving the dashboard + external integrations:
106
+ - `GET /api/hook-log` — paginated hook log entries
107
+ - `GET /api/hook-log/summary` — aggregated event/tool/decision stats
108
+ - `GET /api/tokens` — cumulative token savings
109
+ - `GET /api/files/heatmap` — file interception frequency ranking
110
+ - `GET /api/providers/health` — component status
111
+ - `GET /api/cache/stats` — cache hit/miss rates, entry counts
112
+ - `GET /api/graph/nodes` — paginated graph nodes
113
+ - `GET /api/graph/god-nodes` — top-connected entities
114
+ - `GET /api/sse` — Server-Sent Events, 1s hook-log polling, auto-cleans on
115
+ disconnect
116
+ - Load tested at 200 concurrent mixed requests in 295ms (~1.5ms/req).
117
+
118
+ ### Added — Phase 2 (Web Dashboard)
119
+
120
+ - **Zero-dependency web dashboard at `GET /ui`** — 35KB self-contained
121
+ HTML/CSS/JS as TypeScript template literals. No external CDNs, no build
122
+ pipeline, works offline / on air-gapped machines.
123
+ - Security: CSP meta tag (`default-src 'self'; connect-src 'self'`) + single
124
+ `esc()` helper at every JS→HTML boundary defends against XSS from
125
+ attacker-controllable file paths/labels mined from repos.
126
+ - 6 tabs: Overview, Sessions, Activity (live SSE), Files, Graph (Canvas 2D
127
+ force-directed, ~200 LOC, handles <500 nodes at 60fps with pan/zoom/click),
128
+ Providers.
129
+ - SVG chart library (`ui-components.ts`): donut, stacked bars, sparkline,
130
+ cache/graph stat blocks. Zero dependencies.
131
+ - **`engram ui` CLI command** — auto-starts the HTTP server if not running
132
+ (PID file check), opens the default browser to `http://127.0.0.1:7337/ui`.
133
+ `--no-open` flag for print-only mode.
134
+
135
+ ### Changed
136
+
137
+ - `npm run build` now always bundles tree-sitter grammars into `dist/grammars/`
138
+ (previously only `prepublishOnly` did, which masked the missing-grammar
139
+ scenario for anyone testing a built `dist/` locally).
140
+ - `--incremental` flag wired into the CLI (previously API-only and undiscoverable).
141
+ - Default skip directories expanded: added `.next`, `.nuxt`, `coverage`,
142
+ `target`, `venv`, `.venv`, `.cache`, `.turbo`, `.output`, `.git`.
143
+ - `extractFile()` now returns `lineCount` from content already parsed,
144
+ eliminating a redundant `readFileSync` per file during extraction.
145
+ - `GraphStore` extended with `runSql()`, `prepare()` (public), and
146
+ `removeNodesForFile()` for incremental mode and cache module.
147
+ - Test count: 579 → 640 (+61 tests: 7 incremental + 17 cache + 15 API + 6
148
+ windsurf + 5 rollback + 11 plugin-loader).
149
+
150
+ ### Fixed
151
+
152
+ - `dist/grammars/` was empty after `npm run build` alone — only populated by
153
+ `prepublishOnly`. Masked in dev by `grammar-loader.ts` falling back to
154
+ `node_modules/tree-sitter-*/`, but shipped broken to anyone running `dist/`
155
+ locally after build. Root cause: tsup's `clean: true` wiped the dir.
156
+
7
157
  ## [1.0.0] — 2026-04-17 — "Protocol"
8
158
 
9
159
  ### Added
package/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  <p align="center">
6
6
  <a href="#install"><strong>Install</strong></a> ·
7
7
  <a href="#quickstart"><strong>Quickstart</strong></a> ·
8
+ <a href="#dashboard"><strong>Dashboard</strong></a> ·
8
9
  <a href="#benchmark"><strong>Benchmark</strong></a> ·
9
10
  <a href="#ide-integrations"><strong>IDE Integrations</strong></a> ·
10
11
  <a href="#http-api"><strong>HTTP API</strong></a> ·
@@ -14,17 +15,22 @@
14
15
 
15
16
  <p align="center">
16
17
  <a href="https://github.com/NickCirv/engram/actions"><img src="https://github.com/NickCirv/engram/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
18
+ <a href="https://www.npmjs.com/package/engramx"><img src="https://img.shields.io/npm/v/engramx?color=blue" alt="npm version"></a>
17
19
  <img src="https://img.shields.io/badge/license-Apache%202.0-blue" alt="License">
18
20
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="Node">
19
- <img src="https://img.shields.io/badge/tests-579%20passing-brightgreen" alt="Tests">
20
- <img src="https://img.shields.io/badge/providers-8-blue" alt="8 Providers">
21
- <img src="https://img.shields.io/badge/token%20savings-88%25%20proven-orange" alt="88% Proven Savings">
21
+ <img src="https://img.shields.io/badge/tests-640%20passing-brightgreen" alt="Tests">
22
+ <img src="https://img.shields.io/badge/providers-8%20%2B%20plugins-blue" alt="8 Providers + plugins">
23
+ <img src="https://img.shields.io/badge/token%20savings-88.1%25%20measured-orange" alt="88% Proven Savings">
22
24
  <img src="https://img.shields.io/badge/native%20deps-zero-green" alt="Zero native deps">
23
25
  <img src="https://img.shields.io/badge/LLM%20cost-$0-green" alt="Zero LLM cost">
24
26
  </p>
25
27
 
26
28
  ---
27
29
 
30
+ > **v2.0 "Ecosystem" shipped 2026-04-17** — web dashboard at `engram ui`, 3-layer memory cache (23μs/op at 99% hit rate), provider plugin system (`~/.engram/plugins/*.mjs`), `engram cache` CLI, schema rollback with automatic backup, incremental re-indexing (78% faster on large repos), auto-bundled tree-sitter grammars, Windsurf + Neovim + Emacs integrations. See [CHANGELOG.md](CHANGELOG.md) for the full diff.
31
+
32
+ ---
33
+
28
34
  # The context spine for AI coding agents.
29
35
 
30
36
  engram intercepts every file read your AI agent makes and replaces it with a pre-assembled context packet — structure, decisions, git history, library docs, and known issues — from 8 providers, delivered in a single ~500-token response. The agent gets what it needs without reading the file. You stop paying for context you've already paid for.
@@ -42,6 +48,55 @@ That's the full setup. The next Claude Code session starts with a project brief
42
48
 
43
49
  ---
44
50
 
51
+ ## Dashboard
52
+
53
+ A zero-dependency web dashboard ships built-in. One command, opens in your browser:
54
+
55
+ ```bash
56
+ engram ui
57
+ ```
58
+
59
+ <p align="center">
60
+ <img src="assets/screenshots/01-overview.png" alt="engram dashboard — Overview tab" width="100%">
61
+ </p>
62
+
63
+ The **Overview** tab: real metrics from your sessions — tokens saved, cost saved at $3/M rate, session-level hit rate, cache performance, graph health.
64
+
65
+ <p align="center">
66
+ <img src="assets/screenshots/03-activity.png" alt="engram dashboard — Activity tab" width="100%">
67
+ </p>
68
+
69
+ **Activity** — live hook events streamed via Server-Sent Events. See every `Read` / `Edit` / `Write` decision (deny = intercepted, passthrough = engram couldn't help). Per-tool breakdown on the right shows where the savings come from.
70
+
71
+ <p align="center">
72
+ <img src="assets/screenshots/04-files.png" alt="engram dashboard — Files heatmap" width="100%">
73
+ </p>
74
+
75
+ **Files** — the heatmap ranks your hot files by interception count. Cursor knows this view.
76
+
77
+ <p align="center">
78
+ <img src="assets/screenshots/05-graph.png" alt="engram dashboard — Knowledge graph visualization" width="100%">
79
+ </p>
80
+
81
+ **Graph** — Canvas 2D force-directed visualization of the knowledge graph. God nodes are larger and labeled. Drag to pan, scroll to zoom, click for details. 300+ nodes at 60fps.
82
+
83
+ <p align="center">
84
+ <img src="assets/screenshots/06-providers.png" alt="engram dashboard — Providers + cache health" width="100%">
85
+ </p>
86
+
87
+ **Providers** — component health (HTTP / LSP / AST / IDE count) and per-layer cache stats (entries + cross-session hit counts).
88
+
89
+ ### Design
90
+
91
+ - **35KB total** — one HTTP response, zero external CDN calls, works offline and on air-gapped machines.
92
+ - **Zero runtime dependencies** — all CSS and JS inlined as TypeScript template literals; SVG charts and Canvas 2D graph hand-rolled (~400 LOC total).
93
+ - **CSP-hardened** — `default-src 'self'; connect-src 'self'` meta tag plus `esc()` at every data-to-HTML boundary. Defends against attacker-controlled file paths and labels mined from untrusted repos.
94
+ - **Live-updating** — SSE stream pushes new hook events to the Activity tab within 1 second.
95
+
96
+ See also the **Sessions** tab (cumulative breakdown + sparkline) in [`assets/screenshots/02-sessions.png`](assets/screenshots/02-sessions.png).
97
+
98
+ ---
99
+
45
100
  ## Benchmark
46
101
 
47
102
  Measured across 10 structured coding tasks against a baseline of reading the relevant files directly. No synthetic data. No cherry-picked queries.
@@ -128,12 +183,14 @@ Requires Node.js 20+. Zero native dependencies. No build tools. Local SQLite via
128
183
  cd ~/my-project
129
184
  engram init # scan codebase → .engram/graph.db (~40ms, 0 tokens)
130
185
  engram install-hook # wire the Sentinel into Claude Code
186
+ engram ui # open the web dashboard in your browser
131
187
  ```
132
188
 
133
189
  Open a Claude Code session. When the agent reads a well-covered file you will see a system-reminder with the structural summary instead of file contents. After the session:
134
190
 
135
191
  ```bash
136
- engram hook-stats # what was intercepted, tokens saved
192
+ engram hook-stats # what was intercepted, tokens saved (CLI)
193
+ engram ui # same data, richer view, real-time updates
137
194
  engram hook-preview src/auth.ts # dry-run: see what the hook would inject for one file
138
195
  ```
139
196
 
@@ -165,10 +222,13 @@ engram hooks install # auto-rebuild graph on every git commit
165
222
  | IDE | Integration | Setup |
166
223
  |-----|------------|-------|
167
224
  | **Claude Code** | Hook-based interception (native, automatic) | `engram install-hook` |
168
- | **Continue.dev** | `@engram` context provider | See [docs/integrations/continue.md](docs/integrations/continue.md) |
169
- | **Cursor** | MDC generation | `engram gen-mdc` |
170
- | **Zed** | Context server | `engram context-server` |
225
+ | **Cursor** | MDC snapshot + native MCP | `engram gen-mdc` &middot; [docs/integrations/cursor-mcp.md](docs/integrations/cursor-mcp.md) |
226
+ | **Continue.dev** | `@engram` context provider | [docs/integrations/continue.md](docs/integrations/continue.md) |
227
+ | **Zed** | Context server (`/engram`) | `engram context-server` |
171
228
  | **Aider** | Context file generation | `engram gen-aider` |
229
+ | **Windsurf** (Codeium) | `.windsurfrules` snapshot + MCP | `engram gen-windsurfrules` |
230
+ | **Neovim** | MCP via codecompanion / avante | [docs/integrations/neovim.md](docs/integrations/neovim.md) |
231
+ | **Emacs** | MCP via gptel-mcp | [docs/integrations/emacs.md](docs/integrations/emacs.md) |
172
232
 
173
233
  Per-IDE setup guides are in [`docs/integrations/`](docs/integrations/).
174
234
 
@@ -7,7 +7,7 @@ function buildSection(heading, lines) {
7
7
  return [`## ${heading}`, "", ...lines, ""].join("\n");
8
8
  }
9
9
  async function generateAiderContext(projectRoot) {
10
- const { getStore } = await import("./core-77MHT3QV.js");
10
+ const { getStore } = await import("./core-6IY5L6II.js");
11
11
  const store = await getStore(projectRoot);
12
12
  try {
13
13
  const allNodes = store.getAllNodes();
@@ -0,0 +1,10 @@
1
+ import {
2
+ ContextCache,
3
+ _resetContextCache,
4
+ getContextCache
5
+ } from "./chunk-CIQQ5Y3S.js";
6
+ export {
7
+ ContextCache,
8
+ _resetContextCache,
9
+ getContextCache
10
+ };
@@ -0,0 +1,31 @@
1
+ // src/tuner/config.ts
2
+ import { existsSync, readFileSync, writeFileSync } from "fs";
3
+ import { join } from "path";
4
+ var DEFAULTS = {
5
+ confidenceThreshold: 0.7,
6
+ totalTokenBudget: 600,
7
+ providers: {}
8
+ };
9
+ function readConfig(projectRoot) {
10
+ const configPath = join(projectRoot, ".engram", "config.json");
11
+ if (!existsSync(configPath)) return DEFAULTS;
12
+ try {
13
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
14
+ return {
15
+ ...DEFAULTS,
16
+ ...raw,
17
+ providers: { ...DEFAULTS.providers, ...raw.providers ?? {} }
18
+ };
19
+ } catch {
20
+ return DEFAULTS;
21
+ }
22
+ }
23
+ function writeConfig(projectRoot, config) {
24
+ const configPath = join(projectRoot, ".engram", "config.json");
25
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
26
+ }
27
+
28
+ export {
29
+ readConfig,
30
+ writeConfig
31
+ };
@@ -0,0 +1,220 @@
1
+ // src/intercept/stats.ts
2
+ var ESTIMATED_TOKENS_PER_READ_DENY = 1200;
3
+ function summarizeHookLog(entries) {
4
+ const byEvent = {};
5
+ const byTool = {};
6
+ const byDecision = {};
7
+ let readDenyCount = 0;
8
+ let firstEntryTs = null;
9
+ let lastEntryTs = null;
10
+ for (const entry of entries) {
11
+ const event = entry.event ?? "unknown";
12
+ byEvent[event] = (byEvent[event] ?? 0) + 1;
13
+ const tool = entry.tool ?? "unknown";
14
+ byTool[tool] = (byTool[tool] ?? 0) + 1;
15
+ if (entry.decision) {
16
+ byDecision[entry.decision] = (byDecision[entry.decision] ?? 0) + 1;
17
+ }
18
+ if (event === "PreToolUse" && tool === "Read" && entry.decision === "deny") {
19
+ readDenyCount += 1;
20
+ }
21
+ const ts = entry.ts;
22
+ if (typeof ts === "string") {
23
+ if (firstEntryTs === null || ts < firstEntryTs) firstEntryTs = ts;
24
+ if (lastEntryTs === null || ts > lastEntryTs) lastEntryTs = ts;
25
+ }
26
+ }
27
+ return {
28
+ totalInvocations: entries.length,
29
+ byEvent: Object.freeze(byEvent),
30
+ byTool: Object.freeze(byTool),
31
+ byDecision: Object.freeze(byDecision),
32
+ readDenyCount,
33
+ estimatedTokensSaved: readDenyCount * ESTIMATED_TOKENS_PER_READ_DENY,
34
+ firstEntry: firstEntryTs,
35
+ lastEntry: lastEntryTs
36
+ };
37
+ }
38
+ function formatStatsSummary(summary) {
39
+ if (summary.totalInvocations === 0) {
40
+ return "engram hook stats: no log entries yet.\n\nRun engram install-hook in a project, then use Claude Code to see interceptions.";
41
+ }
42
+ const lines = [];
43
+ lines.push(`engram hook stats (${summary.totalInvocations} invocations)`);
44
+ lines.push("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
45
+ if (summary.firstEntry && summary.lastEntry) {
46
+ lines.push(`Time range: ${summary.firstEntry} \u2192 ${summary.lastEntry}`);
47
+ lines.push("");
48
+ }
49
+ lines.push("By event:");
50
+ const eventEntries = Object.entries(summary.byEvent).sort(
51
+ (a, b) => b[1] - a[1]
52
+ );
53
+ for (const [event, count] of eventEntries) {
54
+ const pct = (count / summary.totalInvocations * 100).toFixed(1);
55
+ lines.push(` ${event.padEnd(18)} ${String(count).padStart(5)} (${pct}%)`);
56
+ }
57
+ lines.push("");
58
+ lines.push("By tool:");
59
+ const toolEntries = Object.entries(summary.byTool).filter(([k]) => k !== "unknown").sort((a, b) => b[1] - a[1]);
60
+ for (const [tool, count] of toolEntries) {
61
+ lines.push(` ${tool.padEnd(18)} ${String(count).padStart(5)}`);
62
+ }
63
+ if (toolEntries.length === 0) {
64
+ lines.push(" (no tool-tagged entries)");
65
+ }
66
+ lines.push("");
67
+ const decisionEntries = Object.entries(summary.byDecision);
68
+ if (decisionEntries.length > 0) {
69
+ lines.push("PreToolUse decisions:");
70
+ for (const [decision, count] of decisionEntries.sort(
71
+ (a, b) => b[1] - a[1]
72
+ )) {
73
+ lines.push(` ${decision.padEnd(18)} ${String(count).padStart(5)}`);
74
+ }
75
+ lines.push("");
76
+ }
77
+ if (summary.readDenyCount > 0) {
78
+ lines.push(
79
+ `Estimated tokens saved: ~${summary.estimatedTokensSaved.toLocaleString()}`
80
+ );
81
+ lines.push(
82
+ ` (${summary.readDenyCount} Read denies \xD7 ${ESTIMATED_TOKENS_PER_READ_DENY} tok/deny avg)`
83
+ );
84
+ } else {
85
+ lines.push("Estimated tokens saved: 0");
86
+ lines.push(" (no PreToolUse:Read denies recorded yet)");
87
+ }
88
+ return lines.join("\n");
89
+ }
90
+
91
+ // src/intercept/component-status.ts
92
+ import { existsSync, readFileSync, writeFileSync } from "fs";
93
+ import { join, dirname } from "path";
94
+ import { fileURLToPath } from "url";
95
+ import { tmpdir, homedir } from "os";
96
+ function statusPath(projectRoot) {
97
+ return join(projectRoot, ".engram", "component-status.json");
98
+ }
99
+ function readCachedStatus(projectRoot) {
100
+ const path = statusPath(projectRoot);
101
+ if (!existsSync(path)) return null;
102
+ try {
103
+ const raw = JSON.parse(readFileSync(path, "utf-8"));
104
+ if (Date.now() - raw.generatedAt > 3e4) return null;
105
+ return raw;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+ function checkHttp(projectRoot) {
111
+ return existsSync(join(projectRoot, ".engram", "http-server.pid"));
112
+ }
113
+ function checkLsp(projectRoot) {
114
+ if (existsSync(join(projectRoot, ".engram", "lsp-available"))) return true;
115
+ const tmp = tmpdir();
116
+ const candidates = [
117
+ join(tmp, "tsserver.sock"),
118
+ join(tmp, "typescript-language-server.sock")
119
+ ];
120
+ return candidates.some((c) => existsSync(c));
121
+ }
122
+ function checkAst(projectRoot) {
123
+ try {
124
+ const here = dirname(fileURLToPath(import.meta.url));
125
+ const candidates = [
126
+ join(here, "..", "grammars"),
127
+ // from dist/intercept/
128
+ join(here, "..", "..", "dist", "grammars")
129
+ // from src/intercept/ dev
130
+ ];
131
+ for (const dir of candidates) {
132
+ if (existsSync(dir)) return true;
133
+ }
134
+ } catch {
135
+ }
136
+ if (existsSync(join(projectRoot, "node_modules", "web-tree-sitter"))) return true;
137
+ return false;
138
+ }
139
+ function countIdeAdapters(projectRoot) {
140
+ let count = 0;
141
+ if (existsSync(join(projectRoot, ".cursor", "rules", "engram-context.mdc"))) {
142
+ count += 1;
143
+ }
144
+ const continueConfig = join(homedir(), ".continue", "config.json");
145
+ if (existsSync(continueConfig)) {
146
+ try {
147
+ const cfg = readFileSync(continueConfig, "utf-8");
148
+ if (cfg.includes("engram")) count += 1;
149
+ } catch {
150
+ }
151
+ }
152
+ const zedSettings = join(homedir(), ".config", "zed", "settings.json");
153
+ if (existsSync(zedSettings)) {
154
+ try {
155
+ const cfg = readFileSync(zedSettings, "utf-8");
156
+ if (cfg.includes("engram")) count += 1;
157
+ } catch {
158
+ }
159
+ }
160
+ for (const f of ["settings.local.json", "settings.json"]) {
161
+ const claudeSettings = join(projectRoot, ".claude", f);
162
+ if (existsSync(claudeSettings)) {
163
+ try {
164
+ const cfg = readFileSync(claudeSettings, "utf-8");
165
+ if (cfg.includes("engram")) {
166
+ count += 1;
167
+ break;
168
+ }
169
+ } catch {
170
+ }
171
+ }
172
+ }
173
+ if (existsSync(join(projectRoot, ".windsurfrules"))) count += 1;
174
+ if (existsSync(join(projectRoot, ".aider-context.md"))) count += 1;
175
+ if (existsSync(join(projectRoot, ".context", "index.md"))) count += 1;
176
+ return count;
177
+ }
178
+ function refreshComponentStatus(projectRoot) {
179
+ const now = Date.now();
180
+ const components = [
181
+ { name: "http", available: checkHttp(projectRoot), checkedAt: now },
182
+ { name: "lsp", available: checkLsp(projectRoot), checkedAt: now },
183
+ { name: "ast", available: checkAst(projectRoot), checkedAt: now }
184
+ ];
185
+ const ideCount = countIdeAdapters(projectRoot);
186
+ const report = {
187
+ components,
188
+ ideCount,
189
+ generatedAt: now
190
+ };
191
+ try {
192
+ writeFileSync(statusPath(projectRoot), JSON.stringify(report), "utf-8");
193
+ } catch {
194
+ }
195
+ return report;
196
+ }
197
+ function getComponentStatus(projectRoot) {
198
+ const cached = readCachedStatus(projectRoot);
199
+ if (cached) return cached;
200
+ return refreshComponentStatus(projectRoot);
201
+ }
202
+ function formatHudStatus(report) {
203
+ const parts = [];
204
+ for (const c of report.components) {
205
+ const icon = c.available ? "\u2713" : "\u2717";
206
+ parts.push(`${c.name.toUpperCase()} ${icon}`);
207
+ }
208
+ if (report.ideCount > 0) {
209
+ parts.push(`${report.ideCount} IDE${report.ideCount > 1 ? "s" : ""}`);
210
+ }
211
+ return parts.join(" | ");
212
+ }
213
+
214
+ export {
215
+ ESTIMATED_TOKENS_PER_READ_DENY,
216
+ summarizeHookLog,
217
+ formatStatsSummary,
218
+ getComponentStatus,
219
+ formatHudStatus
220
+ };
@@ -310,7 +310,7 @@ function writeToFile(filePath, summary) {
310
310
  writeFileSync2(filePath, newContent);
311
311
  }
312
312
  async function autogen(projectRoot, target, task) {
313
- const { getStore } = await import("./core-77MHT3QV.js");
313
+ const { getStore } = await import("./core-6IY5L6II.js");
314
314
  const store = await getStore(projectRoot);
315
315
  try {
316
316
  let view = VIEWS.general;