prism-mcp-server 9.2.0 → 9.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -278,6 +278,13 @@ bash ~/.gemini/antigravity/scratch/prism_session_loader.sh my-project
278
278
 
279
279
  The CLI uses the same storage layer as the MCP tool (SQLite or Supabase).
280
280
 
281
+ > ⚠️ **CRITICAL (v9.2.2): Split-Brain Prevention**
282
+ > If your MCP server is configured with `PRISM_STORAGE=local` but Supabase credentials are also set, the CLI may read from the **wrong backend** (Supabase) while the server writes to SQLite. This causes stale TODOs and divergent state. Always pass `--storage local` explicitly when using the CLI in a local-mode environment:
283
+ > ```bash
284
+ > prism load my-project --storage local --json
285
+ > ```
286
+ > The `prism_session_loader.sh` wrapper handles this automatically since v9.2.2.
287
+
281
288
  </details>
282
289
 
283
290
  <details>
@@ -295,6 +302,10 @@ SUMMARY=$(echo "$CONTEXT" | jq -r '.handoff[0].last_summary')
295
302
  VERSION=$(echo "$CONTEXT" | jq -r '.handoff[0].version')
296
303
  echo "Project at v$VERSION: $SUMMARY"
297
304
 
305
+ # Explicit storage backend (v9.2.2 — prevents split-brain)
306
+ prism load my-project --storage local --json
307
+ prism load my-project --storage supabase --json
308
+
298
309
  # Role-scoped loading
299
310
  prism load my-project --role qa --json
300
311
 
@@ -778,8 +789,10 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
778
789
 
779
790
  ## 🆕 What's New
780
791
 
781
- > **Current release: v9.1.0Task Router v2 & Local Agent Hardening**
792
+ > **Current release: v9.2.2Critical Split-Brain Fix**
782
793
 
794
+ - 🚨 **v9.2.2 — Critical: Split-Brain Detection & Prevention:** When multiple MCP clients use different storage backends (e.g., Claude Desktop → Supabase, Antigravity → SQLite), session state could silently diverge, causing agents to act on stale TODOs and outdated context. **New: `--storage` flag** on `prism load` CLI lets callers explicitly select which backend to read from. **New: Split-Brain Drift Detection** in `session_load_context` — compares active and alternate backend versions at load time and warns prominently when they diverge. Session loader script updated to respect `PRISM_STORAGE` environment variable.
795
+ - 💻 **v9.2.1 — CLI Full Feature Parity:** `prism load` text mode now delegates to the real `session_load_context` handler, giving CLI-only users the same enriched output as MCP clients: morning briefings, reality drift detection, SDM intuitive recall, visual memory index, role-scoped skill injection, behavioral warnings, importance scores, and agent identity. JSON mode now includes `agent_name` from dashboard settings. Session loader script PATH fix for Homebrew/nvm/volta environments.
783
796
  - 🚦 **v9.1.0 — Task Router v2:** File-type complexity signal for intelligent code-vs-config routing, 6-signal weighted heuristic engine, multi-step false-positive fix, expanded file extension classification. Local agent hardened with buffered streaming, system prompts, memory trimming, and stateful `/api/chat` API.
784
797
  - 🔒 **v9.0.5 — JWKS Auth Security Hardening:** JWT audience/issuer claim validation (`PRISM_JWT_AUDIENCE`, `PRISM_JWT_ISSUER`), structured error logging for JWT failures, typed `PrismAuthenticatedRequest` interface, 11 new JWKS unit tests, Smithery server card fix. Vendor-neutral — tested with Auth0, AgentLair ([llms.txt](https://agentlair.com/llms.txt)), Keycloak, and custom JWKS endpoints.
785
798
  - 🧠 **v9.0.0 — Autonomous Cognitive OS:** Token-Economic Reinforcement Learning (Surprisal Gate + Cognitive Budget), Affect-Tagged Memory (valence-scored retrieval), and Episodic→Semantic Consolidation. Your agents learn compression and develop intuition. → [Cognitive OS](#-autonomous-cognitive-os-v90)
@@ -844,12 +857,16 @@ Every other AI coding pipeline has a fatal flaw: it asks the same model that wro
844
857
 
845
858
  ## 💻 CLI Reference
846
859
 
847
- Prism includes a CLI for environments where MCP tools aren't available (CI/CD pipelines, Bash scripts, non-MCP IDEs like Antigravity):
860
+ Prism includes a CLI for environments where MCP tools aren't available (CI/CD pipelines, Bash scripts, non-MCP IDEs like Antigravity).
861
+
862
+ **Text mode** delegates to the real `session_load_context` handler — full feature parity with MCP clients, including morning briefings, reality drift detection, SDM intuitive recall, visual memory, role-scoped skills, behavioral warnings, and agent identity.
863
+
864
+ **JSON mode** emits a structured envelope for programmatic consumption (scripts, CI/CD, session loaders).
848
865
 
849
866
  ```bash
850
- # Load session context (same output as session_load_context MCP tool)
867
+ # Load session context (full enrichments — same as MCP tool)
851
868
  prism load my-project # Human-readable, standard depth
852
- prism load my-project --level deep # Full context
869
+ prism load my-project --level deep # Full context with all enrichments
853
870
  prism load my-project --level quick --json # Machine-readable JSON
854
871
  prism load my-project --role dev --json # Role-scoped loading
855
872
 
package/dist/cli.js CHANGED
@@ -7,6 +7,7 @@ import { getStorage, closeStorage } from './storage/index.js';
7
7
  import { getSetting } from './storage/configStorage.js';
8
8
  import { PRISM_USER_ID, SERVER_CONFIG } from './config.js';
9
9
  import { getCurrentGitState } from './utils/git.js';
10
+ import { sessionLoadContextHandler } from './tools/ledgerHandlers.js';
10
11
  const program = new Command();
11
12
  program
12
13
  .name('prism')
@@ -17,41 +18,64 @@ program
17
18
  // session_load_context tool. Works with both SQLite and Supabase.
18
19
  // Designed for environments that cannot use MCP tools directly
19
20
  // (Antigravity, Bash scripts, CI/CD pipelines).
21
+ //
22
+ // CRITICAL: --storage flag (v9.2.2)
23
+ // When multiple MCP clients use different storage backends (e.g.
24
+ // Claude Desktop → Supabase, Antigravity → SQLite), the CLI must
25
+ // be told which backend to read from. Without this, the CLI
26
+ // inherits PRISM_STORAGE from the shell env (defaulting to
27
+ // supabase), which may differ from the MCP server's config.
28
+ // This causes a "split-brain" where the CLI returns stale state
29
+ // from the wrong backend.
30
+ //
31
+ // TEXT MODE: Delegates to the real sessionLoadContextHandler for
32
+ // full feature parity — morning briefing, reality drift detection,
33
+ // SDM recall, visual memory, skill injection, behavioral warnings,
34
+ // importance scores, recent validations. Any future MCP enrichments
35
+ // automatically appear in CLI too.
36
+ //
37
+ // JSON MODE: Structured envelope for programmatic consumption
38
+ // (session loader scripts, CI/CD pipelines, etc.).
20
39
  program
21
40
  .command('load <project>')
22
41
  .description('Load session context for a project (same output as session_load_context MCP tool)')
23
42
  .option('-l, --level <level>', 'Context depth: quick, standard, deep', 'standard')
24
43
  .option('-r, --role <role>', 'Role scope for context loading')
44
+ .option('-s, --storage <backend>', 'Storage backend: local (SQLite) or supabase. Overrides PRISM_STORAGE env var.')
25
45
  .option('--json', 'Emit machine-readable JSON instead of formatted text')
26
46
  .action(async (project, options) => {
27
47
  try {
28
- const { level, role, json: jsonOutput } = options;
48
+ const { level, role, storage, json: jsonOutput } = options;
49
+ // v9.2.2: --storage flag overrides PRISM_STORAGE env var to prevent
50
+ // split-brain when CLI environment differs from MCP server config.
51
+ if (storage) {
52
+ const validStorages = ['local', 'supabase'];
53
+ if (!validStorages.includes(storage)) {
54
+ console.error(`Error: Invalid storage "${storage}". Must be one of: ${validStorages.join(', ')}`);
55
+ process.exit(1);
56
+ }
57
+ process.env.PRISM_STORAGE = storage;
58
+ }
29
59
  const validLevels = ['quick', 'standard', 'deep'];
30
60
  if (!validLevels.includes(level)) {
31
61
  console.error(`Error: Invalid level "${level}". Must be one of: ${validLevels.join(', ')}`);
32
62
  process.exit(1);
33
63
  }
34
- // Use the shared storage singleton (respects PRISM_STORAGE, dashboard config, etc.)
35
- const storage = await getStorage();
36
- const effectiveRole = role || await getSetting('default_role', '') || undefined;
37
- const data = await storage.loadContext(project, level, PRISM_USER_ID, effectiveRole);
38
- if (!data) {
39
- if (jsonOutput) {
64
+ if (jsonOutput) {
65
+ // ── JSON mode: structured output for programmatic consumption ──
66
+ const storageBackend = await getStorage();
67
+ const effectiveRole = role || await getSetting('default_role', '') || undefined;
68
+ const agentName = await getSetting('agent_name', '') || undefined;
69
+ const data = await storageBackend.loadContext(project, level, PRISM_USER_ID, effectiveRole);
70
+ if (!data) {
40
71
  console.log(JSON.stringify({ error: `No session context found for project "${project}"` }));
72
+ await closeStorage();
73
+ process.exit(0);
41
74
  }
42
- else {
43
- console.log(`No session context found for project "${project}" at level ${level}.`);
44
- console.log('This project has no previous session history. Starting fresh.');
45
- }
46
- await closeStorage();
47
- process.exit(0);
48
- }
49
- const d = data;
50
- // Gather git state for enrichment
51
- const gitState = getCurrentGitState();
52
- if (jsonOutput) {
53
- // Machine-readable JSON envelope — matches what prism_session_loader.sh produced
75
+ const d = data;
76
+ const gitState = getCurrentGitState();
54
77
  const output = {
78
+ agent_name: agentName || null,
55
79
  handoff: [{
56
80
  project,
57
81
  role: effectiveRole || d.role || 'global',
@@ -77,29 +101,23 @@ program
77
101
  console.log(JSON.stringify(output, null, 2));
78
102
  }
79
103
  else {
80
- // Human-readable formatted output (same format as MCP tool)
81
- let output = `📋 Session context for "${project}" (${level}):\n\n`;
82
- if (d.last_summary)
83
- output += `📝 Last Summary: ${d.last_summary}\n`;
84
- if (d.active_branch)
85
- output += `🌿 Active Branch: ${d.active_branch}\n`;
86
- if (d.key_context)
87
- output += `💡 Key Context: ${d.key_context}\n`;
88
- if (d.pending_todo?.length) {
89
- output += `\n✅ Open TODOs:\n` + d.pending_todo.map((t) => ` - ${t}`).join('\n') + '\n';
90
- }
91
- if (d.active_decisions?.length) {
92
- output += `\n⚖️ Active Decisions:\n` + d.active_decisions.map((dec) => ` - ${dec}`).join('\n') + '\n';
93
- }
94
- if (d.keywords?.length) {
95
- output += `\n🔑 Keywords: ${d.keywords.join(', ')}\n`;
96
- }
97
- if (d.recent_sessions?.length) {
98
- output += `\n⏳ Recent Sessions:\n` + d.recent_sessions.map((s) => ` [${s.session_date?.split('T')[0]}] ${s.summary}`).join('\n') + '\n';
104
+ // ── Text mode: full parity with MCP session_load_context ──
105
+ // Delegates to the real handler so all enrichments (morning briefing,
106
+ // reality drift, SDM recall, visual memory, skill injection,
107
+ // behavioral warnings, etc.) are included automatically.
108
+ const result = await sessionLoadContextHandler({ project, level, role });
109
+ // Surface handler-level errors (e.g. invalid args, storage failures)
110
+ if (result.isError) {
111
+ console.error(result.content[0]?.text || 'Unknown error loading context');
112
+ await closeStorage();
113
+ process.exit(1);
99
114
  }
100
- if (d.version != null) {
101
- output += `\n🔑 Session version: ${d.version}. Pass expected_version: ${d.version} when saving handoff.`;
115
+ let output = '';
116
+ if (result.content?.[0]) {
117
+ output = result.content[0].text;
102
118
  }
119
+ // Append git state (not included in the MCP handler output)
120
+ const gitState = getCurrentGitState();
103
121
  if (gitState.isRepo) {
104
122
  output += `\n\n🔧 Git: ${gitState.branch} @ ${gitState.commitSha?.substring(0, 7)} (Prism v${SERVER_CONFIG.version})`;
105
123
  }
@@ -23,7 +23,7 @@ import { buildVaultDirectory } from "../utils/vaultExporter.js";
23
23
  * ═══════════════════════════════════════════════════════════════════
24
24
  */
25
25
  import { debugLog } from "../utils/logger.js";
26
- import { getStorage } from "../storage/index.js";
26
+ import { getStorage, activeStorageBackend } from "../storage/index.js";
27
27
  import { toKeywordArray } from "../utils/keywordExtractor.js";
28
28
  import { getLLMProvider } from "../utils/llm/factory.js";
29
29
  import { getCurrentGitState, getGitDrift } from "../utils/git.js";
@@ -524,6 +524,67 @@ export async function sessionLoadContextHandler(args) {
524
524
  const versionNote = version
525
525
  ? `\n\n🔑 Session version: ${version}. Pass expected_version: ${version} when saving handoff.`
526
526
  : "";
527
+ // ─── v9.2.2: Split-Brain Drift Detection ───────────────────
528
+ // When using one storage backend (e.g. SQLite), check if an
529
+ // alternate backend (e.g. Supabase) exists with a different
530
+ // version. This prevents agents from unknowingly acting on
531
+ // stale state from a diverged backend.
532
+ //
533
+ // PERF NOTE: We do NOT construct a full StorageBackend for the
534
+ // alternate check — that would trigger full migrations/schema
535
+ // validation on every load (~200-1000ms). Instead we use
536
+ // lightweight direct queries: REST GET for Supabase, raw SQL
537
+ // for SQLite. This keeps the check under ~100ms.
538
+ let splitBrainWarning = "";
539
+ try {
540
+ const { SUPABASE_CONFIGURED, SUPABASE_URL, SUPABASE_KEY } = await import("../config.js");
541
+ if (activeStorageBackend === "local" && SUPABASE_CONFIGURED && SUPABASE_URL && SUPABASE_KEY) {
542
+ // Lightweight Supabase version check via REST (no full init/migrations)
543
+ const { supabaseGet } = await import("../utils/supabaseApi.js");
544
+ const rows = await supabaseGet("session_handoffs", {
545
+ project: `eq.${project}`,
546
+ select: "version",
547
+ limit: "1",
548
+ });
549
+ const altVersion = Array.isArray(rows) && rows[0] ? rows[0].version : null;
550
+ if (altVersion && altVersion !== version) {
551
+ splitBrainWarning = `\n\n⚠️ **SPLIT-BRAIN DETECTED** (v${version} local vs v${altVersion} cloud)\n` +
552
+ `Your local SQLite state (v${version}) differs from the Supabase cloud state (v${altVersion}). ` +
553
+ `This means another client (e.g. Claude Desktop) has saved state that this environment cannot see. ` +
554
+ `TODOs, summaries, and decisions may be stale. Please reconcile by running:\n` +
555
+ ` \`prism load ${project} --storage supabase\` to see the cloud state.`;
556
+ debugLog(`[session_load_context] SPLIT-BRAIN: local v${version} vs supabase v${altVersion}`);
557
+ }
558
+ }
559
+ else if (activeStorageBackend === "supabase") {
560
+ // Lightweight SQLite version check via direct file query (no full init/migrations)
561
+ const dbPath = nodePath.join(os.homedir(), ".prism-mcp", "data.db");
562
+ if (fs.existsSync(dbPath)) {
563
+ let altClient = null;
564
+ try {
565
+ const { createClient } = await import("@libsql/client");
566
+ altClient = createClient({ url: `file:${dbPath}` });
567
+ const result = await altClient.execute(`SELECT version FROM session_handoffs WHERE project = ? LIMIT 1`, [project]);
568
+ const altVersion = result.rows?.[0]?.version;
569
+ if (altVersion && altVersion !== version) {
570
+ splitBrainWarning = `\n\n⚠️ **SPLIT-BRAIN DETECTED** (v${version} cloud vs v${altVersion} local)\n` +
571
+ `Your Supabase cloud state (v${version}) differs from the local SQLite state (v${altVersion}). ` +
572
+ `This means another client has saved state that this environment cannot see. ` +
573
+ `TODOs, summaries, and decisions may be stale. Please reconcile by running:\n` +
574
+ ` \`prism load ${project} --storage local\` to see the local state.`;
575
+ debugLog(`[session_load_context] SPLIT-BRAIN: supabase v${version} vs local v${altVersion}`);
576
+ }
577
+ }
578
+ finally {
579
+ if (altClient)
580
+ altClient.close();
581
+ }
582
+ }
583
+ }
584
+ }
585
+ catch (err) {
586
+ debugLog(`[session_load_context] Split-brain check failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
587
+ }
527
588
  // ─── Reality Drift Detection (v2.0 Step 5) ───
528
589
  // Check if the developer changed code since the last handoff save.
529
590
  let driftReport = "";
@@ -721,7 +782,7 @@ export async function sessionLoadContextHandler(args) {
721
782
  }
722
783
  }
723
784
  // Build the response object before v4.0 augmentations
724
- let responseText = `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${driftReport}${briefingBlock}${sdmRecallBlock}${greetingBlock}${visualMemoryBlock}${skillBlock}${versionNote}`;
785
+ let responseText = `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${splitBrainWarning}${driftReport}${briefingBlock}${sdmRecallBlock}${greetingBlock}${visualMemoryBlock}${skillBlock}${versionNote}`;
725
786
  // ─── v4.0: Behavioral Warnings Injection ───────────────────
726
787
  // If loadContext returned behavioral_warnings, add them to the
727
788
  // formatted output so the agent sees them prominently.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "9.2.0",
3
+ "version": "9.2.2",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
5
  "description": "The Mind Palace for AI Agents — a true Cognitive Architecture with Hebbian learning (episodic→semantic consolidation), ACT-R spreading activation (multi-hop causal reasoning), uncertainty-aware rejection gates (agents that know when they don't know), adversarial evaluation (anti-sycophancy), fail-closed Dark Factory pipelines, persistent memory (SQLite/Supabase), multi-agent Hivemind, time travel & visual dashboard. Zero-config local mode.",
6
6
  "module": "index.ts",