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 +82 -0
- package/bin/init.js +173 -0
- package/index.js +13 -0
- package/package.json +4 -2
- package/src/search.js +13 -0
- package/src/server.js +1 -1
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.
|
|
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);
|