prism-mcp-server 7.4.0 → 7.5.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 Demo](docs/mind-palace-demo.webp)
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.
@@ -26,6 +28,7 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
26
28
  - [Setup Guides](#-setup-guides)
27
29
  - [Universal Import](#-universal-import-bring-your-history)
28
30
  - [What Makes Prism Different](#-what-makes-prism-different)
31
+ - [Data Privacy & Egress](#-data-privacy--egress)
29
32
  - [Use Cases](#-use-cases)
30
33
  - [What's New](#-whats-new)
31
34
  - [How Prism Compares](#-how-prism-compares)
@@ -33,7 +36,7 @@ Works with **Claude Desktop · Claude Code · Cursor · Windsurf · Cline · Gem
33
36
  - [Environment Variables](#environment-variables)
34
37
  - [Architecture](#architecture)
35
38
  - [Scientific Foundation](#-scientific-foundation)
36
- - [Product Roadmap](#-product-roadmap)
39
+ - [Milestones & Roadmap](#-milestones--roadmap)
37
40
  - [Troubleshooting FAQ](#-troubleshooting-faq)
38
41
 
39
42
  ---
@@ -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,8 @@ 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
+ > 🔄 **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.
88
+
73
89
  <details>
74
90
  <summary>Port 3000 already in use? (Next.js / Vite / etc.)</summary>
75
91
 
@@ -109,6 +125,8 @@ Then open `http://localhost:3001` instead.
109
125
 
110
126
  > 🔑 The core Mind Palace works **100% offline** with zero API keys. Cloud keys unlock intelligence features. See [Environment Variables](#environment-variables).
111
127
 
128
+ > 💰 **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.
129
+
112
130
  ---
113
131
 
114
132
  ## ✨ The Magic Moment
@@ -321,6 +339,12 @@ Then add to your MCP config:
321
339
  > **❓ Seeing warnings about missing API keys on startup?**
322
340
  > 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
341
 
342
+ > 💡 **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:
343
+ > ```
344
+ > At the start of every conversation, call session_load_context with project "my-project" before doing any work.
345
+ > ```
346
+ > 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.
347
+
324
348
  ---
325
349
 
326
350
  ## 📥 Universal Import — Bring Your History
@@ -369,6 +393,7 @@ Every save creates a versioned snapshot. Made a mistake? `memory_checkout` rever
369
393
  A gorgeous glassmorphism UI at `localhost:3000` that lets you see exactly what your agent is thinking:
370
394
 
371
395
  - **Current State & TODOs** — the exact context injected into the LLM's prompt
396
+ - **Intent Health Gauges** — per-project 0–100 health score with staleness decay, TODO load, and decision signals
372
397
  - **Interactive Knowledge Graph** — force-directed neural graph with click-to-filter, node renaming, and surgical keyword deletion
373
398
  - **Deep Storage Manager** — preview and execute vector purge operations with dry-run safety
374
399
  - **Session Ledger** — full audit trail of every decision your agent has made
@@ -387,7 +412,7 @@ Powered by a pure TypeScript port of Google's TurboQuant (inspired by Google's I
387
412
  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
413
 
389
414
  ### 🚦 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)
415
+ 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
416
 
392
417
  ### 🖼️ Visual Memory
393
418
  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 +423,36 @@ OpenTelemetry spans for every MCP tool call, LLM hop, and background worker. Rou
398
423
  ### 🌐 Autonomous Web Scholar
399
424
  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
425
 
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.
426
+ ### 🔒 Data Privacy & Egress
427
+
428
+ **Where is my data stored?**
429
+
430
+ All data lives under `~/.prism-mcp/` on your machine:
431
+
432
+ | File | Contents |
433
+ |------|----------|
434
+ | `~/.prism-mcp/data.db` | All sessions, handoffs, embeddings, knowledge graph (SQLite + WAL) |
435
+ | `~/.prism-mcp/prism-config.db` | Dashboard settings, system config, API keys |
436
+ | `~/.prism-mcp/media/<project>/` | Visual memory vault (screenshots, HTML captures) |
437
+ | `~/.prism-mcp/dashboard.port` | Ephemeral port lock file |
438
+ | `~/.prism-mcp/sync.lock` | Sync coordination lock |
439
+
440
+ **Hard reset:** To completely erase your agent's brain, stop Prism and delete the directory:
441
+ ```bash
442
+ rm -rf ~/.prism-mcp
443
+ ```
444
+ Prism will recreate the directory with empty databases on next startup.
445
+
446
+ **What leaves your machine?**
447
+ - **Local mode (default):** Nothing. Zero network calls. All data is on-disk SQLite.
448
+ - **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.
449
+ - **With `BRAVE_API_KEY` / `FIRECRAWL_API_KEY`:** Web Scholar queries are sent to Brave/Firecrawl for search and scraping.
450
+ - **With Supabase:** Session data syncs to your own Supabase instance (you control the Postgres database).
451
+
452
+ **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.
403
453
 
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)
406
456
 
407
457
  ---
408
458
 
@@ -412,6 +462,7 @@ When you trigger a Dark Factory pipeline, Prism doesn't just run your task — i
412
462
  - **Multi-agent collaboration** — Dev, QA, and PM agents share real-time context without stepping on each other's memory.
413
463
  - **Consulting / multi-project** — Switch between client projects with progressive loading: `quick` (~50 tokens), `standard` (~200), or `deep` (~1000+).
414
464
  - **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.
465
+ - **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
466
  - **Team onboarding** — New team member's agent loads the full project history instantly.
416
467
  - **Behavior enforcement** — Agent corrections auto-graduate into permanent `.cursorrules` / `.clauderules` rules.
417
468
  - **Offline / air-gapped** — Full SQLite local mode + Ollama LLM adapter. Zero internet dependency.
@@ -543,18 +594,13 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
543
594
 
544
595
  ## 🆕 What's New
545
596
 
597
+ > **Current release: v7.5.0**
546
598
 
547
- > **Current release: v7.4.0**
599
+ - 🩺 **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.
600
+ - ⚔️ **v7.4.0 — Adversarial Evaluation:** Split-brain anti-sycophancy pipeline. Generator and evaluator in isolated roles with evidence-bound findings.
601
+ - 🏭 **v7.3.x — Dark Factory + Stability:** Fail-closed 3-gate execution pipeline. Dashboard stability and verification diagnostics.
548
602
 
549
- - ⚔️ **v7.4.0 Adversarial 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.
556
-
557
- 👉 **[Full release history → CHANGELOG.md](CHANGELOG.md)** · [ROADMAP →](ROADMAP.md)
603
+ 👉 **[Full release history CHANGELOG.md](CHANGELOG.md)** · **[ROADMAP →](ROADMAP.md)**
558
604
 
559
605
  ---
560
606
 
@@ -793,6 +839,10 @@ Requires `PRISM_DARK_FACTORY_ENABLED=true`.
793
839
 
794
840
  </details>
795
841
 
842
+ ### System Settings (Dashboard)
843
+ Some configurations are stored dynamically in SQLite (`system_settings` table) and can be edited through the Dashboard UI at `http://localhost:3000`:
844
+ - **`intent_health_stale_threshold_days`** (default: `30`): Number of days before a project is considered fully stale for Intent Health scoring.
845
+
796
846
  ---
797
847
 
798
848
  ## Architecture
@@ -888,6 +938,7 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
888
938
  | **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
939
  | **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
940
  | **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 |
941
+ | **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
942
  | **v7.x** | Affect-Tagged Memory — sentiment shapes what gets recalled | Affect-modulated retrieval (neuroscience) | 🔭 Horizon |
892
943
  | **v8+** | Zero-Search Retrieval — no index, no ANN, just ask the vector | Holographic Reduced Representations | 🔭 Horizon |
893
944
 
@@ -895,34 +946,26 @@ Prism is evolving from smart session logging toward a **cognitive memory archite
895
946
 
896
947
  ---
897
948
 
898
- ## 📦 Recent Milestones & Roadmap
899
-
900
- > **[Full ROADMAP.md →](ROADMAP.md)**
949
+ ## 📦 Milestones & Roadmap
901
950
 
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.
951
+ > **Current: v7.5.0** Intent Health Dashboard + XSS Hardening ([CHANGELOG](CHANGELOG.md))
904
952
 
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.
907
-
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.
953
+ | Release | Headline |
954
+ |---------|----------|
955
+ | **v7.5** | Intent Health scoring + 10 XSS patches |
956
+ | **v7.4** | Adversarial Evaluation (anti-sycophancy) |
957
+ | **v7.3** | Dark Factory fail-closed execution |
958
+ | **v7.2** | Verification Harness |
959
+ | **v7.1** | Task Router |
960
+ | **v7.0** | ACT-R Activation Memory |
961
+ | **v6.5** | HDC Cognitive Routing |
962
+ | **v6.2** | Synthesize & Prune |
922
963
 
923
964
  ### 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.
965
+ - **v7.x: Affect-Tagged Memory** — Recall prioritization improves by weighting memories with affective/contextual valence.
966
+ - **v8+: Zero-Search Retrieval** — Direct vector-addressed recall reduces retrieval indirection.
967
+
968
+ 👉 **[Full ROADMAP.md →](ROADMAP.md)**
926
969
 
927
970
 
928
971
  ## ❓ Troubleshooting FAQ
package/dist/cli.js CHANGED
File without changes
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "7.4.0",
3
+ "version": "7.5.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",