prism-mcp-server 9.2.3 β 9.2.5
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 +7 -2
- package/dist/storage/index.js +45 -0
- package/dist/storage/reconcile.js +237 -0
- package/dist/storage/sqlite.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -789,8 +789,9 @@ The Generator strips the `console.log`, resubmits, and the next `EVALUATE` retur
|
|
|
789
789
|
|
|
790
790
|
## π What's New
|
|
791
791
|
|
|
792
|
-
> **Current release: v9.2.
|
|
792
|
+
> **Current release: v9.2.4 β Cross-Backend Reconciliation**
|
|
793
793
|
|
|
794
|
+
- π **v9.2.4 β Cross-Backend Reconciliation:** Automatic two-layer sync from Supabase β SQLite on startup. When Claude Desktop writes handoffs and ledger entries to Supabase, Antigravity (local SQLite) now automatically detects stale data and pulls newer handoffs + the 20 most recent ledger entries. 5-second timeout prevents startup freeze. Targeted ID lookups (not full table scans) keep it safe for large databases. 13 tests including malformed JSON resilience, multi-role dedup, and timeout handling.
|
|
794
795
|
- π§ **v9.2.3 β Code Review Hardening:** 10x faster split-brain detection (lightweight direct queries replace full `StorageBackend` construction), variable shadowing fix in CLI, resource leak fix in SQLite alternate client.
|
|
795
796
|
- π¨ **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.
|
|
796
797
|
- π» **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.
|
|
@@ -1220,10 +1221,14 @@ Prism MCP is open-source and free for individual developers. For teams and enter
|
|
|
1220
1221
|
|
|
1221
1222
|
## π¦ Milestones & Roadmap
|
|
1222
1223
|
|
|
1223
|
-
> **Current: v9.
|
|
1224
|
+
> **Current: v9.2.4** β Cross-Backend Reconciliation ([CHANGELOG](CHANGELOG.md))
|
|
1224
1225
|
|
|
1225
1226
|
| Release | Headline |
|
|
1226
1227
|
|---------|----------|
|
|
1228
|
+
| **v9.2.4** | π Cross-Backend Reconciliation β automatic Supabase β SQLite sync on startup, two-layer (handoff + ledger), 5s timeout, 13 tests |
|
|
1229
|
+
| **v9.2.3** | π§ Code Review Hardening β 10x faster split-brain detection, variable shadowing fix, resource leak fix |
|
|
1230
|
+
| **v9.2.2** | π¨ Split-Brain Detection & Prevention β `--storage` flag, drift detection, session loader hardening |
|
|
1231
|
+
| **v9.2.1** | π» CLI Full Feature Parity β text mode enrichments, agent identity, PATH fix |
|
|
1227
1232
|
| **v9.1.0** | π¦ Task Router v2 β file-type routing signal, 6-signal heuristics, local agent streaming buffer |
|
|
1228
1233
|
| **v9.0.5** | π JWKS Auth Security Hardening β audience/issuer validation, JWT failure logging, typed agent identity |
|
|
1229
1234
|
| **v9.0** | π§ Autonomous Cognitive OS β Surprisal Gate, Cognitive Budget, Affect-Tagged Memory |
|
package/dist/storage/index.js
CHANGED
|
@@ -60,6 +60,51 @@ export async function getStorage() {
|
|
|
60
60
|
`Must be "local" or "supabase".`);
|
|
61
61
|
}
|
|
62
62
|
await storageInstance.initialize();
|
|
63
|
+
// βββ v9.2.4: Cross-Backend Handoff Reconciliation ββββββββββββββ
|
|
64
|
+
// When running on local SQLite but Supabase credentials exist,
|
|
65
|
+
// pull any newer handoffs from Supabase into SQLite. This fixes
|
|
66
|
+
// the split-brain where Claude Desktop writes go to Supabase but
|
|
67
|
+
// Antigravity reads from SQLite and sees stale data.
|
|
68
|
+
//
|
|
69
|
+
// IMPORTANT: The supabaseReady check above only resolves dashboard
|
|
70
|
+
// credentials when requestedBackend==="supabase". For reconciliation
|
|
71
|
+
// we need credentials even when backend is "local", so we do a
|
|
72
|
+
// second probe here.
|
|
73
|
+
if (activeStorageBackend === "local") {
|
|
74
|
+
let canReconcile = supabaseReady;
|
|
75
|
+
if (!canReconcile) {
|
|
76
|
+
// Probe dashboard config for Supabase credentials
|
|
77
|
+
const dashUrl = await getSetting("SUPABASE_URL");
|
|
78
|
+
const dashKey = await getSetting("SUPABASE_KEY");
|
|
79
|
+
if (dashUrl && dashKey) {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = new URL(dashUrl);
|
|
82
|
+
if (parsed.protocol === "http:" || parsed.protocol === "https:") {
|
|
83
|
+
canReconcile = true;
|
|
84
|
+
process.env.SUPABASE_URL = dashUrl;
|
|
85
|
+
process.env.SUPABASE_KEY = dashKey;
|
|
86
|
+
debugLog("[Prism Storage] Reconciliation: using Supabase credentials from dashboard config");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// Invalid URL β skip reconciliation
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (canReconcile) {
|
|
95
|
+
try {
|
|
96
|
+
const { reconcileHandoffs } = await import("./reconcile.js");
|
|
97
|
+
const { SqliteStorage } = await import("./sqlite.js");
|
|
98
|
+
const sqliteInstance = storageInstance;
|
|
99
|
+
const getTimestamps = () => sqliteInstance.getHandoffTimestamps();
|
|
100
|
+
await reconcileHandoffs(storageInstance, getTimestamps);
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
// Non-fatal: reconciliation is best-effort
|
|
104
|
+
debugLog(`[Prism Storage] Reconciliation skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
63
108
|
return storageInstance;
|
|
64
109
|
}
|
|
65
110
|
/**
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Backend Handoff & Ledger Reconciliation (v9.2.4)
|
|
3
|
+
*
|
|
4
|
+
* Fixes the split-brain data inconsistency where writes made via
|
|
5
|
+
* Claude Desktop (Supabase) are invisible to Antigravity (local SQLite).
|
|
6
|
+
*
|
|
7
|
+
* SYNCS TWO LAYERS:
|
|
8
|
+
* 1. session_handoffs β latest project state (TODOs, summary, decisions)
|
|
9
|
+
* 2. session_ledger β recent session history (used by standard/deep loads)
|
|
10
|
+
*
|
|
11
|
+
* WHEN THIS RUNS:
|
|
12
|
+
* - Automatically during getStorage() initialization when:
|
|
13
|
+
* 1. The active backend is "local" (SQLite), AND
|
|
14
|
+
* 2. Supabase credentials are available (env or dashboard config)
|
|
15
|
+
*
|
|
16
|
+
* PERFORMANCE:
|
|
17
|
+
* - 2 Supabase REST calls per synced project:
|
|
18
|
+
* - session_handoffs: 1-5 rows (~1KB) β instant
|
|
19
|
+
* - session_ledger: last 20 entries per stale project (~50KB) β fast
|
|
20
|
+
* - Local SQLite: bulk timestamp check + targeted ID lookups + N upserts
|
|
21
|
+
* - Total: ~300-800ms (dominated by network, not DB)
|
|
22
|
+
* - Safe for databases with millions of entries β scoped queries only
|
|
23
|
+
*
|
|
24
|
+
* DESIGN:
|
|
25
|
+
* - Read-only on Supabase (never writes to remote)
|
|
26
|
+
* - Last-writer-wins by updated_at/created_at timestamp
|
|
27
|
+
* - Non-blocking: wrapped in try/catch, errors downgraded to debug log
|
|
28
|
+
* - Idempotent: safe to run on every boot (ledger uses ID dedup)
|
|
29
|
+
* - 5-second timeout on Supabase calls to prevent startup freeze
|
|
30
|
+
*/
|
|
31
|
+
import { supabaseGet } from "../utils/supabaseApi.js";
|
|
32
|
+
import { debugLog } from "../utils/logger.js";
|
|
33
|
+
import { PRISM_USER_ID } from "../config.js";
|
|
34
|
+
/** Timeout for each Supabase REST call (ms). Prevents startup freeze. */
|
|
35
|
+
const RECONCILE_TIMEOUT_MS = 5_000;
|
|
36
|
+
/**
|
|
37
|
+
* Safely parse a JSON array field from Supabase.
|
|
38
|
+
* Handles: arrays (pass-through), JSON strings (parse), garbage (empty array).
|
|
39
|
+
* Never throws.
|
|
40
|
+
*/
|
|
41
|
+
function safeParseArray(val) {
|
|
42
|
+
if (Array.isArray(val))
|
|
43
|
+
return val;
|
|
44
|
+
if (typeof val === "string") {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(val);
|
|
47
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Wrap a promise with a timeout. Rejects with AbortError if exceeded.
|
|
57
|
+
*/
|
|
58
|
+
function withTimeout(promise, ms, label) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const timer = setTimeout(() => reject(new Error(`[Reconcile] Timeout after ${ms}ms: ${label}`)), ms);
|
|
61
|
+
promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); });
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Pull newer handoffs AND recent ledger entries from Supabase into local SQLite.
|
|
66
|
+
*
|
|
67
|
+
* @param localStorage - The initialized SqliteStorage instance
|
|
68
|
+
* @param getLocalTimestamps - Function to bulk-read local handoff timestamps
|
|
69
|
+
* @returns Summary of what was synced
|
|
70
|
+
*/
|
|
71
|
+
export async function reconcileHandoffs(localStorage, getLocalTimestamps) {
|
|
72
|
+
const result = { checked: 0, synced: 0, projects: [], ledgerEntriesSynced: 0 };
|
|
73
|
+
try {
|
|
74
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
75
|
+
// LAYER 1: Handoff Reconciliation (session_handoffs)
|
|
76
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
77
|
+
// Step 1: Fetch all handoffs from Supabase (single REST call, ~1-5 rows)
|
|
78
|
+
// Timeout prevents startup freeze if Supabase is slow/unreachable.
|
|
79
|
+
const remoteHandoffs = await withTimeout(supabaseGet("session_handoffs", {
|
|
80
|
+
user_id: `eq.${PRISM_USER_ID}`,
|
|
81
|
+
select: "*",
|
|
82
|
+
}), RECONCILE_TIMEOUT_MS, "fetch handoffs");
|
|
83
|
+
if (!Array.isArray(remoteHandoffs) || remoteHandoffs.length === 0) {
|
|
84
|
+
debugLog("[Reconcile] No remote handoffs found β nothing to sync");
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
result.checked = remoteHandoffs.length;
|
|
88
|
+
// Step 2: Get all local handoff timestamps in one query (not per-project)
|
|
89
|
+
let localTimestamps;
|
|
90
|
+
if (getLocalTimestamps) {
|
|
91
|
+
localTimestamps = await getLocalTimestamps();
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Fallback: empty map means all remotes will be synced
|
|
95
|
+
localTimestamps = new Map();
|
|
96
|
+
}
|
|
97
|
+
// Step 3: Compare and sync only stale handoffs
|
|
98
|
+
// Use a Set to deduplicate projects with multiple roles (FIX #6)
|
|
99
|
+
const syncedProjectsSet = new Set();
|
|
100
|
+
for (const remote of remoteHandoffs) {
|
|
101
|
+
const project = remote.project;
|
|
102
|
+
const role = remote.role || "global";
|
|
103
|
+
const key = `${project}::${role}`;
|
|
104
|
+
const remoteUpdatedAt = remote.updated_at;
|
|
105
|
+
const localUpdatedAt = localTimestamps.get(key);
|
|
106
|
+
// Sync if: local doesn't exist, or remote is newer
|
|
107
|
+
const needsSync = !localUpdatedAt
|
|
108
|
+
|| (remoteUpdatedAt && new Date(remoteUpdatedAt) > new Date(localUpdatedAt));
|
|
109
|
+
if (needsSync) {
|
|
110
|
+
// FIX #4: safeParseArray prevents JSON.parse crash from aborting all projects
|
|
111
|
+
await localStorage.saveHandoff({
|
|
112
|
+
project,
|
|
113
|
+
user_id: PRISM_USER_ID,
|
|
114
|
+
role,
|
|
115
|
+
last_summary: remote.last_summary ?? null,
|
|
116
|
+
pending_todo: safeParseArray(remote.pending_todo),
|
|
117
|
+
active_decisions: safeParseArray(remote.active_decisions),
|
|
118
|
+
keywords: safeParseArray(remote.keywords),
|
|
119
|
+
key_context: remote.key_context ?? null,
|
|
120
|
+
active_branch: remote.active_branch ?? null,
|
|
121
|
+
metadata: typeof remote.metadata === "object" && remote.metadata !== null ? remote.metadata : {},
|
|
122
|
+
});
|
|
123
|
+
result.synced++;
|
|
124
|
+
result.projects.push(project);
|
|
125
|
+
syncedProjectsSet.add(project); // FIX #6: dedup multi-role projects
|
|
126
|
+
debugLog(`[Reconcile] Synced handoff "${project}" (role: ${role}) β ` +
|
|
127
|
+
`remote: ${remoteUpdatedAt}, local: ${localUpdatedAt || "missing"}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
131
|
+
// LAYER 2: Recent Ledger Reconciliation (session_ledger)
|
|
132
|
+
//
|
|
133
|
+
// For any project whose handoff was stale, also pull recent
|
|
134
|
+
// ledger entries so that standard/deep context loads include
|
|
135
|
+
// session history written via Supabase.
|
|
136
|
+
//
|
|
137
|
+
// We only pull the last 20 entries per project (not the full
|
|
138
|
+
// history) β this covers standard/deep context needs without
|
|
139
|
+
// doing a bulk data migration.
|
|
140
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
141
|
+
if (syncedProjectsSet.size > 0) {
|
|
142
|
+
result.ledgerEntriesSynced = await reconcileLedger(localStorage, [...syncedProjectsSet]);
|
|
143
|
+
}
|
|
144
|
+
if (result.synced > 0) {
|
|
145
|
+
// FIX #7: Use debugLog instead of console.error for non-error output
|
|
146
|
+
debugLog(`[Prism Reconcile] Synced ${result.synced} handoff(s)` +
|
|
147
|
+
`${result.ledgerEntriesSynced > 0 ? ` + ${result.ledgerEntriesSynced} ledger entries` : ""}` +
|
|
148
|
+
` from Supabase β SQLite: ${result.projects.join(", ")}`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
debugLog("[Reconcile] All local data is up-to-date with Supabase");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
// Non-fatal: log and continue. Supabase may be unreachable (offline mode).
|
|
156
|
+
debugLog(`[Reconcile] Failed to reconcile (non-fatal): ` +
|
|
157
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Pull recent ledger entries from Supabase for the given projects.
|
|
163
|
+
*
|
|
164
|
+
* Uses targeted ID lookup for dedup: only queries the specific IDs
|
|
165
|
+
* returned from Supabase, not the entire local ledger. (FIX #2)
|
|
166
|
+
*
|
|
167
|
+
* @param localStorage - The initialized StorageBackend (SQLite)
|
|
168
|
+
* @param projects - Deduplicated list of projects with stale handoffs
|
|
169
|
+
* @returns Number of ledger entries synced
|
|
170
|
+
*/
|
|
171
|
+
async function reconcileLedger(localStorage, projects) {
|
|
172
|
+
let totalSynced = 0;
|
|
173
|
+
for (const project of projects) {
|
|
174
|
+
try {
|
|
175
|
+
// Fetch the 20 most recent ledger entries for this project
|
|
176
|
+
// Timeout prevents hang if Supabase is slow (FIX #3)
|
|
177
|
+
const remoteLedger = await withTimeout(supabaseGet("session_ledger", {
|
|
178
|
+
user_id: `eq.${PRISM_USER_ID}`,
|
|
179
|
+
project: `eq.${project}`,
|
|
180
|
+
archived_at: "is.null",
|
|
181
|
+
deleted_at: "is.null",
|
|
182
|
+
select: "id,project,conversation_id,summary,user_id,role,todos,files_changed,decisions,keywords,event_type,importance,created_at,session_date",
|
|
183
|
+
order: "created_at.desc",
|
|
184
|
+
limit: "20",
|
|
185
|
+
}), RECONCILE_TIMEOUT_MS, `fetch ledger for ${project}`);
|
|
186
|
+
if (!Array.isArray(remoteLedger) || remoteLedger.length === 0) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
// FIX #2: Only query the specific IDs we need to check β not the entire ledger.
|
|
190
|
+
// This is O(remote_count) not O(total_ledger_entries).
|
|
191
|
+
const remoteIds = remoteLedger.map(e => e.id);
|
|
192
|
+
const existingEntries = await localStorage.getLedgerEntries({
|
|
193
|
+
ids: remoteIds,
|
|
194
|
+
select: "id",
|
|
195
|
+
});
|
|
196
|
+
const existingIds = new Set((Array.isArray(existingEntries) ? existingEntries : [])
|
|
197
|
+
.map((e) => e.id));
|
|
198
|
+
// Insert only entries that don't exist locally
|
|
199
|
+
for (const entry of remoteLedger) {
|
|
200
|
+
if (existingIds.has(entry.id)) {
|
|
201
|
+
continue; // Already exists locally
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
await localStorage.saveLedger({
|
|
205
|
+
id: entry.id,
|
|
206
|
+
project: entry.project,
|
|
207
|
+
conversation_id: entry.conversation_id || "reconciled",
|
|
208
|
+
summary: entry.summary,
|
|
209
|
+
user_id: PRISM_USER_ID,
|
|
210
|
+
role: entry.role || "global",
|
|
211
|
+
todos: safeParseArray(entry.todos),
|
|
212
|
+
files_changed: safeParseArray(entry.files_changed),
|
|
213
|
+
decisions: safeParseArray(entry.decisions),
|
|
214
|
+
keywords: safeParseArray(entry.keywords),
|
|
215
|
+
event_type: entry.event_type || "session",
|
|
216
|
+
importance: entry.importance || 0,
|
|
217
|
+
});
|
|
218
|
+
totalSynced++;
|
|
219
|
+
}
|
|
220
|
+
catch (insertErr) {
|
|
221
|
+
// Skip entries that fail (e.g., UNIQUE constraint = already exists)
|
|
222
|
+
const msg = insertErr instanceof Error ? insertErr.message : String(insertErr);
|
|
223
|
+
if (!msg.includes("UNIQUE") && !msg.includes("constraint")) {
|
|
224
|
+
debugLog(`[Reconcile] Failed to insert ledger entry ${entry.id}: ${msg}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
debugLog(`[Reconcile] Ledger sync for "${project}": ${remoteLedger.length} remote, ` +
|
|
229
|
+
`${existingIds.size} already local, ${totalSynced} new`);
|
|
230
|
+
}
|
|
231
|
+
catch (err) {
|
|
232
|
+
debugLog(`[Reconcile] Ledger sync failed for "${project}" (non-fatal): ` +
|
|
233
|
+
`${err instanceof Error ? err.message : String(err)}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return totalSynced;
|
|
237
|
+
}
|
package/dist/storage/sqlite.js
CHANGED
|
@@ -1049,6 +1049,24 @@ export class SqliteStorage {
|
|
|
1049
1049
|
});
|
|
1050
1050
|
debugLog(`[SqliteStorage] Hard-deleted ledger entry ${id}`);
|
|
1051
1051
|
}
|
|
1052
|
+
// βββ v9.2.4: Cross-Backend Reconciliation Helper βββββββββββ
|
|
1053
|
+
//
|
|
1054
|
+
// Returns a Map of "project::role" β "updated_at" for all local handoffs.
|
|
1055
|
+
// Used by reconcileHandoffs() for lightweight timestamp comparison.
|
|
1056
|
+
// Single SELECT on session_handoffs β no joins, no ledger access.
|
|
1057
|
+
async getHandoffTimestamps() {
|
|
1058
|
+
const result = await this.db.execute(`SELECT project, role, updated_at FROM session_handoffs`);
|
|
1059
|
+
const map = new Map();
|
|
1060
|
+
for (const row of result.rows) {
|
|
1061
|
+
const project = row.project;
|
|
1062
|
+
const role = row.role || "global";
|
|
1063
|
+
const updatedAt = row.updated_at;
|
|
1064
|
+
if (updatedAt) {
|
|
1065
|
+
map.set(`${project}::${role}`, updatedAt);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return map;
|
|
1069
|
+
}
|
|
1052
1070
|
// βββ Handoff Operations (OCC) ββββββββββββββββββββββββββββββ
|
|
1053
1071
|
async saveHandoff(handoff, expectedVersion) {
|
|
1054
1072
|
const role = handoff.role || "global"; // v3.0: default to 'global'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.5",
|
|
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",
|