prism-mcp-server 7.4.0 → 7.6.0

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 CHANGED
@@ -8,6 +8,8 @@
8
8
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
9
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
10
10
 
11
+ ![Prism Mind Palace Dashboard](docs/mind-palace-dashboard.png)
12
+
11
13
  **Your AI agent forgets everything between sessions. Prism fixes that.**
12
14
 
13
15
  One command. Persistent memory. Local-first by default. Optional cloud power-ups.
@@ -21,20 +23,21 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
21
23
  ## 📖 Table of Contents
22
24
 
23
25
  - [Why Prism?](#why-prism)
24
- - [Quick Start](#-quick-start)
25
- - [The Magic Moment](#-the-magic-moment)
26
- - [Setup Guides](#-setup-guides)
27
- - [Universal Import](#-universal-import-bring-your-history)
28
- - [What Makes Prism Different](#-what-makes-prism-different)
29
- - [Use Cases](#-use-cases)
30
- - [What's New](#-whats-new)
31
- - [How Prism Compares](#-how-prism-compares)
32
- - [Tool Reference](#-tool-reference)
26
+ - [Quick Start](#quick-start)
27
+ - [The Magic Moment](#the-magic-moment)
28
+ - [Setup Guides](#setup-guides)
29
+ - [Universal Import: Bring Your History](#universal-import-bring-your-history)
30
+ - [What Makes Prism Different](#what-makes-prism-different)
31
+ - [Data Privacy & Egress](#data-privacy--egress)
32
+ - [Use Cases](#use-cases)
33
+ - [What's New](#whats-new)
34
+ - [How Prism Compares](#how-prism-compares)
35
+ - [Tool Reference](#tool-reference)
33
36
  - [Environment Variables](#environment-variables)
34
37
  - [Architecture](#architecture)
35
- - [Scientific Foundation](#-scientific-foundation)
36
- - [Product Roadmap](#-product-roadmap)
37
- - [Troubleshooting FAQ](#-troubleshooting-faq)
38
+ - [Scientific Foundation](#scientific-foundation)
39
+ - [Milestones & Roadmap](#milestones--roadmap)
40
+ - [Troubleshooting FAQ](#troubleshooting-faq)
38
41
 
39
42
  ---
40
43
 
@@ -46,12 +49,23 @@ Every time you start a new conversation with an AI coding assistant, it starts f
46
49
 
47
50
  > 📌 **Terminology:** Throughout this doc, **"Prism"** refers to the MCP server and storage engine. **"Mind Palace"** refers to the visual dashboard UI at `localhost:3000` — your window into the agent's brain. They work together; the dashboard is optional.
48
51
 
49
- **Starting in v7.0**, Prism doesn't just *store* memories — it **ranks them like a human brain.** The ACT-R activation model (from cognitive science) means memories that were accessed recently and frequently surface first, while stale context fades naturally. Combine that with candidate-scoped spreading activation and you get retrieval quality that no flat vector search can match.
52
+ Prism has two pillars:
53
+
54
+ 1. **🧠 Persistent Memory** — Memories are ranked like a human brain: recently and frequently accessed context surfaces first, while stale context fades naturally. The result is retrieval quality that no flat vector search can match. *(See [Scientific Foundation](#-scientific-foundation) for the ACT-R math.)*
55
+
56
+ 2. **🏭 Autonomous Execution (Dark Factory)** — When you're ready, Prism can run coding tasks end-to-end with a fail-closed pipeline where an adversarial evaluator catches bugs the generator missed — before you ever see the PR. *(See [Dark Factory](#-dark-factory--adversarial-autonomous-pipelines).)*
50
57
 
51
58
  ---
52
59
 
53
60
  ## 🚀 Quick Start
54
61
 
62
+ ### Prerequisites
63
+
64
+ - **Node.js v18+** (v20 LTS recommended; v23.x has [known `npx` quirk](#common-installation-pitfalls))
65
+ - Any MCP-compatible client (Claude Desktop, Cursor, Windsurf, Cline, etc.)
66
+ - No API keys required for core features (see [Capability Matrix](#capability-matrix))
67
+
68
+ ### Install
55
69
 
56
70
  Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`, etc.):
57
71
 
@@ -70,6 +84,10 @@ Add to your MCP client config (`claude_desktop_config.json`, `.cursor/mcp.json`,
70
84
 
71
85
  **That's it.** Restart your client. All tools are available. The **Mind Palace Dashboard** (the visual UI for your agent's brain) starts automatically at `http://localhost:3000`. You don't need to keep a tab open — the dashboard runs in the background and the MCP tools work with or without it.
72
86
 
87
+ > 🔮 **Pro Tip:** Once installed, open **`http://localhost:3000`** in your browser to view the Mind Palace Dashboard — a beautiful, real-time UI of your agent's brain. Explore the Knowledge Graph, Intent Health gauges, and Session Ledger.
88
+
89
+ > 🔄 **Updating Prism:** `npx -y` caches the package locally. To force an update to the latest version, restart your MCP client — `npx -y` will fetch the newest release automatically. If you're stuck on a stale version, run `npx clear-npx-cache` (or `npm cache clean --force`) before restarting.
90
+
73
91
  <details>
74
92
  <summary>Port 3000 already in use? (Next.js / Vite / etc.)</summary>
75
93
 
@@ -109,6 +127,8 @@ Then open `http://localhost:3001` instead.
109
127
 
110
128
  > 🔑 The core Mind Palace works **100% offline** with zero API keys. Cloud keys unlock intelligence features. See [Environment Variables](#environment-variables).
111
129
 
130
+ > 💰 **API Cost Note:** `GOOGLE_API_KEY` (Gemini) has a generous free tier that covers most individual use. `BRAVE_API_KEY` offers 2,000 free searches/month. `FIRECRAWL_API_KEY` has a free plan with 500 credits. For typical solo development, expect **$0/month** on the free tiers. Only high-volume teams or heavy autonomous pipeline usage will incur meaningful costs.
131
+
112
132
  ---
113
133
 
114
134
  ## ✨ The Magic Moment
@@ -300,6 +320,32 @@ Then add to your MCP config:
300
320
 
301
321
  </details>
302
322
 
323
+ <details>
324
+ <summary><strong>Cloud Deployment (Render)</strong></summary>
325
+
326
+ Prism can be deployed natively to cloud platforms like [Render](https://render.com) so your agent's memory is always online and accessible across different machines or teams.
327
+
328
+ 1. Fork this repository.
329
+ 2. In the Render Dashboard, create a new **Web Service** pointing to your repository.
330
+ 3. In the setup wizard, select **Docker** as the Runtime.
331
+ 4. Set the Dockerfile path to `Dockerfile.smithery`.
332
+ 5. Connect your local MCP client to your new cloud endpoint using the `sse` transport:
333
+
334
+ ```json
335
+ {
336
+ "mcpServers": {
337
+ "prism-mcp-cloud": {
338
+ "command": "npx",
339
+ "args": ["-y", "supergateway", "--url", "https://your-prism-app.onrender.com/sse"]
340
+ }
341
+ }
342
+ }
343
+ ```
344
+
345
+ > **Note:** The `Dockerfile.smithery` uses an optimized multi-stage build that compiles Typescript safely in a development environment before booting the server in a stripped-down production image. No NPM publishing required!
346
+
347
+ </details>
348
+
303
349
  ### Common Installation Pitfalls
304
350
 
305
351
  > **❌ Don't use `npm install -g`:**
@@ -321,9 +367,15 @@ Then add to your MCP config:
321
367
  > **❓ Seeing warnings about missing API keys on startup?**
322
368
  > That's expected and not an error. `BRAVE_API_KEY` / `GOOGLE_API_KEY` warnings are informational only — core session memory works with zero keys. See [Environment Variables](#environment-variables) for what each key unlocks.
323
369
 
370
+ > 💡 **Do agents auto-load Prism?** Agents using Cursor, Windsurf, or other MCP clients will see the `session_load_context` tool automatically, but may not call it unprompted. Add this to your project's `.cursorrules` (or equivalent system prompt) to guarantee auto-load:
371
+ > ```
372
+ > At the start of every conversation, call session_load_context with project "my-project" before doing any work.
373
+ > ```
374
+ > Claude Code users can use the `.clauderules` auto-load hook shown in the [Setup Guides](#setup-guides). Prism also has a **server-side fallback** (v5.2.1+) that auto-pushes context after 10 seconds if no load is detected.
375
+
324
376
  ---
325
377
 
326
- ## 📥 Universal Import Bring Your History
378
+ ## 📥 Universal Import: Bring Your History
327
379
 
328
380
  Switching to Prism? Don't leave months of AI session history behind. Prism can **ingest historical sessions from Claude Code, Gemini, and OpenAI** and give your Mind Palace an instant head start — no manual re-entry required.
329
381
 
@@ -349,7 +401,7 @@ npx -y prism-mcp-server universal-import --format gemini --path ./gemini_history
349
401
  **Option 2 — Dashboard:** Open `localhost:3000`, navigate to the **Import** tab, select the format and file, and click Import. Supports dry-run preview.
350
402
 
351
403
  ### Why It's Safe to Re-Run
352
- * **OOM-Safe Streaming:** Processes massive log files line-by-line using `stream-json`.
404
+ * **Memory-Safe Streaming:** Processes massive log files line-by-line using `stream-json` to prevent Out-of-Memory (OOM) crashes.
353
405
  * **Idempotent Dedup:** Content-hash prevents duplicate imports on re-run (`skipCount` reported).
354
406
  * **Chronological Integrity:** Uses timestamp fallbacks and `requestId` sorting to preserve your memory timeline.
355
407
  * **Smart Context Mapping:** Extracts `cwd`, `gitBranch`, and tool usage patterns into searchable metadata.
@@ -369,6 +421,7 @@ Every save creates a versioned snapshot. Made a mistake? `memory_checkout` rever
369
421
  A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what your agent is thinking:
370
422
 
371
423
  - **Current State & TODOs** — the exact context injected into the LLM's prompt
424
+ - **Intent Health Gauges** — per-project 0–100 health score with staleness decay, TODO load, and decision signals
372
425
  - **Interactive Knowledge Graph** — force-directed neural graph with click-to-filter, node renaming, and surgical keyword deletion
373
426
  - **Deep Storage Manager** — preview and execute vector purge operations with dry-run safety
374
427
  - **Session Ledger** — full audit trail of every decision your agent has made
@@ -378,7 +431,7 @@ A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what y
378
431
  - **Morning Briefing** — AI-synthesized action plan after 4+ hours away
379
432
  - **Brain Health** — memory integrity scan with one-click auto-repair
380
433
 
381
- ![Mind Palace Dashboard](docs/mind-palace-dashboard.png)
434
+
382
435
 
383
436
  ### 🧬 10× Memory Compression
384
437
  Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's ICLR research), Prism compresses 768-dim embeddings from **3,072 bytes → ~400 bytes** — enabling decades of session history on a standard laptop. No native modules. No vector database required.
@@ -387,7 +440,7 @@ Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's I
387
440
  Multiple agents (dev, QA, PM) can work on the same project with **role-isolated memory**. Agents discover each other automatically, share context in real-time via Telepathy sync, and see a team roster during context loading. → [Multi-agent setup example](examples/multi-agent-hivemind/)
388
441
 
389
442
  ### 🚦 Task Router
390
- Prism can score coding tasks and recommend whether to keep execution on the host model or delegate to local Claw. This enables faster handling of small, local-safe edits while preserving host execution for non-delegable or higher-complexity work. In client startup/skill flows, use defensive delegation: route only coding tasks, call `session_task_route` only when available, delegate to `claw` only when executor tooling exists and task is non-destructive, and fallback to host when router/executor is unavailable. → [Task router real-life example](examples/router_real_life_test.ts)
443
+ Prism can score coding tasks and recommend whether to keep execution on the host model or delegate to a **local Claw agent** (a lightweight sub-agent powered by Ollama/vLLM for fast, local-safe edits). This enables faster handling of small edits while preserving host execution for complex work. In client startup/skill flows, use defensive delegation: route only coding tasks, call `session_task_route` only when available, delegate to `claw` only when executor tooling exists and task is non-destructive, and fallback to host when router/executor is unavailable. → [Task router real-life example](examples/router_real_life_test.ts)
391
444
 
392
445
  ### 🖼️ Visual Memory
393
446
  Save UI screenshots, architecture diagrams, and bug states to a searchable vault. Images are auto-captioned by a VLM (Claude Vision / GPT-4V / Gemini) and become semantically searchable across sessions.
@@ -398,11 +451,39 @@ OpenTelemetry spans for every MCP tool call, LLM hop, and background worker. Rou
398
451
  ### 🌐 Autonomous Web Scholar
399
452
  Prism researches while you sleep. A background pipeline searches the web, scrapes articles, synthesizes findings via LLM, and injects results directly into your semantic memory — fully searchable on your next session. Brave Search → Firecrawl scrape → LLM synthesis → Prism ledger. Task-aware, Hivemind-integrated, and zero-config when API keys are missing (falls back to Yahoo + Readability).
400
453
 
401
- ### 🔒 GDPR Compliant
402
- Soft/hard delete (Art. 17), full export in JSON, Markdown, or Obsidian vault `.zip` (Art. 20), API key redaction, per-project TTL retention, and audit trail. Enterprise-ready out of the box.
403
-
404
454
  ### 🏭 Dark Factory — Adversarial Autonomous Pipelines
405
- When you trigger a Dark Factory pipeline, Prism doesn't just run your task — it fights itself to produce high-quality output. A `PLAN_CONTRACT` step locks a machine-parseable rubric before any code is written. After execution, an **Adversarial Evaluator** (in a fully isolated context) scores the output against the rubric. It cannot pass the Generator without providing exact file and line evidence for every failing criterion. Failed evaluations inject the critique directly into the Generator's retry prompt so it's never flying blind. The result: security issues, regressions, and lazy debug logs caught autonomously — before you ever see the PR.
455
+ When you trigger a Dark Factory pipeline, Prism doesn't just run your task — it fights itself to produce high-quality output. A `PLAN_CONTRACT` step locks a machine-parseable rubric before any code is written. After execution, an **Adversarial Evaluator** (in a fully isolated context) scores the output against the rubric. It cannot pass the Generator without providing exact file and line evidence for every failing criterion. Failed evaluations inject the critique directly into the Generator's retry prompt so it's never flying blind. The result: security issues, regressions, and lazy debug logs caught autonomously — before you ever see the PR. → [See it in action](examples/adversarial-eval-demo/README.md)
456
+
457
+ ---
458
+
459
+ ## 🔒 Data Privacy & Egress
460
+
461
+ **Where is my data stored?**
462
+
463
+ All data lives under `~/.prism-mcp/` on your machine:
464
+
465
+ | File | Contents |
466
+ |------|----------|
467
+ | `~/.prism-mcp/data.db` | All sessions, handoffs, embeddings, knowledge graph (SQLite + WAL) |
468
+ | `~/.prism-mcp/prism-config.db` | Dashboard settings, system config, API keys |
469
+ | `~/.prism-mcp/media/<project>/` | Visual memory vault (screenshots, HTML captures) |
470
+ | `~/.prism-mcp/dashboard.port` | Ephemeral port lock file |
471
+ | `~/.prism-mcp/sync.lock` | Sync coordination lock |
472
+
473
+ **Hard reset:** To completely erase your agent's brain, stop Prism and delete the directory:
474
+ ```bash
475
+ rm -rf ~/.prism-mcp
476
+ ```
477
+ Prism will recreate the directory with empty databases on next startup.
478
+
479
+ **What leaves your machine?**
480
+ - **Local mode (default):** Nothing. Zero network calls. All data is on-disk SQLite.
481
+ - **With `GOOGLE_API_KEY`:** Text snippets are sent to Gemini for embedding generation, summaries, and Morning Briefings. No session data is stored on Google's servers beyond the API call.
482
+ - **With `VOYAGE_API_KEY` / `OPENAI_API_KEY`:** Text snippets are sent to providers if selected as your embedding endpoints.
483
+ - **With `BRAVE_API_KEY` / `FIRECRAWL_API_KEY`:** Web Scholar queries are sent to Brave/Firecrawl for search and scraping.
484
+ - **With Supabase:** Session data syncs to your own Supabase instance (you control the Postgres database).
485
+
486
+ **GDPR compliance:** Soft/hard delete (Art. 17), full export in JSON, Markdown, or Obsidian vault `.zip` (Art. 20), API key redaction in exports, per-project TTL retention policies, and immutable audit trail. Enterprise-ready out of the box.
406
487
 
407
488
  ---
408
489
 
@@ -411,7 +492,8 @@ When you trigger a Dark Factory pipeline, Prism doesn't just run your task — i
411
492
  - **Long-running feature work** — Save state at end of day, restore full context next morning. No re-explaining.
412
493
  - **Multi-agent collaboration** — Dev, QA, and PM agents share real-time context without stepping on each other's memory.
413
494
  - **Consulting / multi-project** — Switch between client projects with progressive loading: `quick` (~50 tokens), `standard` (~200), or `deep` (~1000+).
414
- - **Autonomous execution (v7.4)** — Dark Factory pipeline: `plan → plan_contract → execute → evaluate → verify → finalize`. Generator and evaluator run in isolated roles — the evaluator cannot approve without evidence-bound findings scored against a pre-committed rubric.
495
+ - **Autonomous execution (v7.4)** — Dark Factory pipeline: `plan → plan_contract → execute → evaluate → verify → finalize`. Generator and evaluator run in isolated roles — the evaluator cannot approve without evidence-bound findings scored against a pre-committed rubric.
496
+ - **Project health monitoring (v7.5)** — Intent Health Dashboard scores each project 0–100 based on staleness, TODO load, and decision quality — turning silent drift into an actionable signal.
415
497
  - **Team onboarding** — New team member's agent loads the full project history instantly.
416
498
  - **Behavior enforcement** — Agent corrections auto-graduate into permanent `.cursorrules` / `.clauderules` rules.
417
499
  - **Offline / air-gapped** — Full SQLite local mode + Ollama LLM adapter. Zero internet dependency.
@@ -434,8 +516,6 @@ Then continue a specific thread with a follow-up message to the selected agent,
434
516
 
435
517
  ---
436
518
 
437
- ---
438
-
439
519
  ## ⚔️ Adversarial Evaluation in Action
440
520
 
441
521
  > **Split-Brain Anti-Sycophancy** — the signature feature of v7.4.0.
@@ -543,18 +623,13 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
543
623
 
544
624
  ## 🆕 What's New
545
625
 
626
+ > **Current release: v7.5.0**
546
627
 
547
- > **Current release: v7.4.0**
548
-
549
- - ⚔️ **v7.4.0Adversarial Evaluation (Anti-Sycophancy):** The Dark Factory pipeline now separates generator and evaluator into isolated roles. `PLAN_CONTRACT` locks a machine-parseable rubric before any code runs. `EVALUATE` scores the output with evidence-bound findings (`file`, `line`, `description`). Failed evaluations retry with `plan_viable` routing — conservatively escalating to full PLAN re-planning on parse failures instead of burning revision budget.
550
- - 🔧 **v7.3.3 — Dashboard Stability Hotfix:** Fixed a multi-layer quote-escaping trap in the `abortPipeline` onclick handler that silently killed the dashboard IIFE and froze the project selector at "Loading projects..." forever. Fixed via `data-id` attribute pattern + ES5 lint guard (`npm run lint:dashboard`).
551
- - 🏭 **v7.3.1 — Dark Factory (Fail-Closed Execution):** The LLM can no longer touch the filesystem directly. Every autonomous `EXECUTE` step passes 3 gates — Parse → Type → Scope — before any side effect occurs. Scope violations terminate the entire pipeline.
552
- - 📊 **v7.3.2 — Verification Diagnostics v2:** `verify status --json` now emits per-layer `diff_counts` + `changed_keys`. JSON schema is contract-enforced in CI (`schema_version: 1`).
553
- - 🔭 **v7.2.0 — Verification Harness:** Spec-frozen contracts (`verification_harness.json` hash-locked before execution), multi-layer assertions across Data / Agent / Pipeline, and finalization gate policies (`warn` / `gate` / `abort`).
554
- - 🚦 **v7.1.0 — Task Router:** Heuristic + ML-experience routing delegates cloud vs. local model in under 2ms, cold-start safe, per-project experience-corrected.
555
- - 🧠 **v7.0.0 — ACT-R Activation Memory:** `B_i = ln(Σ t_j^{-d})` recency × frequency re-ranking. Stale memories fade naturally. Active context surfaces automatically.
628
+ - 🩺 **v7.5.0 Intent Health Dashboard + Security Hardening:** Real-time 0–100 project health scoring (staleness × TODO load × decisions). 10 XSS injection vectors patched. Algorithm hardened with NaN guards and score ceiling.
629
+ - ⚔️ **v7.4.0 — Adversarial Evaluation:** Split-brain anti-sycophancy pipeline. Generator and evaluator in isolated roles with evidence-bound findings.
630
+ - 🏭 **v7.3.x — Dark Factory + Stability:** Fail-closed 3-gate execution pipeline. Dashboard stability and verification diagnostics.
556
631
 
557
- 👉 **[Full release history → CHANGELOG.md](CHANGELOG.md)** · [ROADMAP →](ROADMAP.md)
632
+ 👉 **[Full release history → CHANGELOG.md](CHANGELOG.md)** · **[ROADMAP →](ROADMAP.md)**
558
633
 
559
634
  ---
560
635
 
@@ -765,6 +840,8 @@ Requires `PRISM_DARK_FACTORY_ENABLED=true`.
765
840
  | `PRISM_ENABLE_HIVEMIND` | No | `"true"` to enable multi-agent tools — restart required |
766
841
  | `PRISM_INSTANCE` | No | Instance name for multi-server PID isolation |
767
842
  | `GOOGLE_API_KEY` | No | Gemini — enables semantic search, Briefings, compaction |
843
+ | `VOYAGE_API_KEY` | No | Voyage AI — optional premium embedding provider |
844
+ | `OPENAI_API_KEY` | No | OpenAI — optional proxy model and embedding provider |
768
845
  | `BRAVE_ANSWERS_API_KEY` | No | Separate Brave Answers key |
769
846
  | `SUPABASE_URL` | If cloud | Supabase project URL |
770
847
  | `SUPABASE_KEY` | If cloud | Supabase anon/service key |
@@ -793,6 +870,10 @@ Requires `PRISM_DARK_FACTORY_ENABLED=true`.
793
870
 
794
871
  </details>
795
872
 
873
+ ### System Settings (Dashboard)
874
+ Some configurations are stored dynamically in SQLite (`system_settings` table) and can be edited through the Dashboard UI at `http://localhost:3000`:
875
+ - **`intent_health_stale_threshold_days`** (default: `30`): Number of days before a project is considered fully stale for Intent Health scoring.
876
+
796
877
  ---
797
878
 
798
879
  ## Architecture
@@ -888,6 +969,7 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
888
969
  | **v7.3** | Dark Factory — 3-gate fail-closed EXECUTE pipeline (parse → type → scope) with structured JSON action contract | Industrial safety systems (defense-in-depth, fail-closed valves) | ✅ Shipped |
889
970
  | **v7.2** | Verification-first harness — spec-freeze contract, rubric hash lock, multi-layer assertions, CLI `verify` commands | Programmatic verification systems + adversarial validation loops | ✅ Shipped |
890
971
  | **v7.4** | Adversarial Evaluation — PLAN_CONTRACT + EVALUATE with isolated generator/evaluator roles, pre-committed rubrics, and evidence-bound findings | Anti-sycophancy research, adversarial ML evaluation frameworks | ✅ Shipped |
972
+ | **v7.5** | Intent Health Dashboard — 3-signal scoring algorithm (staleness, TODO load, decisions), comprehensive XSS hardening (10 vectors), NaN/`Infinity` guards | Proactive monitoring, defense-in-depth security | ✅ Shipped |
891
973
  | **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
892
974
  | **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
893
975
 
@@ -895,34 +977,26 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
895
977
 
896
978
  ---
897
979
 
898
- ## 📦 Recent Milestones & Roadmap
899
-
900
- > **[Full ROADMAP.md →](ROADMAP.md)**
901
-
902
- ### v6.2: The "Synthesize & Prune" Phase ✅
903
- Shipped in v6.2.0. Edge synthesis, graph pruning with SLO observability, temporal decay heatmaps, active recall prompt generation, and full dashboard metrics integration.
980
+ ## 📦 Milestones & Roadmap
904
981
 
905
- ### v6.5: Cognitive Architecture
906
- Shipped. Full Superposed Memory (SDM) + Hyperdimensional Computing (HDC/VSA) cognitive routing pipeline. Compositional memory states via XOR binding, Hamming resolution, and policy-gated routing (direct / clarify / fallback). 705 tests passing.
982
+ > **Current: v7.5.0** Intent Health Dashboard + XSS Hardening ([CHANGELOG](CHANGELOG.md))
907
983
 
908
- ### v7.4: Adversarial Evaluation
909
- Shipped. `PLAN_CONTRACT` + `EVALUATE` steps added to the Dark Factory pipeline. Generator and evaluator operate in isolated roles with pre-committed rubrics. Evidence-bound findings with `criterion_id`, `severity`, `file`, and `line` (number). Conservative `plan_viable=false` default on parse failure escalates to full PLAN re-plan. 78 new tests, 978 total.
910
-
911
- ### v7.3: Dark Factory Fail-Closed Execution ✅
912
- Shipped. Structured JSON action contract for autonomous `EXECUTE` steps. 3-gate validation pipeline (parse → type → scope) terminates pipelines on any violation before filesystem side effects. 67 edge-case tests covering adversarial LLM output, path traversal, and type coercion.
913
-
914
- ### v7.1: Prism Task Router
915
- Shipped. Deterministic task routing (`session_task_route`) with optional experience-based confidence adjustment for host vs. local Claw delegation.
916
-
917
- ### v7.0: ACT-R Activation Memory
918
- Shipped. Scientifically-grounded retrieval re-ranking via ACT-R base-level activation (`B_i = ln(Σ t_j^(-d))`), candidate-scoped spreading activation, parameterized sigmoid normalization, composite scoring, and zero-cold-start access log infrastructure. 49 dedicated unit tests, 705 total passing.
919
-
920
- ### v7.2: Verification Harness ✅
921
- Shipped. Spec-frozen verification contract (`implementation_plan.md` + `verification_harness.json` + immutable `validation_result`), multi-layer machine checks (`data`, `agent`, `pipeline`), finalization gate policies (`warn` / `gate` / `abort`), and CLI `verify generate` / `verify status --json` with schema-versioned output.
984
+ | Release | Headline |
985
+ |---------|----------|
986
+ | **v7.5** | Intent Health scoring + 10 XSS patches |
987
+ | **v7.4** | Adversarial Evaluation (anti-sycophancy) |
988
+ | **v7.3** | Dark Factory fail-closed execution |
989
+ | **v7.2** | Verification Harness |
990
+ | **v7.1** | Task Router |
991
+ | **v7.0** | ACT-R Activation Memory |
992
+ | **v6.5** | HDC Cognitive Routing |
993
+ | **v6.2** | Synthesize & Prune |
922
994
 
923
995
  ### Future Tracks
924
- - **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence, making surfaced context more behaviorally useful.
925
- - **v8+: Zero-Search Retrieval** — Direct vector-addressed recall (“just ask the vector”) reduces retrieval indirection and moves Prism toward truly native associative memory.
996
+ - **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence.
997
+ - **v8+: Zero-Search Retrieval** — Direct vector-addressed recall reduces retrieval indirection.
998
+
999
+ 👉 **[Full ROADMAP.md →](ROADMAP.md)**
926
1000
 
927
1001
 
928
1002
  ## ❓ Troubleshooting FAQ
@@ -939,18 +1013,19 @@ A: Use `session_forget_memory` for targeted soft/hard deletion. For manual clean
939
1013
  **Q: How do I verify the install quickly?**
940
1014
  A: Run `npm run build && npm test`, then open the Mind Palace dashboard (`localhost:3000`) and confirm projects load plus Graph Health renders.
941
1015
 
1016
+
942
1017
  ---
943
1018
 
944
1019
  ### 💡 Known Limitations & Quirks
945
1020
 
946
1021
  - **LLM-dependent features require an API key.** Semantic search, Morning Briefings, auto-compaction, and VLM captioning need a `GOOGLE_API_KEY` (your Gemini API key) or equivalent provider key. Without one, Prism falls back to keyword-only search (FTS5).
947
- - **Auto-load is model- and client-dependent.** Session auto-loading relies on both the LLM following system prompt instructions *and* the MCP client completing tool registration before the model's first turn. Prism provides platform-specific [Setup Guides](#-setup-guides) and a server-side fallback (v5.2.1) that auto-pushes context after 10 seconds.
1022
+ - **Auto-load is model- and client-dependent.** Session auto-loading relies on both the LLM following system prompt instructions *and* the MCP client completing tool registration before the model's first turn. Prism provides platform-specific [Setup Guides](#setup-guides) and a server-side fallback (v5.2.1) that auto-pushes context after 10 seconds.
948
1023
  - **MCP client race conditions.** Some MCP clients may not finish tool enumeration before the model generates its first response, causing transient `unknown_tool` errors. This is a client-side timing issue — Prism's server completes the MCP handshake in ~60ms. Workaround: the server-side auto-push fallback and the startup skill's retry logic.
949
1024
  - **No real-time sync without Supabase.** Local SQLite mode is single-machine only. Multi-device or team sync requires a Supabase backend.
950
1025
  - **Embedding quality varies by provider.** Gemini `text-embedding-004` and OpenAI `text-embedding-3-small` produce high-quality 768-dim vectors. Prism passes `dimensions: 768` via the Matryoshka API for OpenAI models (native output is 1536-dim; this truncation is lossless and outperforms ada-002 at full 1536 dims). Ollama embeddings (e.g., `nomic-embed-text`) are usable but may reduce retrieval accuracy.
951
1026
  - **Dashboard is HTTP-only.** The Mind Palace dashboard at `localhost:3000` does not support HTTPS. For remote access, use a reverse proxy (nginx/Caddy) or SSH tunnel. Basic auth is available via `PRISM_DASHBOARD_USER` / `PRISM_DASHBOARD_PASS`.
952
1027
  - **Long-lived clients can accumulate zombie processes.** MCP clients that run for extended periods (e.g., Claude CLI) may leave orphaned Prism server processes. The lifecycle manager detects true orphans (PPID=1) but allows coexistence for active parent processes. Use `PRISM_INSTANCE` to isolate instances across clients.
953
- - **Migration is one-way.** Universal History Migration imports sessions *into* Prism but does not export back to Claude/Gemini/OpenAI formats. Use `session_export_memory` for portable JSON/Markdown export, or the `vault` format for Obsidian/Logseq-compatible `.zip` archives.
1028
+ - **Migration is one-way.** Universal Import ingests sessions *into* Prism but does not export back to Claude/Gemini/OpenAI formats. Use `session_export_memory` for portable JSON/Markdown export, or the `vault` format for Obsidian/Logseq-compatible `.zip` archives.
954
1029
  - **Export ceiling at 10,000 ledger entries.** The `session_export_memory` tool and the dashboard export button cap vault/JSON exports at 10,000 entries per project as an OOM guard. Projects exceeding this limit should use per-project exports and time-based filtering to stay within the ceiling. This limit does not affect search or context loading.
955
1030
  - **No Windows CI testing.** Prism is developed and tested on macOS/Linux. It should work on Windows via Node.js, but edge cases (file paths, PID locks) may surface.
956
1031
 
package/dist/cli.js CHANGED
File without changes
package/dist/config.js CHANGED
@@ -15,6 +15,9 @@ import { fileURLToPath } from "node:url";
15
15
  * SUPABASE_KEY — (optional) Your Supabase anon/service key. Enables session memory tools.
16
16
  * PRISM_USER_ID — (optional) Unique tenant ID for multi-user Supabase instances.
17
17
  * Defaults to "default". Set per-user in Claude Desktop config.
18
+ * VOYAGE_API_KEY — (optional) API key for Voyage AI embeddings. Enables embedding_provider=voyage.
19
+ * Voyage AI is the embedding provider recommended by Anthropic for use with
20
+ * Claude. Get a free key at https://dash.voyageai.com.
18
21
  *
19
22
  * If a required key is missing, the process exits immediately.
20
23
  * If an optional key is missing, a warning is logged but the server continues
@@ -60,6 +63,14 @@ export const BRAVE_ANSWERS_API_KEY = process.env.BRAVE_ANSWERS_API_KEY;
60
63
  if (!BRAVE_ANSWERS_API_KEY) {
61
64
  console.error("Warning: BRAVE_ANSWERS_API_KEY environment variable is missing. Brave Answers tool will be unavailable.");
62
65
  }
66
+ // ─── Optional: Voyage AI API Key ──────────────────────────────
67
+ // Used when embedding_provider = "voyage" in the dashboard.
68
+ // Voyage AI is the embedding provider recommended by Anthropic for use
69
+ // alongside Claude. voyage-3 supports 768-dim output via MRL truncation,
70
+ // matching Prism's storage schema for zero-migration drop-in replacement.
71
+ // Without this, VoyageAdapter construction will throw at server start if
72
+ // embedding_provider=voyage is selected.
73
+ export const VOYAGE_API_KEY = process.env.VOYAGE_API_KEY;
63
74
  // ─── v2.0: Storage Backend Selection ─────────────────────────
64
75
  // REVIEWER NOTE: Step 1 of v2.0 introduces a storage abstraction.
65
76
  // Currently only "supabase" is implemented. "local" (SQLite) is
@@ -0,0 +1,62 @@
1
+ export function computeIntentHealth(ctx, staleThresholdDays = 30, nowMs = Date.now()) {
2
+ if (!Number.isFinite(staleThresholdDays) || staleThresholdDays <= 0)
3
+ staleThresholdDays = 30; // Guard against NaN / zero / negative
4
+ // 2. Compute staleness (Days since last session)
5
+ let stalenessDays = 0;
6
+ if (ctx.recent_sessions && ctx.recent_sessions.length > 0) {
7
+ const lastTimestamp = ctx.recent_sessions[0].created_at ? new Date(ctx.recent_sessions[0].created_at).getTime() : NaN;
8
+ stalenessDays = isNaN(lastTimestamp) ? 0 : Math.max(0, (nowMs - lastTimestamp) / (1000 * 60 * 60 * 24));
9
+ }
10
+ // 3. Count TODOs and Decisions
11
+ const todoCount = ctx.pending_todo?.length || 0;
12
+ const hasDecisions = ctx.recent_sessions?.some((s) => Array.isArray(s.decisions) && s.decisions.length > 0) ?? false;
13
+ // 4. Execute Intent Debt Scoring Algorithm
14
+ // Staleness (50 points): Linear decay until threshold
15
+ const stalenessScore = Math.max(0, 50 - (stalenessDays / staleThresholdDays) * 50);
16
+ // Open TODOs (30 points)
17
+ let todoScore = 30;
18
+ if (todoCount >= 10)
19
+ todoScore = 0;
20
+ else if (todoCount >= 7)
21
+ todoScore = 5;
22
+ else if (todoCount >= 4)
23
+ todoScore = 15;
24
+ else if (todoCount >= 1)
25
+ todoScore = 25;
26
+ // Decisions (20 points): 70% of max if missing
27
+ const decisionScore = hasDecisions ? 20 : 14;
28
+ const totalScore = Math.min(100, Math.round(stalenessScore + todoScore + decisionScore));
29
+ // 5. Generate Actionable Signals
30
+ const signals = [];
31
+ if (stalenessDays > staleThresholdDays) {
32
+ signals.push({ type: "staleness", message: `Stale: Last updated ${Math.round(stalenessDays)} days ago`, severity: "critical" });
33
+ }
34
+ else if (stalenessDays > staleThresholdDays / 2) {
35
+ signals.push({ type: "staleness", message: `Aging: Last updated ${Math.round(stalenessDays)} days ago`, severity: "warn" });
36
+ }
37
+ else {
38
+ signals.push({ type: "staleness", message: `Fresh: Last updated ${Math.round(stalenessDays)} days ago`, severity: "ok" });
39
+ }
40
+ if (todoCount >= 10) {
41
+ signals.push({ type: "todos", message: `${todoCount} TODOs pending (Overwhelming)`, severity: "critical" });
42
+ }
43
+ else if (todoCount >= 4) {
44
+ signals.push({ type: "todos", message: `${todoCount} TODOs pending`, severity: "warn" });
45
+ }
46
+ else {
47
+ signals.push({ type: "todos", message: `${todoCount} TODOs pending`, severity: "ok" });
48
+ }
49
+ if (hasDecisions) {
50
+ signals.push({ type: "decisions", message: "Active decisions captured", severity: "ok" });
51
+ }
52
+ else {
53
+ signals.push({ type: "decisions", message: "No active decisions documented", severity: "warn" });
54
+ }
55
+ return {
56
+ score: totalScore,
57
+ staleness_days: Math.round(stalenessDays),
58
+ open_todo_count: todoCount,
59
+ has_active_decisions: hasDecisions,
60
+ signals: signals
61
+ };
62
+ }
@@ -23,7 +23,8 @@ import * as fs from "fs";
23
23
  import { getStorage } from "../storage/index.js";
24
24
  import { PRISM_USER_ID, SERVER_CONFIG } from "../config.js";
25
25
  import { renderDashboardHTML } from "./ui.js";
26
- import { getAllSettings, setSetting, getSetting } from "../storage/configStorage.js";
26
+ import { computeIntentHealth } from "./intentHealth.js";
27
+ import { getAllSettings, setSetting, getSetting, getSettingSync } from "../storage/configStorage.js";
27
28
  import { compactLedgerHandler } from "../tools/compactionHandler.js";
28
29
  import { getLLMProvider } from "../utils/llm/factory.js";
29
30
  import { buildVaultDirectory } from "../utils/vaultExporter.js";
@@ -1095,6 +1096,39 @@ self.addEventListener('message', (e) => {
1095
1096
  });
1096
1097
  return res.end(Buffer.from(pngBase64, "base64"));
1097
1098
  }
1099
+ // ─── API: Intent Health (Feature A) ───
1100
+ // GET /api/intent-health?project=xxx
1101
+ if (url.pathname === "/api/intent-health" && req.method === "GET") {
1102
+ const project = url.searchParams.get("project");
1103
+ if (!project) {
1104
+ res.writeHead(400, { "Content-Type": "application/json" });
1105
+ return res.end(JSON.stringify({ error: "project is required" }));
1106
+ }
1107
+ try {
1108
+ const storage = await getStorageSafe();
1109
+ if (!storage) {
1110
+ res.writeHead(503, { "Content-Type": "application/json" });
1111
+ return res.end(JSON.stringify({ error: "Storage initializing..." }));
1112
+ }
1113
+ // Load 'standard' context to get active_decisions and recent_sessions
1114
+ // Note: API MUST use "standard" level (deep skips recent_sessions, which breaks staleness).
1115
+ const ctx = await storage.loadContext(project, "standard", PRISM_USER_ID);
1116
+ if (!ctx) {
1117
+ res.writeHead(404, { "Content-Type": "application/json" });
1118
+ return res.end(JSON.stringify({ error: "Project not found" }));
1119
+ }
1120
+ // 1. Fetch configurable threshold (default 30 days, avoiding 7-day panic)
1121
+ const staleThresholdDays = parseInt(getSettingSync("intent_health_stale_threshold_days", "30"), 10) || 30;
1122
+ const healthResult = computeIntentHealth(ctx, staleThresholdDays);
1123
+ res.writeHead(200, { "Content-Type": "application/json" });
1124
+ return res.end(JSON.stringify(healthResult));
1125
+ }
1126
+ catch (err) {
1127
+ console.error("[intent-health] Error computing intent health:", err);
1128
+ res.writeHead(500, { "Content-Type": "application/json" });
1129
+ return res.end(JSON.stringify({ error: "Failed to compute intent health" }));
1130
+ }
1131
+ }
1098
1132
  // ─── 404 ───
1099
1133
  res.writeHead(404, { "Content-Type": "text/plain" });
1100
1134
  res.end("Not found");
@@ -625,6 +625,15 @@ export function renderDashboardHTML(version) {
625
625
  <ul class="todo-list" id="todos"></ul>
626
626
  </div>
627
627
 
628
+ <!-- Intent Health (Feature A) -->
629
+ <div class="card" id="intentHealthCard" style="display: none;">
630
+ <div class="card-title" style="display: flex; justify-content: space-between; align-items: center;">
631
+ <div><span class="dot" style="background:var(--accent-purple)"></span> Intent Health</div>
632
+ <span style="font-size: 11px; color: var(--text-muted); cursor: help; border-bottom: 1px dotted var(--text-muted); letter-spacing: normal; text-transform: none;" title="Intent debt limits how AI agents can evolve the system (Storey / Fowler)">What is this?</span>
633
+ </div>
634
+ <div id="intentHealthCardContent"></div>
635
+ </div>
636
+
628
637
  <!-- Git Metadata -->
629
638
  <div class="card">
630
639
  <div class="card-title"><span class="dot" style="background:var(--accent-green)"></span> Git Metadata</div>
@@ -1465,15 +1474,15 @@ function loadPipelines() {
1465
1474
  html += '<span style="font-weight:600;color:var(--text-primary)">' + emoji + ' ' + p.status + '</span>';
1466
1475
  html += '<span style="font-size:0.7rem;font-family:var(--font-mono);color:var(--text-muted)">' + p.id.slice(0, 8) + '…</span>';
1467
1476
  html += '</div>';
1468
- html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + objective + '</div>';
1477
+ html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + escapeHtml(objective) + '</div>';
1469
1478
  html += '<div style="display:flex;gap:1rem;font-size:0.72rem;color:var(--text-muted);flex-wrap:wrap">';
1470
- html += '<span>📁 ' + (p.project || '?') + '</span>';
1479
+ html += '<span>📁 ' + escapeHtml(p.project || '?') + '</span>';
1471
1480
  html += '<span>🔄 ' + p.iteration + ' / ' + maxIter + '</span>';
1472
- html += '<span>📍 ' + (p.current_step || '?') + '</span>';
1481
+ html += '<span>📍 ' + escapeHtml(p.current_step || '?') + '</span>';
1473
1482
  html += '<span>🕐 ' + new Date(p.updated_at).toLocaleString() + '</span>';
1474
1483
  html += '</div>';
1475
1484
  if (p.error) {
1476
- html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + p.error.slice(0, 200) + '</div>';
1485
+ html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + escapeHtml(p.error.slice(0, 200)) + '</div>';
1477
1486
  }
1478
1487
  if (isActive) {
1479
1488
  html += '<div style="margin-top:0.5rem"><button onclick="abortPipeline(this.dataset.id)" data-id="' + p.id + '" class="cleanup-btn" style="font-size:0.72rem">🛑 Abort Pipeline</button></div>';
@@ -1495,7 +1504,7 @@ function loadPipelines() {
1495
1504
  }
1496
1505
  })
1497
1506
  .catch(function (err) {
1498
- document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + err.message + '</div>';
1507
+ document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + escapeHtml(err.message) + '</div>';
1499
1508
  });
1500
1509
  }
1501
1510
  function abortPipeline(id) {
@@ -1672,11 +1681,36 @@ function loadIdentityChip() {
1672
1681
  select = document.getElementById('projectSelect');
1673
1682
  if (data.projects && data.projects.length > 0) {
1674
1683
  select.innerHTML = '<option value="">— Select a project —</option>' +
1675
- data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1684
+ data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
1685
+
1686
+ // v7.5.0: Background fetch intent health to add global traffic-light dots
1687
+ // Fetch sequentially to prevent SQLite WAL saturation with N concurrent loadContext calls
1688
+ var pIndex = 0;
1689
+ function fetchNextHealth() {
1690
+ if (pIndex >= data.projects.length) return;
1691
+ var p = data.projects[pIndex++];
1692
+ fetch('/api/intent-health?project=' + encodeURIComponent(p))
1693
+ .then(function (res) { return res.json(); })
1694
+ .then(function (hData) {
1695
+ if (hData && typeof hData.score === 'number') {
1696
+ var icon = hData.score >= 80 ? "🟢" : (hData.score >= 50 ? "🟡" : "🔴");
1697
+ var opt = null;
1698
+ for (var oi = 0; oi < select.options.length; oi++) {
1699
+ if (select.options[oi].value === p) { opt = select.options[oi]; break; }
1700
+ }
1701
+ if (opt) opt.textContent = icon + " " + p;
1702
+ }
1703
+ fetchNextHealth();
1704
+ }).catch(function () { fetchNextHealth(); });
1705
+ }
1706
+ if (data.projects && data.projects.length > 0) {
1707
+ fetchNextHealth();
1708
+ }
1709
+
1676
1710
  gp = document.getElementById('graphProjectFilter');
1677
1711
  if (gp) {
1678
1712
  gp.innerHTML = '<option value="">All Projects</option>' +
1679
- data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1713
+ data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
1680
1714
  }
1681
1715
  // Restore last selected project from localStorage
1682
1716
  var lastProject = localStorage.getItem('prism_last_project');
@@ -1708,8 +1742,15 @@ function loadProject() {
1708
1742
  switch (_a.label) {
1709
1743
  case 0:
1710
1744
  project = document.getElementById('projectSelect').value;
1711
- if (!project)
1745
+ if (!project) {
1746
+ var hc = document.getElementById('intentHealthCard');
1747
+ if (hc) hc.style.display = 'none';
1748
+ var welcomeEl = document.getElementById('welcome');
1749
+ var contentEl = document.getElementById('content');
1750
+ if (contentEl) contentEl.style.display = 'none';
1751
+ if (welcomeEl) welcomeEl.style.display = 'flex';
1712
1752
  return [2 /*return*/];
1753
+ }
1713
1754
  // Persist last selected project
1714
1755
  try { localStorage.setItem('prism_last_project', project); } catch(e) {}
1715
1756
  document.getElementById('welcome').style.display = 'none';
@@ -1767,7 +1808,7 @@ function loadProject() {
1767
1808
  var snap = h.snapshot || {};
1768
1809
  var summary = snap.last_summary || snap.summary || 'Snapshot';
1769
1810
  return '<div class="timeline-item history">' +
1770
- '<div class="meta"><span class="badge badge-purple">v' + h.version + '</span>' +
1811
+ '<div class="meta"><span class="badge badge-purple">v' + escapeHtml(h.version) + '</span>' +
1771
1812
  '<span>' + formatDate(h.created_at) + '</span></div>' +
1772
1813
  escapeHtml(summary) + '</div>';
1773
1814
  }).join('');
@@ -1785,7 +1826,7 @@ function loadProject() {
1785
1826
  try {
1786
1827
  var parsed = typeof decisions === 'string' ? JSON.parse(decisions) : decisions;
1787
1828
  if (Array.isArray(parsed) && parsed.length > 0) {
1788
- extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.join(', ') + '</div>';
1829
+ extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.map(function(d) { return escapeHtml(d); }).join(', ') + '</div>';
1789
1830
  }
1790
1831
  }
1791
1832
  catch (e) { }
@@ -1851,6 +1892,10 @@ function loadProject() {
1851
1892
  document.getElementById('content').className = 'grid grid-main fade-in';
1852
1893
  document.getElementById('content').style.display = 'grid';
1853
1894
  projectLoaded = true; // Fix 3: mark project as loaded for tab-switch restore
1895
+
1896
+ // Feature A: Run Intent Health Fetch
1897
+ fetchIntentHealth(project);
1898
+
1854
1899
  // v3.1: Analytics + Lifecycle Controls + Import
1855
1900
  document.getElementById('analyticsCard').style.display = 'block';
1856
1901
  document.getElementById('lifecycleCard').style.display = 'block';
@@ -2393,7 +2438,7 @@ function showToast(msg, isErr) {
2393
2438
  function escapeHtml(str) {
2394
2439
  if (!str)
2395
2440
  return '';
2396
- return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2441
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
2397
2442
  }
2398
2443
  function formatDate(isoStr) {
2399
2444
  if (!isoStr)
@@ -4060,6 +4105,67 @@ if ('serviceWorker' in navigator) {
4060
4105
  });
4061
4106
  }
4062
4107
 
4108
+ /* --- INTENT HEALTH COMPONENT (ES5 Safe) --- */
4109
+
4110
+ function fetchIntentHealth(project) {
4111
+ var container = document.getElementById('intentHealthCardContent');
4112
+ var card = document.getElementById('intentHealthCard');
4113
+ if (!container || !card) return;
4114
+
4115
+ card.style.display = 'block';
4116
+ container.innerHTML = '<div style="color: #8b949e; padding: 16px;">Computing intent debt...</div>';
4117
+
4118
+ fetch('/api/intent-health?project=' + encodeURIComponent(project))
4119
+ .then(function(res) { return res.json(); })
4120
+ .then(function(data) {
4121
+ if (data.error) {
4122
+ container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Error: ' + escapeHtml(data.error) + '</div>';
4123
+ return;
4124
+ }
4125
+ renderIntentHealthCard(data);
4126
+ })
4127
+ .catch(function(err) {
4128
+ container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Failed to load intent health.</div>';
4129
+ });
4130
+ }
4131
+
4132
+ function renderIntentHealthCard(data) {
4133
+ var container = document.getElementById('intentHealthCardContent');
4134
+ if (!container) return;
4135
+
4136
+ // Determine Gauge Color
4137
+ var scoreColor = '#2ea043'; // Green
4138
+ if (data.score < 50) {
4139
+ scoreColor = '#ff7b72'; // Red
4140
+ } else if (data.score < 80) {
4141
+ scoreColor = '#d29922'; // Yellow
4142
+ }
4143
+
4144
+ // Render Signals
4145
+ var signalsHtml = '';
4146
+ for (var i = 0; i < data.signals.length; i++) {
4147
+ var sig = data.signals[i];
4148
+ var icon = '🟢';
4149
+ if (sig.severity === 'warn') icon = '🟡';
4150
+ if (sig.severity === 'critical') icon = '🔴';
4151
+ signalsHtml += '<div style="margin-bottom: 8px; font-size: 13px;">' + icon + ' ' + escapeHtml(sig.message) + '</div>';
4152
+ }
4153
+
4154
+ // Render Layout
4155
+ var html = '' +
4156
+ '<div style="display: flex; gap: 24px; align-items: center; padding: 16px;">' +
4157
+ '<div style="text-align: center; flex-shrink: 0; min-width: 80px;">' +
4158
+ '<div style="font-size: 42px; font-weight: bold; color: ' + scoreColor + '; line-height: 1;">' + (typeof data.score === 'number' ? data.score : '—') + '</div>' +
4159
+ '<div style="font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; margin-top: 8px;">Health Score</div>' +
4160
+ '</div>' +
4161
+ '<div style="flex-grow: 1; border-left: 1px solid var(--border-glass); padding-left: 24px;">' +
4162
+ signalsHtml +
4163
+ '</div>' +
4164
+ '</div>';
4165
+
4166
+ container.innerHTML = html;
4167
+ }
4168
+
4063
4169
  </script>
4064
4170
  </body>
4065
4171
  </html>`;
@@ -1184,6 +1184,30 @@ export class SqliteStorage {
1184
1184
  importance: r.importance,
1185
1185
  }));
1186
1186
  }
1187
+ // ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
1188
+ context.recent_validations = []; // Default empty
1189
+ try {
1190
+ const validationResult = await this.db.execute({
1191
+ sql: `SELECT run_at, passed, pass_rate, gate_action, critical_failures
1192
+ FROM verification_runs
1193
+ WHERE project = ? AND user_id = ?
1194
+ ORDER BY run_at DESC
1195
+ LIMIT 3`,
1196
+ args: [project, userId],
1197
+ });
1198
+ if (validationResult.rows.length > 0) {
1199
+ context.recent_validations = validationResult.rows.map(r => ({
1200
+ run_at: r.run_at,
1201
+ passed: Boolean(r.passed),
1202
+ pass_rate: r.pass_rate,
1203
+ gate_action: r.gate_action,
1204
+ critical_failures: Number(r.critical_failures) || 0,
1205
+ }));
1206
+ }
1207
+ }
1208
+ catch (e) {
1209
+ // Graceful degradation if verification_runs table hasn't been migrated yet
1210
+ }
1187
1211
  if (level === "standard") {
1188
1212
  // Add recent ledger entries (role-scoped)
1189
1213
  const recentLedger = await this.db.execute({
@@ -170,7 +170,36 @@ export class SupabaseStorage {
170
170
  p_role: role || "global", // v3.0: pass role to RPC
171
171
  });
172
172
  const data = Array.isArray(result) ? result[0] : result;
173
- return data ?? null;
173
+ const context = data ?? null;
174
+ if (!context)
175
+ return null;
176
+ // ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
177
+ if (level === "standard" || level === "deep") {
178
+ context.recent_validations = [];
179
+ try {
180
+ const valData = await supabaseGet("verification_runs", {
181
+ project: `eq.${project}`,
182
+ user_id: `eq.${userId}`,
183
+ select: "run_at,passed,pass_rate,gate_action,critical_failures",
184
+ order: "run_at.desc",
185
+ limit: "3"
186
+ });
187
+ const validations = Array.isArray(valData) ? valData : [];
188
+ if (validations.length > 0) {
189
+ context.recent_validations = validations.map((r) => ({
190
+ run_at: r.run_at,
191
+ passed: Boolean(r.passed),
192
+ pass_rate: r.pass_rate,
193
+ gate_action: r.gate_action,
194
+ critical_failures: Number(r.critical_failures) || 0,
195
+ }));
196
+ }
197
+ }
198
+ catch (e) {
199
+ // Graceful degradation if table doesn't exist yet
200
+ }
201
+ }
202
+ return context;
174
203
  }
175
204
  catch (e) {
176
205
  debugLog("[SupabaseStorage] loadContext RPC failed: " + (e instanceof Error ? e.message : String(e)));
@@ -657,6 +657,19 @@ export async function sessionLoadContextHandler(args) {
657
657
  if (d.session_history?.length) {
658
658
  formattedContext += `\n📂 Session History (${d.session_history.length} entries):\n` + d.session_history.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
659
659
  }
660
+ if (d.recent_validations?.length) {
661
+ formattedContext += `\n🔬 Recent Validations:\n` + d.recent_validations.map((v) => {
662
+ const passStr = v.passed ? "✅ PASS" : "❌ FAIL";
663
+ const icon = v.gate_action ? (v.passed ? "🚀" : "🛑") : "ℹ️";
664
+ const dateStr = (v.run_at || "unknown").split("T")[0];
665
+ const rateStr = Math.round((Number(v.pass_rate) || 0) * 100);
666
+ let out = ` ${icon} [${dateStr}] ${passStr} (${rateStr}%)`;
667
+ if (v.critical_failures > 0) {
668
+ out += ` -> Critical Blockers: ${v.critical_failures}`;
669
+ }
670
+ return out;
671
+ }).join("\n") + `\n`;
672
+ }
660
673
  // ─── Role-Scoped Skill Injection ─────────────────────────────
661
674
  // If the active role has a skill document stored, append it so the
662
675
  // agent loads its rules/conventions automatically at session start.
@@ -85,8 +85,11 @@ export class AnthropicAdapter {
85
85
  // silent zero-vector or crash.
86
86
  throw new Error("AnthropicAdapter does not support text embeddings. " +
87
87
  "Anthropic has no native embedding API. " +
88
- "In the Mind Palace dashboard, set 'Embedding Provider' to Gemini or OpenAI/Ollama. " +
89
- "When using Ollama locally, 'nomic-embed-text' is a free, high-quality option.");
88
+ "Their official recommendation is Voyage AI (voyage-3, voyage-3-lite). " +
89
+ "In the Mind Palace dashboard, set 'Embedding Provider' to: " +
90
+ "'voyage' (Anthropic-recommended, set VOYAGE_API_KEY), " +
91
+ "'openai' (OpenAI cloud or local Ollama with nomic-embed-text), " +
92
+ "or 'gemini' (Google AI, set GOOGLE_API_KEY).");
90
93
  }
91
94
  // ─── Image Description (VLM) ─────────────────────────────────────────────
92
95
  /**
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Voyage AI Adapter (v1.0)
3
+ * ─────────────────────────────────────────────────────────────────────────────
4
+ * PURPOSE:
5
+ * Implements LLMProvider using Voyage AI's REST API for text embeddings.
6
+ * Voyage AI is the embedding provider officially recommended by Anthropic
7
+ * for use alongside Claude — it fills the gap left by Anthropic's lack
8
+ * of a native embedding API.
9
+ *
10
+ * TEXT GENERATION:
11
+ * Voyage AI is an embeddings-only service. generateText() throws an explicit
12
+ * error, the same pattern used by AnthropicAdapter.generateEmbedding().
13
+ * Set text_provider separately (anthropic, openai, or gemini).
14
+ *
15
+ * EMBEDDING DIMENSION PARITY (768 dims):
16
+ * Prism's SQLite (sqlite-vec) and Supabase (pgvector) schemas define
17
+ * embedding columns as EXACTLY 768 dimensions.
18
+ *
19
+ * Voyage solution: voyage-3 and voyage-3-lite output 1024 dims by default,
20
+ * but both support the `output_dimension` parameter (Matryoshka Representation
21
+ * Learning), enabling truncation to 768 while preserving quality.
22
+ * voyage-3-lite at 768 dims is the fastest and most cost-efficient option.
23
+ *
24
+ * MODELS:
25
+ * voyage-3 — Highest quality, 1024 dims natively (MRL → 768)
26
+ * voyage-3-lite — Fast & cheap, 512 dims natively (MRL → 768 NOT supported)
27
+ * voyage-3-large — Best quality, use for offline indexing
28
+ * voyage-code-3 — Optimised for code (recommended for dev sessions)
29
+ *
30
+ * NOTE: voyage-3-lite natively outputs 512 dims; it does NOT support
31
+ * output_dimension truncation to 768. Use voyage-3 for dimension parity.
32
+ * Default is voyage-3 for this reason.
33
+ *
34
+ * CONFIG KEYS (Prism dashboard "AI Providers" tab OR environment variables):
35
+ * voyage_api_key — Required. Voyage AI API key (pa-...)
36
+ * voyage_model — Embedding model (default: voyage-3)
37
+ *
38
+ * USAGE WITH ANTHROPIC TEXT PROVIDER:
39
+ * Set text_provider=anthropic, embedding_provider=voyage in the dashboard.
40
+ * This pairs Claude for reasoning with Voyage for semantic memory — the
41
+ * combination Anthropic recommends in their documentation.
42
+ *
43
+ * API REFERENCE:
44
+ * https://docs.voyageai.com/reference/embeddings-api
45
+ */
46
+ import { getSettingSync } from "../../../storage/configStorage.js";
47
+ import { debugLog } from "../../logger.js";
48
+ // ─── Constants ────────────────────────────────────────────────────────────────
49
+ // Must match Prism's DB schema (sqlite-vec and pgvector column sizes).
50
+ const EMBEDDING_DIMS = 768;
51
+ // voyage-3 supports up to 32,000 tokens. Character-based cap (consistent
52
+ // with OpenAI and Gemini adapters) avoids tokenizer dependency.
53
+ // 8000 chars ≈ 1500-2000 tokens for typical session summaries.
54
+ const MAX_EMBEDDING_CHARS = 8000;
55
+ // Default model: voyage-3 (supports output_dimension=768 via MRL)
56
+ // voyage-3-lite is NOT recommended as its native 512 dims < 768.
57
+ const DEFAULT_MODEL = "voyage-3";
58
+ const VOYAGE_API_BASE = "https://api.voyageai.com/v1";
59
+ // ─── Adapter ─────────────────────────────────────────────────────────────────
60
+ export class VoyageAdapter {
61
+ apiKey;
62
+ constructor() {
63
+ const apiKey = getSettingSync("voyage_api_key", process.env.VOYAGE_API_KEY ?? "");
64
+ if (!apiKey) {
65
+ throw new Error("VoyageAdapter requires a Voyage AI API key. " +
66
+ "Get one free at https://dash.voyageai.com — then set VOYAGE_API_KEY " +
67
+ "or configure it in the Mind Palace dashboard under 'AI Providers'.");
68
+ }
69
+ this.apiKey = apiKey;
70
+ debugLog("[VoyageAdapter] Initialized");
71
+ }
72
+ // ─── Text Generation (Not Supported) ────────────────────────────────────
73
+ async generateText(_prompt, _systemInstruction) {
74
+ // Voyage AI is an embeddings-only service.
75
+ // Use text_provider=anthropic, openai, or gemini for text generation.
76
+ throw new Error("VoyageAdapter does not support text generation. " +
77
+ "Voyage AI is an embeddings-only service. " +
78
+ "Set text_provider to 'anthropic', 'openai', or 'gemini' in the dashboard.");
79
+ }
80
+ // ─── Embedding Generation ────────────────────────────────────────────────
81
+ async generateEmbedding(text) {
82
+ if (!text || !text.trim()) {
83
+ throw new Error("[VoyageAdapter] generateEmbedding called with empty text");
84
+ }
85
+ // Truncate to character limit (consistent with other adapters)
86
+ const truncated = text.length > MAX_EMBEDDING_CHARS
87
+ ? text.slice(0, MAX_EMBEDDING_CHARS).replace(/\s+\S*$/, "")
88
+ : text;
89
+ const model = getSettingSync("voyage_model", DEFAULT_MODEL);
90
+ debugLog(`[VoyageAdapter] generateEmbedding — model=${model}, chars=${truncated.length}`);
91
+ const requestBody = {
92
+ input: [truncated],
93
+ model,
94
+ // Request exactly 768 dims via Matryoshka truncation.
95
+ // Supported by voyage-3, voyage-3-large, voyage-code-3.
96
+ // voyage-3-lite (native 512 dims) will ignore this and return 512,
97
+ // which will be caught by the dimension guard below.
98
+ output_dimension: EMBEDDING_DIMS,
99
+ };
100
+ const response = await fetch(`${VOYAGE_API_BASE}/embeddings`, {
101
+ method: "POST",
102
+ headers: {
103
+ "Authorization": `Bearer ${this.apiKey}`,
104
+ "Content-Type": "application/json",
105
+ },
106
+ body: JSON.stringify(requestBody),
107
+ });
108
+ if (!response.ok) {
109
+ const errorText = await response.text().catch(() => "unknown error");
110
+ throw new Error(`[VoyageAdapter] API request failed — status=${response.status}: ${errorText}`);
111
+ }
112
+ const data = (await response.json());
113
+ const embedding = data?.data?.[0]?.embedding;
114
+ if (!Array.isArray(embedding)) {
115
+ throw new Error("[VoyageAdapter] Unexpected response format — no embedding array found");
116
+ }
117
+ // Dimension guard: Prism's DB schema requires exactly 768 dims.
118
+ // This catches voyage-3-lite (512) or future API changes silently early.
119
+ if (embedding.length !== EMBEDDING_DIMS) {
120
+ throw new Error(`[VoyageAdapter] Embedding dimension mismatch: expected ${EMBEDDING_DIMS}, ` +
121
+ `got ${embedding.length}. ` +
122
+ `Use voyage-3 (not voyage-3-lite) to get 768-dim output via MRL truncation. ` +
123
+ `Change voyage_model in the Mind Palace dashboard.`);
124
+ }
125
+ debugLog(`[VoyageAdapter] Embedding generated — dims=${embedding.length}, ` +
126
+ `tokens_used=${data.usage?.total_tokens ?? "unknown"}`);
127
+ return embedding;
128
+ }
129
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * LLM Provider Factory (v4.4Split Provider Architecture)
2
+ * LLM Provider Factory (v4.5Voyage AI Embedding Support)
3
3
  * ─────────────────────────────────────────────────────────────────────────────
4
4
  * PURPOSE:
5
5
  * Single point of resolution for the active LLMProvider.
@@ -11,19 +11,21 @@
11
11
  * Two independent settings control text and embedding routing:
12
12
  *
13
13
  * text_provider — "gemini" (default) | "openai" | "anthropic"
14
- * embedding_provider — "auto" (default) | "gemini" | "openai"
14
+ * embedding_provider — "auto" (default) | "gemini" | "openai" | "voyage"
15
15
  *
16
16
  * When embedding_provider = "auto":
17
17
  * * If text_provider is gemini or openai → use same provider for embeddings
18
18
  * * If text_provider is anthropic → auto-fallback to gemini for embeddings
19
- * (Anthropic has no native embedding API)
19
+ * (Anthropic has no native embedding API; consider setting
20
+ * embedding_provider=voyage for the Anthropic-recommended pairing)
20
21
  *
21
22
  * EXAMPLE CONFIGURATIONS:
22
23
  * text_provider=gemini, embedding_provider=auto → Gemini+Gemini (default)
23
24
  * text_provider=openai, embedding_provider=auto → OpenAI+OpenAI
24
25
  * text_provider=anthropic, embedding_provider=auto → Claude+Gemini (auto-bridge)
26
+ * text_provider=anthropic, embedding_provider=voyage → Claude+Voyage (Anthropic-recommended)
25
27
  * text_provider=anthropic, embedding_provider=openai → Claude+Ollama (cost-optimized)
26
- * text_provider=gemini, embedding_provider=openai → Gemini+Ollama (mixed)
28
+ * text_provider=gemini, embedding_provider=voyage → Gemini+Voyage (mixed)
27
29
  *
28
30
  * SINGLETON + GRACEFUL DEGRADATION:
29
31
  * Same as before — instance cached per process, errors fall back to Gemini.
@@ -41,6 +43,7 @@ import { getSettingSync } from "../../storage/configStorage.js";
41
43
  import { GeminiAdapter } from "./adapters/gemini.js";
42
44
  import { OpenAIAdapter } from "./adapters/openai.js";
43
45
  import { AnthropicAdapter } from "./adapters/anthropic.js";
46
+ import { VoyageAdapter } from "./adapters/voyage.js";
44
47
  import { TracingLLMProvider } from "./adapters/traced.js";
45
48
  // Module-level singleton — one composed provider per MCP server process.
46
49
  let providerInstance = null;
@@ -59,8 +62,10 @@ function buildEmbeddingAdapter(type) {
59
62
  // Note: "anthropic" is intentionally absent from this switch.
60
63
  // Anthropic has no embedding API, so it can never be an embedding provider.
61
64
  // The factory resolves "auto" away from "anthropic" before calling this.
65
+ // For Anthropic text users, "voyage" is the Anthropic-recommended pairing.
62
66
  switch (type) {
63
67
  case "openai": return new OpenAIAdapter();
68
+ case "voyage": return new VoyageAdapter();
64
69
  case "gemini":
65
70
  default: return new GeminiAdapter();
66
71
  }
@@ -90,7 +95,9 @@ export function getLLMProvider() {
90
95
  if (textType === "anthropic") {
91
96
  console.info("[LLMFactory] text_provider=anthropic with embedding_provider=auto: " +
92
97
  "routing embeddings to GeminiAdapter (Anthropic has no native embedding API). " +
93
- "Set embedding_provider=openai in dashboard to use Ollama/OpenAI instead.");
98
+ "For the Anthropic-recommended pairing, set embedding_provider=voyage in the dashboard " +
99
+ "(voyage-3 supports 768-dim output via MRL). " +
100
+ "Alternatively, set embedding_provider=openai to use Ollama/OpenAI.");
94
101
  }
95
102
  }
96
103
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "7.4.0",
3
+ "version": "7.6.0",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
5
  "description": "The Mind Palace for AI Agents — adversarial evaluation (PLAN_CONTRACT→EVALUATE anti-sycophancy), fail-closed Dark Factory autonomous pipelines (3-gate parse→type→scope), persistent memory (SQLite/Supabase), ACT-R cognitive retrieval, behavioral learning & IDE rules sync, multi-agent Hivemind, time travel, visual dashboard. Zero-config local mode.",
6
6
  "module": "index.ts",