prism-mcp-server 2.3.8 → 2.3.10

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/dist/config.js CHANGED
@@ -22,7 +22,7 @@
22
22
  // multi-tenant Row Level Security (RLS) for production hosting.
23
23
  export const SERVER_CONFIG = {
24
24
  name: "prism-mcp",
25
- version: "2.3.7",
25
+ version: "2.3.9",
26
26
  };
27
27
  // ─── Required: Brave Search API Key ───────────────────────────
28
28
  export const BRAVE_API_KEY = process.env.BRAVE_API_KEY;
@@ -50,10 +50,9 @@ if (!BRAVE_ANSWERS_API_KEY) {
50
50
  export const SUPABASE_URL = process.env.SUPABASE_URL;
51
51
  export const SUPABASE_KEY = process.env.SUPABASE_KEY;
52
52
  export const SESSION_MEMORY_ENABLED = !!(SUPABASE_URL && SUPABASE_KEY);
53
- if (SESSION_MEMORY_ENABLED) {
54
- console.error("Session memory enabled (Supabase configured)");
55
- }
56
- else {
53
+ // Note: debug() is defined at the bottom of this file; these lines
54
+ // execute at import time after the full module is loaded by Node.
55
+ if (!SESSION_MEMORY_ENABLED) {
57
56
  console.error("Info: Session memory disabled (set SUPABASE_URL + SUPABASE_KEY to enable)");
58
57
  }
59
58
  // ─── v2.0: Storage Backend Selection ─────────────────────────
@@ -64,7 +63,7 @@ else {
64
63
  // Set PRISM_STORAGE=local to use SQLite (once implemented).
65
64
  // Set PRISM_STORAGE=supabase to use Supabase REST API (default).
66
65
  export const PRISM_STORAGE = process.env.PRISM_STORAGE || "supabase";
67
- console.error(`Storage backend: ${PRISM_STORAGE}`);
66
+ // Logged at debug level — see debug() at bottom of file
68
67
  // ─── Optional: Multi-Tenant User ID ──────────────────────────
69
68
  // REVIEWER NOTE: When multiple users share the same Supabase instance,
70
69
  // PRISM_USER_ID isolates their data. Each user sets a unique ID in their
@@ -76,9 +75,7 @@ console.error(`Storage backend: ${PRISM_STORAGE}`);
76
75
  // For enterprise: use a stable unique identifier (UUID, email hash, etc.)
77
76
  // For personal use: any unique string works (e.g., "alice", "bob")
78
77
  export const PRISM_USER_ID = process.env.PRISM_USER_ID || "default";
79
- if (PRISM_USER_ID !== "default") {
80
- console.error(`Multi-tenant mode: user_id="${PRISM_USER_ID}"`);
81
- }
78
+ // Multi-tenant info logged at debug level in startServer()
82
79
  // ─── v2.1: Auto-Capture Feature ─────────────────────────────
83
80
  // REVIEWER NOTE: Automatically captures HTML snapshots of local dev servers
84
81
  // when handoffs are saved. Prevents UI context loss between sessions.
@@ -88,6 +85,13 @@ export const PRISM_CAPTURE_PORTS = (process.env.PRISM_CAPTURE_PORTS || "3000,300
88
85
  .split(",")
89
86
  .map(p => parseInt(p.trim(), 10))
90
87
  .filter(p => !isNaN(p));
88
+ // ─── v2.3: Debug Logging ──────────────────────────────────────
89
+ // Optionally enable verbose output (stderr) for Prism initialization,
90
+ // memory indexing, and background tasks.
91
+ export const PRISM_DEBUG_LOGGING = process.env.PRISM_DEBUG_LOGGING === "true";
91
92
  if (PRISM_AUTO_CAPTURE) {
92
- console.error(`[AutoCapture] Enabled for ports: ${PRISM_CAPTURE_PORTS.join(", ")}`);
93
+ // Use console.error instead of debugLog here to prevent circular dependency
94
+ if (PRISM_DEBUG_LOGGING) {
95
+ console.error(`[AutoCapture] Enabled for ports: ${PRISM_CAPTURE_PORTS.join(", ")}`);
96
+ }
93
97
  }
package/dist/server.js CHANGED
@@ -147,7 +147,6 @@ const activeSubscriptions = new Set();
147
147
  export function notifyResourceUpdate(project, server) {
148
148
  const uri = `memory://${project}/handoff`;
149
149
  if (activeSubscriptions.has(uri)) {
150
- console.error(`[resource-subscription] Notifying update for ${uri}`);
151
150
  server.notification({
152
151
  method: "notifications/resources/updated",
153
152
  params: { uri },
@@ -165,8 +164,6 @@ export function notifyResourceUpdate(project, server) {
165
164
  * with subscribe support for live refresh
166
165
  */
167
166
  export function createServer() {
168
- console.error(`Creating Prism MCP server v${SERVER_CONFIG.version}`);
169
- console.error(`Registering ${ALL_TOOLS.length} tools (${BASE_TOOLS.length} base + ${SESSION_MEMORY_ENABLED ? SESSION_MEMORY_TOOLS.length : 0} session memory)`);
170
167
  const server = new Server({
171
168
  name: SERVER_CONFIG.name,
172
169
  version: SERVER_CONFIG.version,
@@ -196,7 +193,6 @@ export function createServer() {
196
193
  // REVIEWER NOTE: The initialize handler is unchanged from v0.3.0
197
194
  // except that it now reports the expanded capabilities.
198
195
  server.setRequestHandler(InitializeRequestSchema, async (request) => {
199
- console.error(`Client connected: ${request.params.clientInfo?.name || 'unknown'}`);
200
196
  return {
201
197
  protocolVersion: request.params.protocolVersion,
202
198
  serverInfo: {
@@ -214,7 +210,6 @@ export function createServer() {
214
210
  });
215
211
  // ── Handler: List Tools ──
216
212
  server.setRequestHandler(ListToolsRequestSchema, async () => {
217
- console.error("Received list tools request");
218
213
  return {
219
214
  tools: ALL_TOOLS,
220
215
  };
@@ -271,7 +266,6 @@ export function createServer() {
271
266
  }
272
267
  const project = promptArgs?.project || "default";
273
268
  const level = promptArgs?.level || "standard";
274
- console.error(`[prompt:resume_session] Loading ${level} context for "${project}"`);
275
269
  // v2.3.6 FIX: Use storage abstraction instead of direct supabaseRpc
276
270
  const storage = await getStorage();
277
271
  const result = await storage.loadContext(project, level, PRISM_USER_ID);
@@ -365,7 +359,6 @@ export function createServer() {
365
359
  throw new Error(`Unknown resource URI: ${uri}. Expected format: memory://{project}/handoff`);
366
360
  }
367
361
  const project = decodeURIComponent(match[1]);
368
- console.error(`[resource:read] Fetching handoff for "${project}"`);
369
362
  try {
370
363
  // v2.3.6 FIX: Use storage abstraction instead of direct supabaseRpc
371
364
  const storage = await getStorage();
@@ -404,13 +397,11 @@ export function createServer() {
404
397
  // for projects the client hasn't attached.
405
398
  server.setRequestHandler(SubscribeRequestSchema, async (request) => {
406
399
  const uri = request.params.uri;
407
- console.error(`[resource-subscription] Client subscribed to ${uri}`);
408
400
  activeSubscriptions.add(uri);
409
401
  return {};
410
402
  });
411
403
  server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
412
404
  const uri = request.params.uri;
413
- console.error(`[resource-subscription] Client unsubscribed from ${uri}`);
414
405
  activeSubscriptions.delete(uri);
415
406
  return {};
416
407
  });
@@ -422,13 +413,11 @@ export function createServer() {
422
413
  // The server reference is passed to sessionSaveHandoffHandler so it
423
414
  // can trigger resource update notifications on successful saves.
424
415
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
425
- console.error(`Tool call: ${request.params.name}`);
426
416
  try {
427
417
  const { name, arguments: args } = request.params;
428
418
  if (!args) {
429
419
  throw new Error("No arguments provided");
430
420
  }
431
- console.error(`Processing ${name} with arguments: ${JSON.stringify(args)}`);
432
421
  switch (name) {
433
422
  // ── Search & Analysis Tools (always available) ──
434
423
  case "brave_web_search":
@@ -595,11 +584,8 @@ export function createSandboxServer() {
595
584
  * responses to stdout. Log messages go to stderr.
596
585
  */
597
586
  export async function startServer() {
598
- console.error("Initializing Prism MCP server...");
599
587
  const server = createServer();
600
- console.error("Creating stdio transport...");
601
588
  const transport = new StdioServerTransport();
602
- console.error("Connecting server to transport...");
603
589
  await server.connect(transport);
604
590
  // ─── v2.0 Step 6: Initialize SyncBus (Telepathy) ───
605
591
  if (SESSION_MEMORY_ENABLED) {
@@ -607,8 +593,6 @@ export async function startServer() {
607
593
  const syncBus = await getSyncBus();
608
594
  await syncBus.startListening();
609
595
  syncBus.on("update", (event) => {
610
- console.error(`[Telepathy] Memory for project '${event.project}' ` +
611
- `updated to v${event.version} by another agent!`);
612
596
  // Send an MCP logging notification to the IDE
613
597
  try {
614
598
  server.sendLoggingMessage({
@@ -622,7 +606,6 @@ export async function startServer() {
622
606
  console.error(`[Telepathy] Failed to send notification: ${err}`);
623
607
  }
624
608
  });
625
- console.error("[Telepathy] SyncBus active — multi-client sync enabled");
626
609
  }
627
610
  catch (err) {
628
611
  console.error(`[Telepathy] SyncBus init failed (non-fatal): ${err}`);
@@ -632,7 +615,6 @@ export async function startServer() {
632
615
  startDashboardServer().catch(err => {
633
616
  console.error(`[Dashboard] Mind Palace startup failed (non-fatal): ${err}`);
634
617
  });
635
- console.error(`Prism MCP Server v${SERVER_CONFIG.version} running on stdio`);
636
618
  // Keep the process alive — without this, Node.js would exit
637
619
  // because there are no active event loop handles after the
638
620
  // synchronous setup completes.
@@ -10,6 +10,7 @@
10
10
  * // Pass `storage` to all session memory handlers
11
11
  */
12
12
  import { PRISM_STORAGE } from "../config.js";
13
+ import { debugLog } from "../utils/logger.js";
13
14
  import { SupabaseStorage } from "./supabase.js";
14
15
  let storageInstance = null;
15
16
  /**
@@ -24,7 +25,7 @@ let storageInstance = null;
24
25
  export async function getStorage() {
25
26
  if (storageInstance)
26
27
  return storageInstance;
27
- console.error(`[Prism Storage] Initializing backend: ${PRISM_STORAGE}`);
28
+ debugLog(`[Prism Storage] Initializing backend: ${PRISM_STORAGE}`);
28
29
  if (PRISM_STORAGE === "local") {
29
30
  const { SqliteStorage } = await import("./sqlite.js");
30
31
  storageInstance = new SqliteStorage();
@@ -20,6 +20,7 @@ import * as fs from "fs";
20
20
  import * as path from "path";
21
21
  import * as os from "os";
22
22
  import { randomUUID } from "crypto";
23
+ import { debugLog } from "../utils/logger.js";
23
24
  export class SqliteStorage {
24
25
  db;
25
26
  dbPath;
@@ -38,11 +39,11 @@ export class SqliteStorage {
38
39
  await this.db.execute("PRAGMA journal_mode=WAL");
39
40
  // Run all migrations
40
41
  await this.runMigrations();
41
- console.error(`[SqliteStorage] Initialized at ${this.dbPath}`);
42
+ debugLog(`[SqliteStorage] Initialized at ${this.dbPath}`);
42
43
  }
43
44
  async close() {
44
45
  this.db.close();
45
- console.error("[SqliteStorage] Closed");
46
+ debugLog("[SqliteStorage] Closed");
46
47
  }
47
48
  // ─── Migrations ────────────────────────────────────────────
48
49
  async runMigrations() {
@@ -13,17 +13,18 @@
13
13
  * ═══════════════════════════════════════════════════════════════════
14
14
  */
15
15
  import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
16
+ import { debugLog } from "../utils/logger.js";
16
17
  export class SupabaseStorage {
17
18
  // ─── Lifecycle ─────────────────────────────────────────────
18
19
  async initialize() {
19
20
  // Supabase is always ready — connection is stateless (REST API).
20
21
  // The SUPABASE_URL and SUPABASE_KEY are validated at import time
21
22
  // by supabaseApi.ts's guard clause.
22
- console.error("[SupabaseStorage] Initialized (REST API, stateless)");
23
+ debugLog("[SupabaseStorage] Initialized (REST API, stateless)");
23
24
  }
24
25
  async close() {
25
26
  // No-op for Supabase — connections are stateless.
26
- console.error("[SupabaseStorage] Closed (no-op for REST)");
27
+ debugLog("[SupabaseStorage] Closed (no-op for REST)");
27
28
  }
28
29
  // ─── Ledger Operations ─────────────────────────────────────
29
30
  async saveLedger(entry) {
@@ -7,6 +7,7 @@
7
7
  * Singleton pattern — only one bus per process (prevents duplicate watchers).
8
8
  */
9
9
  import { PRISM_STORAGE } from "../config.js";
10
+ import { debugLog } from "../utils/logger.js";
10
11
  let _bus = null;
11
12
  export async function getSyncBus() {
12
13
  if (_bus)
@@ -28,6 +29,6 @@ export async function getSyncBus() {
28
29
  _bus = new SupabaseSyncBus(url, key);
29
30
  }
30
31
  }
31
- console.error(`[SyncBus] Initialized: ${_bus.constructor.name} (client=${_bus.clientId.substring(0, 8)})`);
32
+ debugLog(`[SyncBus] Initialized: ${_bus.constructor.name} (client=${_bus.clientId.substring(0, 8)})`);
32
33
  return _bus;
33
34
  }
@@ -29,6 +29,7 @@ import * as fs from "fs";
29
29
  import * as path from "path";
30
30
  import * as os from "os";
31
31
  import { SyncBus } from "./index.js";
32
+ import { debugLog } from "../utils/logger.js";
32
33
  export class SqliteSyncBus extends SyncBus {
33
34
  lockFilePath;
34
35
  watching = false;
@@ -53,7 +54,7 @@ export class SqliteSyncBus extends SyncBus {
53
54
  };
54
55
  // Atomic write — content is < 200 bytes, well within single-page write
55
56
  fs.writeFileSync(this.lockFilePath, JSON.stringify(payload), "utf8");
56
- console.error(`[SyncBus] Broadcast: project=${project}, version=${version}, ` +
57
+ debugLog(`[SyncBus] Broadcast: project=${project}, version=${version}, ` +
57
58
  `client=${this.clientId.substring(0, 8)}`);
58
59
  }
59
60
  async startListening() {
@@ -69,7 +70,7 @@ export class SqliteSyncBus extends SyncBus {
69
70
  const event = JSON.parse(data);
70
71
  // Only emit if it came from a DIFFERENT Prism MCP instance
71
72
  if (event.client_id && event.client_id !== this.clientId) {
72
- console.error(`[SyncBus] Received update from client ${event.client_id.substring(0, 8)}: ` +
73
+ debugLog(`[SyncBus] Received update from client ${event.client_id.substring(0, 8)}: ` +
73
74
  `project=${event.project}, version=${event.version}`);
74
75
  this.emit("update", event);
75
76
  }
@@ -79,7 +80,7 @@ export class SqliteSyncBus extends SyncBus {
79
80
  }
80
81
  }
81
82
  });
82
- console.error(`[SyncBus] Listening for updates on ${this.lockFilePath} ` +
83
+ debugLog(`[SyncBus] Listening for updates on ${this.lockFilePath} ` +
83
84
  `(client=${this.clientId.substring(0, 8)})`);
84
85
  }
85
86
  async stopListening() {
@@ -87,6 +88,6 @@ export class SqliteSyncBus extends SyncBus {
87
88
  return;
88
89
  this.watching = false;
89
90
  fs.unwatchFile(this.lockFilePath);
90
- console.error("[SyncBus] Stopped listening");
91
+ debugLog("[SyncBus] Stopped listening");
91
92
  }
92
93
  }
@@ -17,6 +17,7 @@
17
17
  * ═══════════════════════════════════════════════════════════════════
18
18
  */
19
19
  import { SyncBus } from "./index.js";
20
+ import { debugLog } from "../utils/logger.js";
20
21
  export class SupabaseSyncBus extends SyncBus {
21
22
  supabaseUrl;
22
23
  supabaseKey;
@@ -47,7 +48,7 @@ export class SupabaseSyncBus extends SyncBus {
47
48
  }, (payload) => {
48
49
  const newRow = payload.new;
49
50
  if (newRow) {
50
- console.error(`[SyncBus:Supabase] Received update: project=${newRow.project}, version=${newRow.version}`);
51
+ debugLog(`[SyncBus:Supabase] Received update: project=${newRow.project}, version=${newRow.version}`);
51
52
  this.emit("update", {
52
53
  project: newRow.project,
53
54
  version: newRow.version,
@@ -57,9 +58,9 @@ export class SupabaseSyncBus extends SyncBus {
57
58
  }
58
59
  })
59
60
  .subscribe((status) => {
60
- console.error(`[SyncBus:Supabase] Channel status: ${status}`);
61
+ debugLog(`[SyncBus:Supabase] Channel status: ${status}`);
61
62
  });
62
- console.error(`[SyncBus:Supabase] Listening for realtime updates ` +
63
+ debugLog(`[SyncBus:Supabase] Listening for realtime updates ` +
63
64
  `(client=${this.clientId.substring(0, 8)})`);
64
65
  }
65
66
  catch (err) {
@@ -72,7 +73,7 @@ export class SupabaseSyncBus extends SyncBus {
72
73
  if (this.channel && this.supabase) {
73
74
  try {
74
75
  await this.supabase.removeChannel(this.channel);
75
- console.error("[SyncBus:Supabase] Stopped listening");
76
+ debugLog("[SyncBus:Supabase] Stopped listening");
76
77
  }
77
78
  catch {
78
79
  // Ignore cleanup errors
@@ -9,6 +9,7 @@
9
9
  import { getStorage } from "../storage/index.js";
10
10
  import { GOOGLE_API_KEY, PRISM_USER_ID } from "../config.js";
11
11
  import { GoogleGenerativeAI } from "@google/generative-ai";
12
+ import { debugLog } from "../utils/logger.js";
12
13
  // ─── Constants ────────────────────────────────────────────────
13
14
  const COMPACTION_CHUNK_SIZE = 10;
14
15
  const MAX_ENTRIES_PER_RUN = 100;
@@ -44,7 +45,7 @@ export async function compactLedgerHandler(args) {
44
45
  throw new Error("Invalid arguments for session_compact_ledger");
45
46
  }
46
47
  const { project, threshold = 50, keep_recent = 10, dry_run = false, } = args;
47
- console.error(`[compact_ledger] ${dry_run ? "DRY RUN: " : ""}` +
48
+ debugLog(`[compact_ledger] ${dry_run ? "DRY RUN: " : ""}` +
48
49
  `project=${project || "auto-detect"}, threshold=${threshold}, keep_recent=${keep_recent}`);
49
50
  const storage = await getStorage();
50
51
  // Step 1: Find candidates
@@ -102,7 +103,7 @@ export async function compactLedgerHandler(args) {
102
103
  for (const candidate of candidates) {
103
104
  const proj = candidate.project;
104
105
  const toCompact = Math.min(candidate.to_compact, MAX_ENTRIES_PER_RUN);
105
- console.error(`[compact_ledger] Compacting ${toCompact} entries for "${proj}"`);
106
+ debugLog(`[compact_ledger] Compacting ${toCompact} entries for "${proj}"`);
106
107
  // Fetch oldest entries (the ones to be rolled up)
107
108
  const oldEntries = await storage.getLedgerEntries({
108
109
  project: `eq.${proj}`,
@@ -24,6 +24,7 @@ import { analyzePaperWithGemini } from "../utils/googleAi.js";
24
24
  import { isBraveWebSearchArgs, isBraveLocalSearchArgs, isBraveAnswersArgs, isGeminiResearchPaperAnalysisArgs, isBraveWebSearchCodeModeArgs, isBraveLocalSearchCodeModeArgs, isCodeModeTransformArgs } from "./definitions.js";
25
25
  import { runInSandbox } from "../utils/executor.js";
26
26
  import { CODE_MODE_TEMPLATES, getTemplateNames } from "../templates/codeMode.js";
27
+ import { debugLog } from "../utils/logger.js";
27
28
  // ─── Simple Search Handlers ──────────────────────────────────
28
29
  /** Performs a standard web search and returns formatted text results. */
29
30
  export async function webSearchHandler(args) {
@@ -56,11 +57,11 @@ export async function braveWebSearchCodeModeHandler(args) {
56
57
  };
57
58
  }
58
59
  // 1. Fetch raw data
59
- console.error(`Fetching web search for code mode: "${query}"`);
60
+ debugLog(`Fetching web search for code mode: "${query}"`);
60
61
  const rawDataStr = await performWebSearchRaw(query, count, offset);
61
62
  const beforeSizeKB = (Buffer.byteLength(rawDataStr, 'utf8') / 1024).toFixed(1);
62
63
  // 2. Run code mode sandbox
63
- console.error(`Executing code mode sandbox...`);
64
+ debugLog(`Executing code mode sandbox...`);
64
65
  const { stdout, error, executionTimeMs } = await runInSandbox(rawDataStr, code);
65
66
  if (error) {
66
67
  return {
@@ -93,10 +94,10 @@ export async function braveLocalSearchCodeModeHandler(args) {
93
94
  isError: true,
94
95
  };
95
96
  }
96
- console.error(`Fetching local search for code mode: "${query}"`);
97
+ debugLog(`Fetching local search for code mode: "${query}"`);
97
98
  const rawDataStr = await performLocalSearchRaw(query, count);
98
99
  const beforeSizeKB = (Buffer.byteLength(rawDataStr, "utf8") / 1024).toFixed(1);
99
- console.error("Executing local search code mode sandbox...");
100
+ debugLog("Executing local search code mode sandbox...");
100
101
  const { stdout, error, executionTimeMs } = await runInSandbox(rawDataStr, code);
101
102
  if (error) {
102
103
  return {
@@ -151,7 +152,7 @@ export async function codeModeTransformHandler(args) {
151
152
  };
152
153
  }
153
154
  const beforeSizeKB = (Buffer.byteLength(data, "utf8") / 1024).toFixed(1);
154
- console.error(`[code_mode_transform] source=${source_tool}, template=${templateUsed}, input=${beforeSizeKB}KB`);
155
+ debugLog(`[code_mode_transform] source=${source_tool}, template=${templateUsed}, input=${beforeSizeKB}KB`);
155
156
  const { stdout, error, executionTimeMs } = await runInSandbox(data, scriptToRun);
156
157
  if (error) {
157
158
  return {
@@ -215,7 +216,7 @@ export async function researchPaperAnalysisHandler(args) {
215
216
  };
216
217
  }
217
218
  try {
218
- console.error(`Analyzing research paper with Gemini (${analysisType} analysis)...`);
219
+ debugLog(`Analyzing research paper with Gemini (${analysisType} analysis)...`);
219
220
  const analysis = await analyzePaperWithGemini(paperContent, analysisType, additionalContext);
220
221
  return {
221
222
  content: [{ type: "text", text: analysis }],
@@ -15,6 +15,7 @@
15
15
  * without any code changes.
16
16
  * ═══════════════════════════════════════════════════════════════════
17
17
  */
18
+ import { debugLog } from "../utils/logger.js";
18
19
  import { getStorage } from "../storage/index.js";
19
20
  import { toKeywordArray } from "../utils/keywordExtractor.js";
20
21
  import { generateEmbedding } from "../utils/embeddingApi.js";
@@ -39,11 +40,11 @@ export async function sessionSaveLedgerHandler(args) {
39
40
  }
40
41
  const { project, conversation_id, summary, todos, files_changed, decisions } = args;
41
42
  const storage = await getStorage();
42
- console.error(`[session_save_ledger] Saving ledger entry for project="${project}"`);
43
+ debugLog(`[session_save_ledger] Saving ledger entry for project="${project}"`);
43
44
  // Auto-extract keywords from summary + decisions for knowledge accumulation
44
45
  const combinedText = [summary, ...(decisions || [])].join(" ");
45
46
  const keywords = toKeywordArray(combinedText);
46
- console.error(`[session_save_ledger] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
47
+ debugLog(`[session_save_ledger] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
47
48
  // Save via storage backend
48
49
  const result = await storage.saveLedger({
49
50
  project,
@@ -66,7 +67,7 @@ export async function sessionSaveLedgerHandler(args) {
66
67
  await storage.patchLedger(entryId, {
67
68
  embedding: JSON.stringify(embedding),
68
69
  });
69
- console.error(`[session_save_ledger] Embedding saved for entry ${entryId}`);
70
+ debugLog(`[session_save_ledger] Embedding saved for entry ${entryId}`);
70
71
  })
71
72
  .catch((err) => {
72
73
  console.error(`[session_save_ledger] Embedding generation failed (non-fatal): ${err.message}`);
@@ -97,13 +98,13 @@ export async function sessionSaveHandoffHandler(args, server) {
97
98
  }
98
99
  const { project, expected_version, open_todos, active_branch, last_summary, key_context, } = args;
99
100
  const storage = await getStorage();
100
- console.error(`[session_save_handoff] Saving handoff for project="${project}" ` +
101
+ debugLog(`[session_save_handoff] Saving handoff for project="${project}" ` +
101
102
  `(expected_version=${expected_version ?? "none"})`);
102
103
  // Auto-extract keywords from summary + context for knowledge accumulation
103
104
  const combinedText = [last_summary || "", key_context || ""].filter(Boolean).join(" ");
104
105
  const keywords = combinedText ? toKeywordArray(combinedText) : undefined;
105
106
  if (keywords) {
106
- console.error(`[session_save_handoff] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
107
+ debugLog(`[session_save_handoff] Extracted ${keywords.length} keywords: ${keywords.slice(0, 5).join(", ")}...`);
107
108
  }
108
109
  // Auto-capture Git state for Reality Drift Detection (v2.0 Step 5)
109
110
  const gitState = getCurrentGitState();
@@ -111,7 +112,7 @@ export async function sessionSaveHandoffHandler(args, server) {
111
112
  if (gitState.isRepo) {
112
113
  metadata.git_branch = gitState.branch;
113
114
  metadata.last_commit_sha = gitState.commitSha;
114
- console.error(`[session_save_handoff] Git state captured: branch=${gitState.branch}, sha=${gitState.commitSha?.substring(0, 8)}`);
115
+ debugLog(`[session_save_handoff] Git state captured: branch=${gitState.branch}, sha=${gitState.commitSha?.substring(0, 8)}`);
115
116
  }
116
117
  // Save via storage backend (OCC-aware)
117
118
  const data = await storage.saveHandoff({
@@ -127,7 +128,7 @@ export async function sessionSaveHandoffHandler(args, server) {
127
128
  }, expected_version ?? null);
128
129
  // ─── Handle version conflict ───
129
130
  if (data.status === "conflict") {
130
- console.error(`[session_save_handoff] VERSION CONFLICT for "${project}": ` +
131
+ debugLog(`[session_save_handoff] VERSION CONFLICT for "${project}": ` +
131
132
  `expected=${expected_version}, current=${data.current_version}`);
132
133
  return {
133
134
  content: [{
@@ -213,7 +214,7 @@ export async function sessionSaveHandoffHandler(args, server) {
213
214
  key_context: ctx.key_context ?? null,
214
215
  active_branch: ctx.active_branch ?? null,
215
216
  }, newVersion);
216
- console.error(`[AutoCapture] HTML snapshot indexed in visual memory for "${project}"`);
217
+ debugLog(`[AutoCapture] HTML snapshot indexed in visual memory for "${project}"`);
217
218
  }
218
219
  }
219
220
  catch (err) {
@@ -244,14 +245,14 @@ export async function sessionSaveHandoffHandler(args, server) {
244
245
  const oldKeyContext = oldState?.key_context || ""; // extract old key_context
245
246
  // Step 2: Skip merge if old context is empty (nothing to merge with)
246
247
  if (!oldKeyContext || oldKeyContext.trim().length === 0) {
247
- console.error("[FactMerger] No old context to merge — skipping");
248
+ debugLog("[FactMerger] No old context to merge — skipping");
248
249
  return; // first handoff for this project, no merge needed
249
250
  }
250
251
  // Step 3: Call Gemini to intelligently merge old + new context
251
252
  const mergedContext = await consolidateFacts(oldKeyContext, key_context);
252
253
  // Step 4: Skip patch if merged result is same as current key_context
253
254
  if (mergedContext === key_context) {
254
- console.error("[FactMerger] No changes after merge — skipping patch");
255
+ debugLog("[FactMerger] No changes after merge — skipping patch");
255
256
  return; // Gemini determined no contradictions existed
256
257
  }
257
258
  // Step 5: Silently patch the database with the merged context
@@ -268,14 +269,14 @@ export async function sessionSaveHandoffHandler(args, server) {
268
269
  active_branch: active_branch ?? null, // preserve existing branch
269
270
  metadata: {}, // no metadata changes
270
271
  }, newVersion); // use current version for OCC
271
- console.error("[FactMerger] Context merged and patched for \"" + project + "\"");
272
+ debugLog("[FactMerger] Context merged and patched for \"" + project + "\"");
272
273
  }
273
274
  catch (err) {
274
275
  // OCC conflict = user saved again while merge was running (expected)
275
276
  const errMsg = err instanceof Error ? err.message : String(err);
276
277
  if (errMsg.includes("conflict") || errMsg.includes("version")) {
277
278
  // This is GOOD behavior — user's active input takes precedence
278
- console.error("[FactMerger] Merge skipped due to active session (OCC conflict)");
279
+ debugLog("[FactMerger] Merge skipped due to active session (OCC conflict)");
279
280
  }
280
281
  else {
281
282
  // Unexpected error — log but don't crash
@@ -319,7 +320,7 @@ export async function sessionLoadContextHandler(args) {
319
320
  isError: true,
320
321
  };
321
322
  }
322
- console.error(`[session_load_context] Loading ${level} context for project="${project}"`);
323
+ debugLog(`[session_load_context] Loading ${level} context for project="${project}"`);
323
324
  const storage = await getStorage();
324
325
  const data = await storage.loadContext(project, level, PRISM_USER_ID);
325
326
  if (!data) {
@@ -348,7 +349,7 @@ export async function sessionLoadContextHandler(args) {
348
349
  driftReport = `\n\n⚠️ **CONTEXT SHIFT:** This memory was saved on branch ` +
349
350
  `\`${meta.git_branch}\`, but you are currently on branch \`${currentGit.branch}\`. ` +
350
351
  `Code may have diverged — review carefully before making changes.`;
351
- console.error(`[session_load_context] Context shift detected: ${meta.git_branch} → ${currentGit.branch}`);
352
+ debugLog(`[session_load_context] Context shift detected: ${meta.git_branch} → ${currentGit.branch}`);
352
353
  }
353
354
  else if (currentGit.commitSha !== meta.last_commit_sha) {
354
355
  // Same branch, different commits — calculate drift
@@ -358,11 +359,11 @@ export async function sessionLoadContextHandler(args) {
358
359
  `Since this memory was saved (commit ${meta.last_commit_sha.substring(0, 8)}), ` +
359
360
  `the following files were modified outside of agent sessions:\n\`\`\`\n${changes}\n\`\`\`\n` +
360
361
  `Please review these files if they overlap with your current task.`;
361
- console.error(`[session_load_context] Reality drift detected! ${changes.split("\n").length} files changed`);
362
+ debugLog(`[session_load_context] Reality drift detected! ${changes.split("\n").length} files changed`);
362
363
  }
363
364
  }
364
365
  else {
365
- console.error(`[session_load_context] No drift — repo matches saved state`);
366
+ debugLog(`[session_load_context] No drift — repo matches saved state`);
366
367
  }
367
368
  }
368
369
  }
@@ -415,7 +416,7 @@ export async function sessionLoadContextHandler(args) {
415
416
  if (currentVersion) {
416
417
  storage.saveHandoff(handoffUpdate, currentVersion).catch(err => console.error(`[Morning Briefing] Cache save failed (non-fatal): ${err}`));
417
418
  }
418
- console.error(`[session_load_context] Morning Briefing generated for "${project}"`);
419
+ debugLog(`[session_load_context] Morning Briefing generated for "${project}"`);
419
420
  }
420
421
  catch (err) {
421
422
  console.error(`[session_load_context] Morning Briefing failed (non-fatal): ${err}`);
@@ -424,7 +425,7 @@ export async function sessionLoadContextHandler(args) {
424
425
  else if (meta?.morning_briefing) {
425
426
  // Show the cached briefing (generated within last 4 hours)
426
427
  briefingBlock = `\n\n[🌅 MORNING BRIEFING]\n${meta.morning_briefing}`;
427
- console.error(`[session_load_context] Showing cached Morning Briefing for "${project}"`);
428
+ debugLog(`[session_load_context] Showing cached Morning Briefing for "${project}"`);
428
429
  }
429
430
  // ─── Visual Memory Index (v2.0 Step 9) ───
430
431
  // Show lightweight index of saved images — never loads actual image data
@@ -436,10 +437,33 @@ export async function sessionLoadContextHandler(args) {
436
437
  visualMemoryBlock += `- [ID: ${v.id}] ${v.description} (${v.timestamp?.split("T")[0] || "unknown"})\n`;
437
438
  });
438
439
  }
440
+ const d = data;
441
+ let formattedContext = ``;
442
+ if (d.last_summary)
443
+ formattedContext += `📝 Last Summary: ${d.last_summary}\n`;
444
+ if (d.active_branch)
445
+ formattedContext += `🌿 Active Branch: ${d.active_branch}\n`;
446
+ if (d.key_context)
447
+ formattedContext += `💡 Key Context: ${d.key_context}\n`;
448
+ if (d.pending_todo?.length) {
449
+ formattedContext += `\n✅ Open TODOs:\n` + d.pending_todo.map((t) => ` - ${t}`).join("\n") + `\n`;
450
+ }
451
+ if (d.active_decisions?.length) {
452
+ formattedContext += `\n⚖️ Active Decisions:\n` + d.active_decisions.map((dec) => ` - ${dec}`).join("\n") + `\n`;
453
+ }
454
+ if (d.keywords?.length) {
455
+ formattedContext += `\n🔑 Keywords: ${d.keywords.join(", ")}\n`;
456
+ }
457
+ if (d.recent_sessions?.length) {
458
+ formattedContext += `\n⏳ Recent Sessions:\n` + d.recent_sessions.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
459
+ }
460
+ if (d.session_history?.length) {
461
+ 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`;
462
+ }
439
463
  return {
440
464
  content: [{
441
465
  type: "text",
442
- text: `📋 Session context for "${project}" (${level}):\n\n${JSON.stringify(data, null, 2)}${driftReport}${briefingBlock}${visualMemoryBlock}${versionNote}`,
466
+ text: `📋 Session context for "${project}" (${level}):\n\n${formattedContext.trim()}${driftReport}${briefingBlock}${visualMemoryBlock}${versionNote}`,
443
467
  }],
444
468
  isError: false,
445
469
  };
@@ -453,7 +477,7 @@ export async function knowledgeSearchHandler(args) {
453
477
  throw new Error("Invalid arguments for knowledge_search");
454
478
  }
455
479
  const { project, query, category, limit = 10 } = args;
456
- console.error(`[knowledge_search] Searching: project=${project || "all"}, query="${query || ""}", category=${category || "any"}, limit=${limit}`);
480
+ debugLog(`[knowledge_search] Searching: project=${project || "all"}, query="${query || ""}", category=${category || "any"}, limit=${limit}`);
457
481
  const searchKeywords = query ? toKeywordArray(query) : [];
458
482
  const storage = await getStorage();
459
483
  const data = await storage.searchKnowledge({
@@ -506,7 +530,7 @@ export async function knowledgeForgetHandler(args) {
506
530
  isError: true,
507
531
  };
508
532
  }
509
- console.error(`[knowledge_forget] ${dry_run ? "DRY RUN: " : ""}Forgetting: ` +
533
+ debugLog(`[knowledge_forget] ${dry_run ? "DRY RUN: " : ""}Forgetting: ` +
510
534
  `project=${project || "ALL"}, category=${category || "any"}, ` +
511
535
  `older_than=${older_than_days || "any"}d, clear_handoff=${clear_handoff}`);
512
536
  const storage = await getStorage();
@@ -564,7 +588,7 @@ export async function sessionSearchMemoryHandler(args) {
564
588
  throw new Error("Invalid arguments for session_search_memory");
565
589
  }
566
590
  const { query, project, limit = 5, similarity_threshold = 0.7, } = args;
567
- console.error(`[session_search_memory] Semantic search: query="${query}", ` +
591
+ debugLog(`[session_search_memory] Semantic search: query="${query}", ` +
568
592
  `project=${project || "all"}, limit=${limit}, threshold=${similarity_threshold}`);
569
593
  // Step 1: Generate embedding for the search query
570
594
  if (!GOOGLE_API_KEY) {
@@ -672,7 +696,7 @@ export async function backfillEmbeddingsHandler(args) {
672
696
  }
673
697
  const { project, limit = 20, dry_run = false } = args;
674
698
  const safeLimit = Math.min(limit, 50);
675
- console.error(`[backfill_embeddings] ${dry_run ? "DRY RUN: " : ""}` +
699
+ debugLog(`[backfill_embeddings] ${dry_run ? "DRY RUN: " : ""}` +
676
700
  `project=${project || "all"}, limit=${safeLimit}`);
677
701
  const storage = await getStorage();
678
702
  // Find entries missing embeddings
@@ -721,7 +745,7 @@ export async function backfillEmbeddingsHandler(args) {
721
745
  ...(e.decisions || []),
722
746
  ].filter(Boolean).join(" | ");
723
747
  if (!textToEmbed.trim()) {
724
- console.error(`[backfill] Skipping entry ${e.id}: no text content`);
748
+ debugLog(`[backfill] Skipping entry ${e.id}: no text content`);
725
749
  failed++;
726
750
  continue;
727
751
  }
@@ -730,7 +754,7 @@ export async function backfillEmbeddingsHandler(args) {
730
754
  embedding: JSON.stringify(embedding),
731
755
  });
732
756
  repaired++;
733
- console.error(`[backfill] ✅ Repaired ${e.id} (${e.project})`);
757
+ debugLog(`[backfill] ✅ Repaired ${e.id} (${e.project})`);
734
758
  }
735
759
  catch (err) {
736
760
  failed++;
@@ -762,7 +786,7 @@ export async function memoryHistoryHandler(args) {
762
786
  }
763
787
  const { project, limit = 10 } = args;
764
788
  const storage = await getStorage();
765
- console.error(`[memory_history] Fetching history for project="${project}" (limit=${limit})`);
789
+ debugLog(`[memory_history] Fetching history for project="${project}" (limit=${limit})`);
766
790
  const history = await storage.getHistory(project, PRISM_USER_ID, Math.min(limit, 50));
767
791
  if (history.length === 0) {
768
792
  return {
@@ -803,7 +827,7 @@ export async function memoryCheckoutHandler(args) {
803
827
  }
804
828
  const { project, target_version } = args;
805
829
  const storage = await getStorage();
806
- console.error(`[memory_checkout] Reverting project="${project}" to version ${target_version}`);
830
+ debugLog(`[memory_checkout] Reverting project="${project}" to version ${target_version}`);
807
831
  // 1. Find the target snapshot
808
832
  const history = await storage.getHistory(project, PRISM_USER_ID, 50);
809
833
  const targetState = history.find(h => h.version === target_version);
@@ -955,7 +979,7 @@ export async function sessionSaveImageHandler(args) {
955
979
  await storage.saveHandoff(handoffUpdate, currentVersion);
956
980
  const fileSize = fs.statSync(vaultPath).size;
957
981
  const sizeKB = (fileSize / 1024).toFixed(1);
958
- console.error(`[Visual Memory] Saved image [${imageId}] for "${project}" (${sizeKB}KB, ${ext})`);
982
+ debugLog(`[Visual Memory] Saved image [${imageId}] for "${project}" (${sizeKB}KB, ${ext})`);
959
983
  return {
960
984
  content: [{
961
985
  type: "text",
@@ -1025,7 +1049,7 @@ export async function sessionViewImageHandler(args) {
1025
1049
  };
1026
1050
  const mimeType = MIME_MAP[ext] || "image/png";
1027
1051
  const fileSize = fs.statSync(vaultPath).size;
1028
- console.error(`[Visual Memory] Retrieved image [${image_id}] for "${project}" (${(fileSize / 1024).toFixed(1)}KB)`);
1052
+ debugLog(`[Visual Memory] Retrieved image [${image_id}] for "${project}" (${(fileSize / 1024).toFixed(1)}KB)`);
1029
1053
  // Return MCP content array with text + image
1030
1054
  return {
1031
1055
  content: [
@@ -1069,7 +1093,7 @@ export async function sessionHealthCheckHandler(args) {
1069
1093
  };
1070
1094
  }
1071
1095
  const autoFix = args.auto_fix || false; // default: read-only scan
1072
- console.error("[Health Check] Running fsck (auto_fix=" + autoFix + ")");
1096
+ debugLog("[Health Check] Running fsck (auto_fix=" + autoFix + ")");
1073
1097
  try {
1074
1098
  // Get the storage backend (SQLite or Supabase)
1075
1099
  const storage = await getStorage();
@@ -1082,11 +1106,11 @@ export async function sessionHealthCheckHandler(args) {
1082
1106
  if (autoFix && report.issues.length > 0) {
1083
1107
  const embeddingIssue = report.issues.find(i => i.check === "missing_embeddings");
1084
1108
  if (embeddingIssue && embeddingIssue.count > 0) {
1085
- console.error("[Health Check] Auto-fixing " + embeddingIssue.count + " missing embeddings...");
1109
+ debugLog("[Health Check] Auto-fixing " + embeddingIssue.count + " missing embeddings...");
1086
1110
  try {
1087
1111
  await backfillEmbeddingsHandler({ dry_run: false, limit: 50 });
1088
1112
  fixedCount += embeddingIssue.count;
1089
- console.error("[Health Check] Backfill complete.");
1113
+ debugLog("[Health Check] Backfill complete.");
1090
1114
  }
1091
1115
  catch (err) {
1092
1116
  console.error("[Health Check] Backfill failed: " + err);
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import { GoogleGenerativeAI } from "@google/generative-ai";
16
16
  import { GOOGLE_API_KEY } from "../config.js";
17
+ import { debugLog } from "./logger.js";
17
18
  /**
18
19
  * Generates a 3-bullet Morning Briefing using Gemini.
19
20
  *
@@ -58,7 +59,7 @@ Rules:
58
59
  try {
59
60
  const result = await model.generateContent(prompt);
60
61
  const text = result.response.text().trim();
61
- console.error(`[Morning Briefing] Generated for "${context.project}" (${text.length} chars)`);
62
+ debugLog(`[Morning Briefing] Generated for "${context.project}" (${text.length} chars)`);
62
63
  return text;
63
64
  }
64
65
  catch (error) {
@@ -34,6 +34,7 @@
34
34
  */
35
35
  import { GoogleGenerativeAI, TaskType } from "@google/generative-ai";
36
36
  import { GOOGLE_API_KEY } from "../config.js";
37
+ import { debugLog } from "./logger.js";
37
38
  // ─── Constants ────────────────────────────────────────────────
38
39
  // REVIEWER NOTE: Maximum characters to send to the embedding API.
39
40
  // gemini-embedding-001 supports up to 2048 tokens. At ~4 chars/token,
@@ -68,7 +69,7 @@ export async function generateEmbedding(text) {
68
69
  // Fix: truncate at the last word boundary before the limit.
69
70
  let inputText = text;
70
71
  if (inputText.length > MAX_EMBEDDING_CHARS) {
71
- console.error(`[embedding] Input text truncated from ${inputText.length} to ~${MAX_EMBEDDING_CHARS} chars (word-safe)`);
72
+ debugLog(`[embedding] Input text truncated from ${inputText.length} to ~${MAX_EMBEDDING_CHARS} chars (word-safe)`);
72
73
  inputText = inputText.substring(0, MAX_EMBEDDING_CHARS);
73
74
  // Snap back to the last space to avoid splitting a word or surrogate pair
74
75
  const lastSpace = inputText.lastIndexOf(' ');
@@ -83,7 +84,7 @@ export async function generateEmbedding(text) {
83
84
  const genAI = new GoogleGenerativeAI(GOOGLE_API_KEY);
84
85
  const model = genAI.getGenerativeModel({ model: "gemini-embedding-001" }, { apiVersion: "v1beta" } // gemini-embedding-001 requires v1beta
85
86
  );
86
- console.error(`[embedding] Generating 768-dim embedding for ${inputText.length} chars`);
87
+ debugLog(`[embedding] Generating 768-dim embedding for ${inputText.length} chars`);
87
88
  const result = await model.embedContent({
88
89
  content: {
89
90
  role: "user",
@@ -35,6 +35,7 @@
35
35
  */
36
36
  import { GoogleGenerativeAI } from "@google/generative-ai"; // Gemini SDK for LLM calls
37
37
  import { GOOGLE_API_KEY } from "../config.js"; // API key from environment
38
+ import { debugLog } from "./logger.js";
38
39
  /**
39
40
  * Merge old and new key_context using Gemini to resolve contradictions.
40
41
  *
@@ -57,7 +58,7 @@ import { GOOGLE_API_KEY } from "../config.js"; // API key from environment
57
58
  export async function consolidateFacts(oldContext, newContext) {
58
59
  // Guard: need API key to call Gemini
59
60
  if (!GOOGLE_API_KEY) {
60
- console.error("[FactMerger] Skipped — no GOOGLE_API_KEY configured");
61
+ debugLog("[FactMerger] Skipped — no GOOGLE_API_KEY configured");
61
62
  return newContext; // fallback: just use the new context as-is
62
63
  }
63
64
  // Guard: if either context is empty, no merging needed
@@ -69,7 +70,7 @@ export async function consolidateFacts(oldContext, newContext) {
69
70
  }
70
71
  // Guard: if old and new are identical, skip the LLM call entirely
71
72
  if (oldContext.trim() === newContext.trim()) {
72
- console.error("[FactMerger] Old and new context are identical — skipping merge");
73
+ debugLog("[FactMerger] Old and new context are identical — skipping merge");
73
74
  return newContext; // no changes needed
74
75
  }
75
76
  // Initialize Gemini with the configured API key
@@ -96,7 +97,7 @@ export async function consolidateFacts(oldContext, newContext) {
96
97
  // Extract and trim the merged text from Gemini's response
97
98
  const mergedText = result.response.text().trim();
98
99
  // Log the merge result for debugging (to stderr, not stdout)
99
- console.error("[FactMerger] Merged context (" +
100
+ debugLog("[FactMerger] Merged context (" +
100
101
  oldContext.length + " chars old + " +
101
102
  newContext.length + " chars new → " +
102
103
  mergedText.length + " chars merged)");
@@ -23,6 +23,7 @@
23
23
  */
24
24
  import { GoogleGenerativeAI } from "@google/generative-ai"; // Gemini SDK
25
25
  import { GOOGLE_API_KEY } from "../config.js"; // API key from env
26
+ import { debugLog } from "./logger.js";
26
27
  /**
27
28
  * Scan agent memory for prompt injection attacks.
28
29
  *
@@ -44,7 +45,7 @@ import { GOOGLE_API_KEY } from "../config.js"; // API key from env
44
45
  export async function scanForPromptInjection(projectContext) {
45
46
  // No API key = skip scan gracefully (don't block health check)
46
47
  if (!GOOGLE_API_KEY) {
47
- console.error("[Security Scan] Skipped — no GOOGLE_API_KEY configured");
48
+ debugLog("[Security Scan] Skipped — no GOOGLE_API_KEY configured");
48
49
  return { safe: true }; // assume safe when we can't check
49
50
  }
50
51
  // Don't scan empty context — nothing to analyze
@@ -81,7 +82,7 @@ export async function scanForPromptInjection(projectContext) {
81
82
  .replace(/```/g, "") // remove ```
82
83
  .trim(); // trim whitespace
83
84
  const parsed = JSON.parse(cleaned); // parse JSON
84
- console.error("[Security Scan] Result: safe=" + parsed.safe +
85
+ debugLog("[Security Scan] Result: safe=" + parsed.safe +
85
86
  (parsed.reason ? ", reason=" + parsed.reason : ""));
86
87
  return {
87
88
  safe: Boolean(parsed.safe), // normalize to boolean
@@ -0,0 +1,12 @@
1
+ import { PRISM_DEBUG_LOGGING } from "../config.js";
2
+ /**
3
+ * Logs a message to stderr only if PRISM_DEBUG_LOGGING is true.
4
+ * Use this for verbose traces (e.g., initialization, request tracking)
5
+ * that should be hidden from users by default but remain available
6
+ * for troubleshooting.
7
+ */
8
+ export function debugLog(message) {
9
+ if (PRISM_DEBUG_LOGGING) {
10
+ console.error(message);
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "2.3.8",
3
+ "version": "2.3.10",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
5
  "description": "The Mind Palace for AI Agents — local-first MCP server with persistent memory (SQLite/Supabase), visual dashboard, time travel, multi-agent sync, Morning Briefings, reality drift detection, code mode templates, semantic vector search, and Brave Search + Gemini analysis. Zero-config local mode.",
6
6
  "module": "index.ts",