persyst-mcp 2.1.0 → 2.1.1

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/bin/ingest.js ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * persyst-ingest — Direct Git Commit Ingester
5
+ *
6
+ * Usage:
7
+ * npx persyst-mcp ingest [repo_path] [count]
8
+ *
9
+ * This script runs directly without starting the MCP server, allowing
10
+ * git hooks or direct CLI commands to populate the memory database.
11
+ */
12
+
13
+ import { getRecentCommits } from '../src/git.js';
14
+ import {
15
+ insertMemory,
16
+ insertVector,
17
+ insertEntity,
18
+ insertEdge,
19
+ memoryExistsByHashPrefix
20
+ } from '../src/database.js';
21
+ import { generateEmbedding } from '../src/embeddings.js';
22
+ import { searchCache } from '../src/cache.js';
23
+
24
+ const repoPath = process.argv[2] || process.cwd();
25
+ const count = parseInt(process.argv[3], 10) || 10;
26
+
27
+ async function run() {
28
+ console.log(`[persyst] Ingesting git commits for: ${repoPath}`);
29
+ try {
30
+ const commits = await getRecentCommits(repoPath, count);
31
+ let added = 0;
32
+ let skipped = 0;
33
+
34
+ for (const commit of commits) {
35
+ const hashPrefix = commit.hash.slice(0, 7);
36
+ // Check if commit already exists in memories
37
+ if (memoryExistsByHashPrefix(`[${hashPrefix}]%`)) {
38
+ skipped++;
39
+ continue;
40
+ }
41
+
42
+ // Insert memory with git provenance
43
+ const id = insertMemory(commit.fullText, commit.importance, {
44
+ source_type: 'git',
45
+ source_id: commit.hash,
46
+ confidence: 0.8
47
+ });
48
+
49
+ // Generate embedding vector and store
50
+ const embedding = await generateEmbedding(commit.fullText);
51
+ insertVector(id, embedding);
52
+
53
+ // Link Author entity
54
+ const authorId = insertEntity(commit.author, 'person');
55
+ if (authorId) {
56
+ insertEdge(authorId, id, 'authored', 'entity', 'memory');
57
+ }
58
+
59
+ // Link Files Touched
60
+ for (const file of commit.files) {
61
+ const fileId = insertEntity(file, 'file');
62
+ if (fileId) {
63
+ insertEdge(fileId, id, 'touches', 'entity', 'memory');
64
+ }
65
+ }
66
+
67
+ added++;
68
+ }
69
+
70
+ if (added > 0) {
71
+ searchCache.invalidate();
72
+ }
73
+
74
+ console.log(`[persyst] Success: Ingested ${added} commits (${skipped} already existed)`);
75
+ process.exit(0);
76
+ } catch (err) {
77
+ console.error(`[persyst] Ingestion failed: ${err.message}`);
78
+ process.exit(1);
79
+ }
80
+ }
81
+
82
+ run();
package/bin/init.js ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * persyst-init — Workspace rules generator for VS Code-based IDEs (Cursor, Windsurf, Antigravity)
5
+ *
6
+ * Usage:
7
+ * npx persyst-mcp init
8
+ *
9
+ * What it does:
10
+ * 1. Safely creates or appends system instructions to `.cursorrules`
11
+ * 2. Safely creates or appends system instructions to `.windsurfrules`
12
+ * 3. Creates a general `.persystrules.md` copy-pasteable guide
13
+ * 4. Prints instructions on configuring MCP servers in Cursor/VS Code/Antigravity
14
+ *
15
+ * Design:
16
+ * - Non-destructive: checks for existing content before appending to avoid duplication
17
+ * - Idempotent: safe to run multiple times
18
+ * - Localized: targets the current working directory (project root)
19
+ */
20
+
21
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from 'fs';
22
+ import { join, resolve, dirname } from 'path';
23
+ import { fileURLToPath } from 'url';
24
+
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+
28
+ // ============================================================
29
+ // SYSTEM INSTRUCTION CONTENT
30
+ // ============================================================
31
+
32
+ const INSTRUCTION_HEADER = '# Persyst Memory Integration';
33
+
34
+ const RULE_CONTENT = `
35
+ ${INSTRUCTION_HEADER}
36
+ You are integrated with Persyst, a local-first MCP memory server that stores user preferences, project guidelines, context, and decisions.
37
+
38
+ ## Proactive Memory Retrieval (CRITICAL)
39
+ - Before responding to the user's prompt or executing any task, you MUST invoke the \`persyst\` MCP server's \`search_memories\` or \`get_optimized_context\` tool.
40
+ - Extract relevant search terms from the user's prompt (e.g. if the user says "update the database schema", query "database", "schema", "sqlite", "table").
41
+ - This ensures you are aware of existing codebase architecture, constraints, preferences, or past decisions before writing code.
42
+
43
+ ## Proactive Memory Storage (CRITICAL)
44
+ - Record Milestones: When you successfully implement a feature, fix a bug, or make an architectural decision, call the \`add_memory\` tool to store a summary of the change.
45
+ - Handle Contradictions: Persyst handles contradiction detection automatically. If a new fact contradicts an old memory, Persyst will flag it.
46
+ - Quality Over Quantity: Do NOT store trivial facts, temporary conversation noise, or duplicate data. "Bad data is worse than no data". Only store long-term architecture decisions, project details, and explicit user preferences.
47
+ `;
48
+
49
+ const GENERAL_GUIDE = `# Persyst General Agent Integration Guide
50
+
51
+ This workspace is configured with the Persyst local-first memory server.
52
+
53
+ ## How to Configure the MCP Server in VS Code / Cursor / Antigravity
54
+
55
+ Add the following configuration to your IDE's MCP Server settings:
56
+
57
+ - **Server Name:** \`persyst\`
58
+ - **Type:** \`command\`
59
+ - **Command:** \`npx\`
60
+ - **Arguments:** \`["-y", "persyst-mcp"]\`
61
+
62
+ Alternatively, if you have installed the package globally (\`npm install -g persyst-mcp\`), you can configure:
63
+ - **Command:** \`persyst-mcp\`
64
+ - **Arguments:** \`[]\`
65
+
66
+ ---
67
+
68
+ ## Copy-Paste System Prompt Instructions
69
+ If your agent does not read \`.cursorrules\` or \`.windsurfrules\` natively, copy and paste the following prompt into the agent's Custom Instructions, System Prompt, or System Rules:
70
+
71
+ \`\`\`markdown
72
+ ${RULE_CONTENT.trim()}
73
+ \`\`\`
74
+ `;
75
+
76
+ // ============================================================
77
+ // HELPERS
78
+ // ============================================================
79
+
80
+ function setupRuleFile(filePath, fileName) {
81
+ let content = RULE_CONTENT;
82
+ let action = 'Created';
83
+
84
+ if (existsSync(filePath)) {
85
+ const existing = readFileSync(filePath, 'utf8');
86
+ if (existing.includes(INSTRUCTION_HEADER)) {
87
+ console.log(` ℹ️ ${fileName} already has Persyst rules configured (skipped).`);
88
+ return;
89
+ }
90
+ content = existing + '\n' + RULE_CONTENT;
91
+ action = 'Appended to';
92
+ }
93
+
94
+ writeFileSync(filePath, content.trim() + '\n', 'utf8');
95
+ console.log(` ✅ ${action} ${fileName}`);
96
+ }
97
+
98
+ // ============================================================
99
+ // MAIN
100
+ // ============================================================
101
+
102
+ function run() {
103
+ console.log('');
104
+ console.log(' 🧠 Persyst — Workspace Rules Setup');
105
+ console.log(' ════════════════════════════════════');
106
+ console.log('');
107
+
108
+ const cwd = process.cwd();
109
+ console.log(` 📁 Target workspace: ${cwd}`);
110
+ console.log('');
111
+
112
+ // 1. Create/Append Cursor Rules
113
+ const cursorRulesPath = join(cwd, '.cursorrules');
114
+ setupRuleFile(cursorRulesPath, '.cursorrules');
115
+
116
+ // 2. Create/Append Windsurf Rules
117
+ const windsurfRulesPath = join(cwd, '.windsurfrules');
118
+ setupRuleFile(windsurfRulesPath, '.windsurfrules');
119
+
120
+ // 3. Create General Guide File
121
+ const generalGuidePath = join(cwd, '.persystrules.md');
122
+ writeFileSync(generalGuidePath, GENERAL_GUIDE.trim() + '\n', 'utf8');
123
+ console.log(' ✅ Created .persystrules.md (General Guide)');
124
+
125
+ // 4. Configure Git post-commit hook for automatic commit ingestion
126
+ const gitDir = join(cwd, '.git');
127
+ if (existsSync(gitDir)) {
128
+ const hooksDir = join(gitDir, 'hooks');
129
+ mkdirSync(hooksDir, { recursive: true });
130
+ const postCommitPath = join(hooksDir, 'post-commit');
131
+ const localPersystPath = resolve(__dirname, '..', 'index.js').replace(/\\/g, '/');
132
+
133
+ const hookContent = `#!/bin/sh
134
+ # Persyst Git Commit Ingestion Hook
135
+ # Automatically ingests recent commits into Persyst memory on every commit.
136
+
137
+ # Local project path fallback for development
138
+ LOCAL_PERSYST="${localPersystPath}"
139
+
140
+ if [ -f "$LOCAL_PERSYST" ]; then
141
+ node "$LOCAL_PERSYST" ingest "$PWD" 5 >/dev/null 2>&1 || true
142
+ else
143
+ npx persyst-mcp ingest "$PWD" 5 >/dev/null 2>&1 || true
144
+ fi
145
+ `;
146
+
147
+ writeFileSync(postCommitPath, hookContent, { mode: 0o755 });
148
+ try {
149
+ chmodSync(postCommitPath, 0o755);
150
+ } catch (_) {}
151
+ console.log(' ✅ Configured Git post-commit hook for auto-ingestion');
152
+ }
153
+
154
+ // 5. Print Success & Configuration Help
155
+ console.log('');
156
+ console.log(' ════════════════════════════════════');
157
+ console.log(' ✅ Rules and Git hooks initialization complete!');
158
+ console.log('');
159
+ console.log(' To connect the memory server to Cursor, Antigravity, or VS Code:');
160
+ console.log(' 1. Open your IDE Settings -> MCP (Model Context Protocol).');
161
+ console.log(' 2. Add a new command server:');
162
+ console.log(' • Name: persyst');
163
+ console.log(' • Command: npx');
164
+ console.log(' • Arguments: -y persyst-mcp');
165
+ console.log('');
166
+ console.log(' The rules we generated will guide the AI agents in this workspace to:');
167
+ console.log(' • Proactively search memory before answering prompts.');
168
+ console.log(' • Log milestone achievements and user preferences.');
169
+ console.log(' • Keep the memory clean ("no bad data").');
170
+ console.log('');
171
+ }
172
+
173
+ run();
package/index.js CHANGED
@@ -10,6 +10,8 @@
10
10
  * node index.js (direct — starts MCP server)
11
11
  * npx persyst-mcp (via npm — starts MCP server)
12
12
  * npx persyst-mcp setup (install Claude Code hooks)
13
+ * npx persyst-mcp init (initialize workspace rules & git hooks)
14
+ * npx persyst-mcp ingest (manually ingest git commits)
13
15
  * persyst-mcp (if installed globally)
14
16
  */
15
17
 
@@ -19,6 +21,17 @@ const subcommand = process.argv[2];
19
21
  if (subcommand === 'setup') {
20
22
  // Delegate to the setup CLI
21
23
  await import('./bin/setup.js');
24
+ } else if (subcommand === 'aider') {
25
+ // Shift 'aider' from process.argv so aider.js gets the correct arguments
26
+ process.argv.splice(2, 1);
27
+ await import('./bin/aider.js');
28
+ } else if (subcommand === 'init') {
29
+ // Delegate to the rules init CLI
30
+ await import('./bin/init.js');
31
+ } else if (subcommand === 'ingest') {
32
+ // Shift 'ingest' from process.argv so ingest.js gets the correct arguments
33
+ process.argv.splice(2, 1);
34
+ await import('./bin/ingest.js');
22
35
  } else {
23
36
  // Default: start the MCP server
24
37
  const { startServer } = await import('./src/server.js');
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "persyst-mcp",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Local-first MCP memory server with hybrid keyword + semantic search for coding agents",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
8
8
  "persyst-mcp": "index.js",
9
9
  "persyst-setup": "bin/setup.js",
10
- "persyst-aider": "bin/aider.js"
10
+ "persyst-aider": "bin/aider.js",
11
+ "persyst-init": "bin/init.js",
12
+ "persyst-ingest": "bin/ingest.js"
11
13
  },
12
14
  "engines": {
13
15
  "node": ">=18.0.0"
package/src/search.js CHANGED
@@ -19,6 +19,8 @@ import { generateEmbedding } from './embeddings.js';
19
19
  import { createAttestation } from './attestation.js';
20
20
  import { searchCache, LRUCache } from './cache.js';
21
21
 
22
+ let lastDataVersion = 0;
23
+
22
24
  /**
23
25
  * Search memories using both keyword and semantic strategies.
24
26
  * Results are cached in the LRU cache for repeated queries.
@@ -30,6 +32,17 @@ import { searchCache, LRUCache } from './cache.js';
30
32
  * @returns {Promise<Array>} Ranked search results (with .attestation property attached)
31
33
  */
32
34
  export async function searchHybrid(queryText, limit = 5, agentId = null, sessionId = null) {
35
+ // Sync in-memory cache with external DB changes using sqlite data_version
36
+ try {
37
+ const currentDataVersion = db.pragma('data_version', { simple: true });
38
+ if (currentDataVersion !== lastDataVersion) {
39
+ searchCache.invalidate();
40
+ lastDataVersion = currentDataVersion;
41
+ }
42
+ } catch (_) {
43
+ // Fallback if pragma fails
44
+ }
45
+
33
46
  // --- Check LRU cache first (Feature 1) ---
34
47
  const cacheKey = LRUCache.key(queryText, limit);
35
48
  const cached = searchCache.get(cacheKey);
package/src/server.js CHANGED
@@ -23,7 +23,7 @@ export async function startServer() {
23
23
  // --- Create MCP server ---
24
24
  const server = new McpServer({
25
25
  name: 'persyst',
26
- version: '2.1.0'
26
+ version: '2.1.1'
27
27
  });
28
28
 
29
29
  // --- Register all tools ---