prism-mcp-server 4.0.0 → 4.3.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 +276 -78
- package/dist/config.js +8 -0
- package/dist/dashboard/ui.js +120 -0
- package/dist/lifecycle.js +164 -0
- package/dist/server.js +108 -49
- package/dist/storage/configStorage.js +44 -11
- package/dist/storage/sqlite.js +31 -0
- package/dist/storage/supabase.js +33 -0
- package/dist/storage/supabaseMigrations.js +110 -0
- package/dist/tools/index.js +2 -2
- package/dist/tools/sessionMemoryDefinitions.js +40 -0
- package/dist/tools/sessionMemoryHandlers.js +151 -2
- package/package.json +1 -1
package/dist/storage/supabase.js
CHANGED
|
@@ -15,10 +15,18 @@
|
|
|
15
15
|
import { supabasePost, supabaseGet, supabaseRpc, supabasePatch, supabaseDelete, } from "../utils/supabaseApi.js";
|
|
16
16
|
import { debugLog } from "../utils/logger.js";
|
|
17
17
|
import { getSetting as cfgGet, setSetting as cfgSet, getAllSettings as cfgGetAll } from "./configStorage.js";
|
|
18
|
+
import { runAutoMigrations } from "./supabaseMigrations.js";
|
|
18
19
|
export class SupabaseStorage {
|
|
19
20
|
// ─── Lifecycle ─────────────────────────────────────────────
|
|
20
21
|
async initialize() {
|
|
21
22
|
debugLog("[SupabaseStorage] Initialized (REST API, stateless)");
|
|
23
|
+
// Auto-apply pending schema migrations (non-fatal)
|
|
24
|
+
try {
|
|
25
|
+
await runAutoMigrations();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error("[SupabaseStorage] Auto-migration failed. Server will continue, but some tools may be unstable.", err instanceof Error ? err.message : err);
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
async close() {
|
|
24
32
|
debugLog("[SupabaseStorage] Closed (no-op for REST)");
|
|
@@ -375,4 +383,29 @@ export class SupabaseStorage {
|
|
|
375
383
|
debugLog("[SupabaseStorage] adjustImportance failed: " + (e instanceof Error ? e.message : String(e)));
|
|
376
384
|
}
|
|
377
385
|
}
|
|
386
|
+
// ─── v4.2: Graduated Insights Query ──────────────────────────
|
|
387
|
+
async getGraduatedInsights(project, userId, minImportance = 7) {
|
|
388
|
+
const data = await supabaseGet("session_ledger", {
|
|
389
|
+
project: `eq.${project}`,
|
|
390
|
+
user_id: `eq.${userId}`,
|
|
391
|
+
importance: `gte.${minImportance}`,
|
|
392
|
+
deleted_at: "is.null",
|
|
393
|
+
archived_at: "is.null",
|
|
394
|
+
select: "id,project,user_id,role,summary,importance,event_type,decisions,created_at",
|
|
395
|
+
order: "importance.desc,created_at.desc",
|
|
396
|
+
});
|
|
397
|
+
const rows = Array.isArray(data) ? data : [];
|
|
398
|
+
return rows.map((r) => ({
|
|
399
|
+
id: r.id,
|
|
400
|
+
project: r.project,
|
|
401
|
+
user_id: r.user_id,
|
|
402
|
+
role: r.role || "global",
|
|
403
|
+
summary: r.summary,
|
|
404
|
+
importance: r.importance || 0,
|
|
405
|
+
event_type: r.event_type || "session",
|
|
406
|
+
decisions: Array.isArray(r.decisions) ? r.decisions : [],
|
|
407
|
+
created_at: r.created_at,
|
|
408
|
+
conversation_id: "",
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
378
411
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Auto-Migration Runner (v4.1)
|
|
3
|
+
*
|
|
4
|
+
* On server startup, this module checks the `prism_schema_versions` table
|
|
5
|
+
* and applies any pending DDL migrations via the `prism_apply_ddl` RPC.
|
|
6
|
+
*
|
|
7
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
8
|
+
* HOW IT WORKS:
|
|
9
|
+
* 1. For each migration in MIGRATIONS[], call prism_apply_ddl(version, name, sql)
|
|
10
|
+
* 2. The Postgres function checks if the version is already applied (idempotent)
|
|
11
|
+
* 3. If not applied, it EXECUTE's the SQL and records the version
|
|
12
|
+
*
|
|
13
|
+
* GRACEFUL DEGRADATION:
|
|
14
|
+
* If prism_apply_ddl doesn't exist (PGRST202), the runner logs a
|
|
15
|
+
* warning and skips — the server still starts, but v4+ tools may
|
|
16
|
+
* fail against an old schema.
|
|
17
|
+
*
|
|
18
|
+
* SECURITY NOTE:
|
|
19
|
+
* prism_apply_ddl is SECURITY DEFINER (runs as postgres owner).
|
|
20
|
+
* The prism_schema_versions table has RLS: only service_role can write.
|
|
21
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
22
|
+
*/
|
|
23
|
+
import { supabaseRpc } from "../utils/supabaseApi.js";
|
|
24
|
+
/**
|
|
25
|
+
* All Supabase DDL migrations.
|
|
26
|
+
*
|
|
27
|
+
* IMPORTANT: Only add migrations for schema changes that Supabase
|
|
28
|
+
* users need. SQLite handles its own schema in sqlite.ts.
|
|
29
|
+
*
|
|
30
|
+
* Each `sql` string is passed to Postgres EXECUTE — it runs as a
|
|
31
|
+
* single transaction. Use IF NOT EXISTS / IF EXISTS guards generously.
|
|
32
|
+
*/
|
|
33
|
+
export const MIGRATIONS = [
|
|
34
|
+
{
|
|
35
|
+
version: 26,
|
|
36
|
+
name: "active_behavioral_memory",
|
|
37
|
+
sql: `
|
|
38
|
+
-- v4.0: Active Behavioral Memory columns
|
|
39
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS event_type TEXT NOT NULL DEFAULT 'session';
|
|
40
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS confidence_score INTEGER DEFAULT NULL;
|
|
41
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS importance INTEGER NOT NULL DEFAULT 0;
|
|
42
|
+
|
|
43
|
+
-- Soft-delete / archival columns
|
|
44
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ DEFAULT NULL;
|
|
45
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS archived_at TIMESTAMPTZ DEFAULT NULL;
|
|
46
|
+
ALTER TABLE session_ledger ADD COLUMN IF NOT EXISTS deleted_reason TEXT DEFAULT NULL;
|
|
47
|
+
|
|
48
|
+
-- Indexes
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_event_type ON session_ledger(event_type);
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_importance ON session_ledger(importance DESC);
|
|
51
|
+
|
|
52
|
+
-- Partial index for high-priority warnings
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_ledger_behavioral_warnings
|
|
54
|
+
ON session_ledger(project, user_id, role, importance DESC)
|
|
55
|
+
WHERE event_type = 'correction' AND importance >= 3
|
|
56
|
+
AND deleted_at IS NULL AND archived_at IS NULL;
|
|
57
|
+
`,
|
|
58
|
+
},
|
|
59
|
+
// Future migrations go here (version 28+)
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* Current schema version — derived from the MIGRATIONS array.
|
|
63
|
+
* Automatically updates when new migrations are added.
|
|
64
|
+
* Used for logging and diagnostics.
|
|
65
|
+
*/
|
|
66
|
+
export const CURRENT_SCHEMA_VERSION = MIGRATIONS.length > 0 ? MIGRATIONS[MIGRATIONS.length - 1].version : 27;
|
|
67
|
+
// ─── Runner ──────────────────────────────────────────────────────
|
|
68
|
+
/**
|
|
69
|
+
* Run all pending auto-migrations on Supabase startup.
|
|
70
|
+
*
|
|
71
|
+
* Called from SupabaseStorage.initialize(). Non-fatal: if the
|
|
72
|
+
* migration infrastructure (027) hasn't been applied, the runner
|
|
73
|
+
* logs a warning and returns silently.
|
|
74
|
+
*/
|
|
75
|
+
export async function runAutoMigrations() {
|
|
76
|
+
if (MIGRATIONS.length === 0) {
|
|
77
|
+
return; // Nothing to apply
|
|
78
|
+
}
|
|
79
|
+
console.error(`[Prism Auto-Migration] Schema v${CURRENT_SCHEMA_VERSION} — checking ${MIGRATIONS.length} migration(s)…`);
|
|
80
|
+
for (const migration of MIGRATIONS) {
|
|
81
|
+
try {
|
|
82
|
+
const result = await supabaseRpc("prism_apply_ddl", {
|
|
83
|
+
p_version: migration.version,
|
|
84
|
+
p_name: migration.name,
|
|
85
|
+
p_sql: migration.sql,
|
|
86
|
+
});
|
|
87
|
+
// Parse the JSON result from the RPC
|
|
88
|
+
const data = (typeof result === "string" ? JSON.parse(result) : result);
|
|
89
|
+
if (data?.status === "applied") {
|
|
90
|
+
console.error(`[Prism Auto-Migration] ✅ Applied migration ${migration.version}: ${migration.name}`);
|
|
91
|
+
}
|
|
92
|
+
else if (data?.status === "already_applied") {
|
|
93
|
+
// Silent skip — expected for idempotent restarts
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
98
|
+
// PGRST202 = function not found → migration infra (027) not applied yet
|
|
99
|
+
if (errMsg.includes("PGRST202") || errMsg.includes("Could not find the function")) {
|
|
100
|
+
console.error("[Prism Auto-Migration] ⚠️ prism_apply_ddl() not found. " +
|
|
101
|
+
"Apply migration 027_auto_migration_infra.sql to enable auto-migrations.\n" +
|
|
102
|
+
" Run: supabase db push (or apply the SQL in the Supabase Dashboard SQL Editor)");
|
|
103
|
+
return; // Stop — no point trying further migrations
|
|
104
|
+
}
|
|
105
|
+
// Any other error: log and throw to surface the problem
|
|
106
|
+
console.error(`[Prism Auto-Migration] ❌ Migration ${migration.version} (${migration.name}) failed: ${errMsg}`);
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -26,8 +26,8 @@ export { webSearchHandler, braveWebSearchCodeModeHandler, localSearchHandler, br
|
|
|
26
26
|
// This file always exports them — server.ts decides whether to include them in the tool list.
|
|
27
27
|
//
|
|
28
28
|
// v0.4.0: Added SESSION_COMPACT_LEDGER_TOOL and SESSION_SEARCH_MEMORY_TOOL
|
|
29
|
-
export { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL, SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL, MEMORY_HISTORY_TOOL, MEMORY_CHECKOUT_TOOL, SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL, SESSION_HEALTH_CHECK_TOOL, SESSION_FORGET_MEMORY_TOOL, KNOWLEDGE_SET_RETENTION_TOOL, SESSION_SAVE_EXPERIENCE_TOOL, KNOWLEDGE_UPVOTE_TOOL, KNOWLEDGE_DOWNVOTE_TOOL } from "./sessionMemoryDefinitions.js";
|
|
30
|
-
export { sessionSaveLedgerHandler, sessionSaveHandoffHandler, sessionLoadContextHandler, knowledgeSearchHandler, knowledgeForgetHandler, sessionSearchMemoryHandler, backfillEmbeddingsHandler, memoryHistoryHandler, memoryCheckoutHandler, sessionSaveImageHandler, sessionViewImageHandler, sessionHealthCheckHandler, sessionForgetMemoryHandler, knowledgeSetRetentionHandler, sessionSaveExperienceHandler, knowledgeUpvoteHandler, knowledgeDownvoteHandler } from "./sessionMemoryHandlers.js";
|
|
29
|
+
export { SESSION_SAVE_LEDGER_TOOL, SESSION_SAVE_HANDOFF_TOOL, SESSION_LOAD_CONTEXT_TOOL, KNOWLEDGE_SEARCH_TOOL, KNOWLEDGE_FORGET_TOOL, SESSION_COMPACT_LEDGER_TOOL, SESSION_SEARCH_MEMORY_TOOL, MEMORY_HISTORY_TOOL, MEMORY_CHECKOUT_TOOL, SESSION_SAVE_IMAGE_TOOL, SESSION_VIEW_IMAGE_TOOL, SESSION_HEALTH_CHECK_TOOL, SESSION_FORGET_MEMORY_TOOL, KNOWLEDGE_SET_RETENTION_TOOL, SESSION_SAVE_EXPERIENCE_TOOL, KNOWLEDGE_UPVOTE_TOOL, KNOWLEDGE_DOWNVOTE_TOOL, KNOWLEDGE_SYNC_RULES_TOOL } from "./sessionMemoryDefinitions.js";
|
|
30
|
+
export { sessionSaveLedgerHandler, sessionSaveHandoffHandler, sessionLoadContextHandler, knowledgeSearchHandler, knowledgeForgetHandler, sessionSearchMemoryHandler, backfillEmbeddingsHandler, memoryHistoryHandler, memoryCheckoutHandler, sessionSaveImageHandler, sessionViewImageHandler, sessionHealthCheckHandler, sessionForgetMemoryHandler, knowledgeSetRetentionHandler, sessionSaveExperienceHandler, knowledgeUpvoteHandler, knowledgeDownvoteHandler, knowledgeSyncRulesHandler } from "./sessionMemoryHandlers.js";
|
|
31
31
|
// ── Compaction Handler (v0.4.0 — Enhancement #2) ──
|
|
32
32
|
// The compaction handler is in a separate file because it's significantly
|
|
33
33
|
// more complex than the other session memory handlers (chunked Gemini
|
|
@@ -743,3 +743,43 @@ export function isKnowledgeVoteArgs(args) {
|
|
|
743
743
|
"id" in args &&
|
|
744
744
|
typeof args.id === "string");
|
|
745
745
|
}
|
|
746
|
+
// ─── v4.2: Knowledge Sync Rules Tool ─────────────────────────
|
|
747
|
+
export const KNOWLEDGE_SYNC_RULES_TOOL = {
|
|
748
|
+
name: "knowledge_sync_rules",
|
|
749
|
+
description: "Auto-sync graduated insights (importance >= 7) into your project's IDE rules file " +
|
|
750
|
+
"(.cursorrules or .clauderules). This bridges behavioral memory with static IDE context — " +
|
|
751
|
+
"turning dynamic agent learnings into always-on rules.\n\n" +
|
|
752
|
+
"**How it works:**\n" +
|
|
753
|
+
"1. Fetches graduated insights from the ledger\n" +
|
|
754
|
+
"2. Formats them as markdown rules inside sentinel markers\n" +
|
|
755
|
+
"3. Idempotently writes them into the target file at the project's configured repo_path\n\n" +
|
|
756
|
+
"**Requirements:** The project must have a repo_path configured in the dashboard.\n\n" +
|
|
757
|
+
"**Idempotency:** Uses `<!-- PRISM:AUTO-RULES:START -->` / `<!-- PRISM:AUTO-RULES:END -->` " +
|
|
758
|
+
"sentinel markers. Running this tool multiple times produces the same file. " +
|
|
759
|
+
"User-maintained content outside the sentinels is never touched.",
|
|
760
|
+
inputSchema: {
|
|
761
|
+
type: "object",
|
|
762
|
+
properties: {
|
|
763
|
+
project: {
|
|
764
|
+
type: "string",
|
|
765
|
+
description: "Project identifier. Must have a repo_path configured in the dashboard.",
|
|
766
|
+
},
|
|
767
|
+
target_file: {
|
|
768
|
+
type: "string",
|
|
769
|
+
description: "Target rules filename (default: '.cursorrules'). " +
|
|
770
|
+
"Common values: '.cursorrules', '.clauderules'.",
|
|
771
|
+
},
|
|
772
|
+
dry_run: {
|
|
773
|
+
type: "boolean",
|
|
774
|
+
description: "If true, returns a preview of the rules block without writing to disk. Default: false.",
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
required: ["project"],
|
|
778
|
+
},
|
|
779
|
+
};
|
|
780
|
+
export function isKnowledgeSyncRulesArgs(args) {
|
|
781
|
+
return (typeof args === "object" &&
|
|
782
|
+
args !== null &&
|
|
783
|
+
"project" in args &&
|
|
784
|
+
typeof args.project === "string");
|
|
785
|
+
}
|
|
@@ -34,7 +34,13 @@ import { isSessionSaveLedgerArgs, isSessionSaveHandoffArgs, isSessionLoadContext
|
|
|
34
34
|
isSessionForgetMemoryArgs, // Phase 2: GDPR-compliant memory deletion type guard
|
|
35
35
|
isKnowledgeSetRetentionArgs, // v3.1: TTL retention policy type guard
|
|
36
36
|
// v4.0: Active Behavioral Memory type guards
|
|
37
|
-
isSessionSaveExperienceArgs, isKnowledgeVoteArgs,
|
|
37
|
+
isSessionSaveExperienceArgs, isKnowledgeVoteArgs,
|
|
38
|
+
// v4.2: Sync Rules type guard
|
|
39
|
+
isKnowledgeSyncRulesArgs, } from "./sessionMemoryDefinitions.js";
|
|
40
|
+
// v4.2: File system access for knowledge_sync_rules
|
|
41
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
42
|
+
import { existsSync } from "node:fs";
|
|
43
|
+
import { join, dirname } from "node:path";
|
|
38
44
|
// v3.1: In-memory debounce lock for auto-compaction.
|
|
39
45
|
// Prevents multiple concurrent Gemini compaction tasks for the same project
|
|
40
46
|
// when many agents call session_save_ledger at the same time.
|
|
@@ -55,6 +61,23 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
55
61
|
}
|
|
56
62
|
const { project, conversation_id, summary, todos, files_changed, decisions, role } = args;
|
|
57
63
|
const storage = await getStorage();
|
|
64
|
+
// ─── Repo path mismatch validation (v4.2) ───
|
|
65
|
+
let repoPathWarning = "";
|
|
66
|
+
if (files_changed && files_changed.length > 0) {
|
|
67
|
+
try {
|
|
68
|
+
const configuredPath = await getSetting(`repo_path:${project}`, "");
|
|
69
|
+
if (configuredPath && configuredPath.trim()) {
|
|
70
|
+
const normalizedPath = configuredPath.trim().replace(/\\/g, "/").replace(/\/+$/, ""); // normalize + strip trailing slash
|
|
71
|
+
const mismatched = files_changed.filter((f) => !f.replace(/\\/g, "/").startsWith(normalizedPath));
|
|
72
|
+
if (mismatched.length === files_changed.length) {
|
|
73
|
+
repoPathWarning = `\n\n⚠️ Project mismatch: none of the files_changed paths match repo_path "${normalizedPath}" ` +
|
|
74
|
+
`configured for project "${project}". Consider saving under the correct project.`;
|
|
75
|
+
debugLog(`[session_save_ledger] Repo path mismatch for "${project}": expected prefix "${normalizedPath}"`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch { /* getSetting non-fatal */ }
|
|
80
|
+
}
|
|
58
81
|
debugLog(`[session_save_ledger] Saving ledger entry for project="${project}"`);
|
|
59
82
|
// Auto-extract keywords from summary + decisions for knowledge accumulation
|
|
60
83
|
const combinedText = [summary, ...(decisions || [])].join(" ");
|
|
@@ -130,6 +153,7 @@ export async function sessionSaveLedgerHandler(args) {
|
|
|
130
153
|
(files_changed?.length ? `Files changed: ${files_changed.length}\n` : "") +
|
|
131
154
|
(decisions?.length ? `Decisions: ${decisions.length}\n` : "") +
|
|
132
155
|
(GOOGLE_API_KEY ? `📊 Embedding generation queued for semantic search.\n` : "") +
|
|
156
|
+
repoPathWarning +
|
|
133
157
|
`\nRaw response: ${JSON.stringify(result)}`,
|
|
134
158
|
}],
|
|
135
159
|
isError: false,
|
|
@@ -360,7 +384,8 @@ export async function sessionLoadContextHandler(args) {
|
|
|
360
384
|
throw new Error("Invalid arguments for session_load_context");
|
|
361
385
|
}
|
|
362
386
|
const { project, level = "standard", role } = args;
|
|
363
|
-
const maxTokens = args.max_tokens
|
|
387
|
+
const maxTokens = args.max_tokens
|
|
388
|
+
|| parseInt(await getSetting("max_tokens", "0"), 10) || undefined; // v4.0: arg > dashboard setting > none
|
|
364
389
|
const agentName = await getSetting("agent_name", "");
|
|
365
390
|
const validLevels = ["quick", "standard", "deep"];
|
|
366
391
|
if (!validLevels.includes(level)) {
|
|
@@ -1684,3 +1709,127 @@ export async function knowledgeDownvoteHandler(args) {
|
|
|
1684
1709
|
isError: false,
|
|
1685
1710
|
};
|
|
1686
1711
|
}
|
|
1712
|
+
// ─── v4.2: Knowledge Sync Rules Handler ─────────────────────
|
|
1713
|
+
//
|
|
1714
|
+
// "The Bridge" — bridges v4.0 Behavioral Memory with v4.2 Repo
|
|
1715
|
+
// Registry. Extracts graduated insights (importance >= 7) from
|
|
1716
|
+
// the ledger and idempotently syncs them into the project's
|
|
1717
|
+
// .cursorrules or .clauderules file, turning dynamic learnings
|
|
1718
|
+
// into static, always-on IDE context.
|
|
1719
|
+
//
|
|
1720
|
+
// Sentinel markers ensure the auto-generated block is isolated
|
|
1721
|
+
// from user-maintained rules. Re-running always produces the
|
|
1722
|
+
// same output, preventing drift.
|
|
1723
|
+
const SENTINEL_START = "<!-- PRISM:AUTO-RULES:START -->";
|
|
1724
|
+
const SENTINEL_END = "<!-- PRISM:AUTO-RULES:END -->";
|
|
1725
|
+
/**
|
|
1726
|
+
* Formats graduated insights into a markdown rules block.
|
|
1727
|
+
* Each insight is rendered as a bullet with its importance score,
|
|
1728
|
+
* event type, and the summary/correction text.
|
|
1729
|
+
*/
|
|
1730
|
+
function formatRulesBlock(insights, project) {
|
|
1731
|
+
const header = `## Prism Graduated Insights (auto-synced)\n\n` +
|
|
1732
|
+
`> These rules were automatically generated by [Prism MCP](https://github.com/fdarcy/prism) ` +
|
|
1733
|
+
`from behavioral memory for project \"${project}\".\n` +
|
|
1734
|
+
`> Last synced: ${new Date().toISOString().split("T")[0]}\n\n`;
|
|
1735
|
+
const rules = insights.map(i => {
|
|
1736
|
+
const tag = i.event_type && i.event_type !== "session" ? ` (${i.event_type})` : "";
|
|
1737
|
+
return `- **[importance: ${i.importance}]**${tag} ${i.summary}`;
|
|
1738
|
+
}).join("\n");
|
|
1739
|
+
return `${SENTINEL_START}\n${header}${rules}\n${SENTINEL_END}`;
|
|
1740
|
+
}
|
|
1741
|
+
/**
|
|
1742
|
+
* Idempotently replaces or appends the sentinel block in a rules file.
|
|
1743
|
+
* Content outside the sentinels is never modified.
|
|
1744
|
+
*/
|
|
1745
|
+
function applySentinelBlock(existingContent, rulesBlock) {
|
|
1746
|
+
const startIdx = existingContent.indexOf(SENTINEL_START);
|
|
1747
|
+
const endIdx = existingContent.indexOf(SENTINEL_END);
|
|
1748
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
1749
|
+
// Replace existing block
|
|
1750
|
+
const before = existingContent.substring(0, startIdx);
|
|
1751
|
+
const after = existingContent.substring(endIdx + SENTINEL_END.length);
|
|
1752
|
+
return `${before}${rulesBlock}${after}`;
|
|
1753
|
+
}
|
|
1754
|
+
// Append with separator
|
|
1755
|
+
const separator = existingContent.length > 0 && !existingContent.endsWith("\n\n")
|
|
1756
|
+
? (existingContent.endsWith("\n") ? "\n" : "\n\n")
|
|
1757
|
+
: "";
|
|
1758
|
+
return `${existingContent}${separator}${rulesBlock}\n`;
|
|
1759
|
+
}
|
|
1760
|
+
export async function knowledgeSyncRulesHandler(args) {
|
|
1761
|
+
if (!isKnowledgeSyncRulesArgs(args)) {
|
|
1762
|
+
throw new Error("Invalid arguments for knowledge_sync_rules");
|
|
1763
|
+
}
|
|
1764
|
+
const { project, target_file = ".cursorrules", dry_run = false } = args;
|
|
1765
|
+
const storage = await getStorage();
|
|
1766
|
+
// 1. Resolve repo path
|
|
1767
|
+
const repoPath = await getSetting(`repo_path:${project}`, "");
|
|
1768
|
+
if (!repoPath || !repoPath.trim()) {
|
|
1769
|
+
return {
|
|
1770
|
+
content: [{
|
|
1771
|
+
type: "text",
|
|
1772
|
+
text: `❌ No repo_path configured for project "${project}".\n` +
|
|
1773
|
+
`Set it in the Mind Palace dashboard (Settings → Project Repo Paths) before syncing rules.`,
|
|
1774
|
+
}],
|
|
1775
|
+
isError: true,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
const normalizedRepoPath = repoPath.trim().replace(/\/+$/, "");
|
|
1779
|
+
// 2. Fetch graduated insights
|
|
1780
|
+
const insights = await storage.getGraduatedInsights(project, PRISM_USER_ID, 7);
|
|
1781
|
+
if (insights.length === 0) {
|
|
1782
|
+
return {
|
|
1783
|
+
content: [{
|
|
1784
|
+
type: "text",
|
|
1785
|
+
text: `ℹ️ No graduated insights found for project "${project}".\n` +
|
|
1786
|
+
`Insights graduate when their importance score reaches 7 or higher.\n` +
|
|
1787
|
+
`Use \`knowledge_upvote\` to increase importance of valuable entries.`,
|
|
1788
|
+
}],
|
|
1789
|
+
isError: false,
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
// 3. Format rules block
|
|
1793
|
+
const rulesBlock = formatRulesBlock(insights.map(i => ({ ...i, importance: i.importance ?? 0 })), project);
|
|
1794
|
+
// 4. Dry-run: return preview without writing
|
|
1795
|
+
if (dry_run) {
|
|
1796
|
+
return {
|
|
1797
|
+
content: [{
|
|
1798
|
+
type: "text",
|
|
1799
|
+
text: `🔍 **Dry Run Preview** — ${insights.length} graduated insight(s) for "${project}":\n\n` +
|
|
1800
|
+
`Target: ${normalizedRepoPath}/${target_file}\n\n` +
|
|
1801
|
+
`\`\`\`markdown\n${rulesBlock}\n\`\`\`\n\n` +
|
|
1802
|
+
`Run again without \`dry_run\` to write this to disk.`,
|
|
1803
|
+
}],
|
|
1804
|
+
isError: false,
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
// 5. Idempotent file write
|
|
1808
|
+
const targetPath = join(normalizedRepoPath, target_file);
|
|
1809
|
+
// Ensure directory exists (handles nested target_file like ".config/rules.md")
|
|
1810
|
+
const targetDir = dirname(targetPath);
|
|
1811
|
+
if (!existsSync(targetDir)) {
|
|
1812
|
+
await mkdir(targetDir, { recursive: true });
|
|
1813
|
+
}
|
|
1814
|
+
let existingContent = "";
|
|
1815
|
+
try {
|
|
1816
|
+
existingContent = await readFile(targetPath, "utf-8");
|
|
1817
|
+
}
|
|
1818
|
+
catch {
|
|
1819
|
+
// File doesn't exist yet — will be created
|
|
1820
|
+
debugLog(`[knowledge_sync_rules] File ${targetPath} doesn't exist, creating new`);
|
|
1821
|
+
}
|
|
1822
|
+
const newContent = applySentinelBlock(existingContent, rulesBlock);
|
|
1823
|
+
await writeFile(targetPath, newContent, "utf-8");
|
|
1824
|
+
debugLog(`[knowledge_sync_rules] Synced ${insights.length} insights to ${targetPath}`);
|
|
1825
|
+
return {
|
|
1826
|
+
content: [{
|
|
1827
|
+
type: "text",
|
|
1828
|
+
text: `✅ Synced ${insights.length} graduated insight(s) to \`${targetPath}\`\n\n` +
|
|
1829
|
+
`Top insights synced:\n` +
|
|
1830
|
+
insights.slice(0, 5).map(i => ` • [${i.importance}] ${i.summary.substring(0, 80)}${i.summary.length > 80 ? "..." : ""}`).join("\n") +
|
|
1831
|
+
(insights.length > 5 ? `\n ... and ${insights.length - 5} more` : ""),
|
|
1832
|
+
}],
|
|
1833
|
+
isError: false,
|
|
1834
|
+
};
|
|
1835
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prism-mcp-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
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",
|