@misterhuydo/cairn-mcp 1.6.2 → 1.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +10 -0
- package/package.json +1 -1
- package/src/graph/db.js +45 -0
- package/src/indexer/parsers/javaParser.js +6 -3
- package/src/tools/todos.js +3 -2
package/.gitattributes
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misterhuydo/cairn-mcp",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.5",
|
|
4
4
|
"description": "MCP server that gives Claude Code persistent memory across sessions. Index your codebase once, search symbols, bundle source, scan for vulnerabilities, and checkpoint/resume work — across Java, TypeScript, Vue, Python, SQL and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/src/graph/db.js
CHANGED
|
@@ -75,6 +75,50 @@ const SCHEMA = `
|
|
|
75
75
|
// across federated TEMP VIEWs. Supports up to 1M rows per sub-project.
|
|
76
76
|
const ID_OFFSET = 1_000_000;
|
|
77
77
|
|
|
78
|
+
// Walk up from currentRoot to find the nearest ancestor .cairn/index.db.
|
|
79
|
+
// Returns the absolute path, or null if none found.
|
|
80
|
+
function findParentCairnDb(currentRoot) {
|
|
81
|
+
let dir = path.dirname(path.resolve(currentRoot));
|
|
82
|
+
while (true) {
|
|
83
|
+
const candidate = path.join(dir, '.cairn', 'index.db');
|
|
84
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
85
|
+
const parent = path.dirname(dir);
|
|
86
|
+
if (parent === dir) return null; // filesystem root
|
|
87
|
+
dir = parent;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// If a parent .cairn exists, register its index.db (root-level files) and all
|
|
92
|
+
// of its sub_indexes (siblings) into the current project's sub_indexes table.
|
|
93
|
+
// This gives a child session full cross-service visibility automatically.
|
|
94
|
+
function mountParentSubIndexes(db) {
|
|
95
|
+
const currentRoot = path.resolve(getProjectRoot());
|
|
96
|
+
const parentDbPath = findParentCairnDb(currentRoot);
|
|
97
|
+
if (!parentDbPath) return;
|
|
98
|
+
|
|
99
|
+
const selfDbPath = path.resolve(path.join(getCairnDir(), 'index.db'));
|
|
100
|
+
const stmt = db.prepare('INSERT OR IGNORE INTO main.sub_indexes (path) VALUES (?)');
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// Register the parent's own index (root-level files, shared configs, etc.)
|
|
104
|
+
if (path.resolve(parentDbPath) !== selfDbPath) {
|
|
105
|
+
stmt.run(path.resolve(parentDbPath));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Register all siblings from the parent's sub_indexes (excluding self)
|
|
109
|
+
const parentDb = new DatabaseSync(parentDbPath, { readonly: true });
|
|
110
|
+
const siblings = parentDb.prepare('SELECT path FROM sub_indexes').all();
|
|
111
|
+
parentDb.close();
|
|
112
|
+
|
|
113
|
+
for (const { path: sibPath } of siblings) {
|
|
114
|
+
const resolved = path.resolve(sibPath);
|
|
115
|
+
if (resolved !== selfDbPath) stmt.run(resolved);
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// Parent DB unreadable or schema mismatch — skip silently
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
78
122
|
// Required tables for a sub-index to be considered valid.
|
|
79
123
|
const REQUIRED_TABLES = ['files', 'symbols', 'dependencies', 'security_findings'];
|
|
80
124
|
|
|
@@ -125,6 +169,7 @@ export function openDB() {
|
|
|
125
169
|
db.exec('PRAGMA journal_mode = WAL');
|
|
126
170
|
db.exec('PRAGMA synchronous = NORMAL');
|
|
127
171
|
db.exec(SCHEMA);
|
|
172
|
+
mountParentSubIndexes(db);
|
|
128
173
|
refreshFederatedViews(db);
|
|
129
174
|
return db;
|
|
130
175
|
}
|
|
@@ -9,7 +9,7 @@ export function parseJava(filePath, content, repoName) {
|
|
|
9
9
|
const kind = classMatch?.[0]?.match(/class|interface|enum|record/)?.[0] || 'class';
|
|
10
10
|
const fqn = pkg ? `${pkg}.${name}` : name;
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const methodNames = [...content.matchAll(
|
|
13
13
|
/(?:public|protected|private)[^{;]*\s+(\w+)\s*\([^)]*\)\s*(?:throws[^{]*)?\{/gm
|
|
14
14
|
)].map(m => m[1]);
|
|
15
15
|
|
|
@@ -20,12 +20,15 @@ export function parseJava(filePath, content, repoName) {
|
|
|
20
20
|
const javadoc = content.match(/\/\*\*([\s\S]*?)\*\//)?.[1]
|
|
21
21
|
?.replace(/\s*\*\s?/g, ' ').trim();
|
|
22
22
|
|
|
23
|
+
const methodSymbols = fqn
|
|
24
|
+
? methodNames.map(m => ({ name: m, fqn: `${fqn}.${m}`, kind: 'method', exported: false, description: '' }))
|
|
25
|
+
: [];
|
|
26
|
+
|
|
23
27
|
return {
|
|
24
28
|
language: 'java',
|
|
25
|
-
symbols: [{ name, fqn, kind, exported: true, description: javadoc || '' }],
|
|
29
|
+
symbols: [{ name, fqn, kind, exported: true, description: javadoc || '' }, ...methodSymbols],
|
|
26
30
|
imports,
|
|
27
31
|
extends: extendsMatch ? [extendsMatch] : [],
|
|
28
32
|
implements: implementsMatch || [],
|
|
29
|
-
methods,
|
|
30
33
|
};
|
|
31
34
|
}
|
package/src/tools/todos.js
CHANGED
|
@@ -2,7 +2,8 @@ import { walkRepo } from '../indexer/fileWalker.js';
|
|
|
2
2
|
import { scanTodos } from '../indexer/todoScanner.js';
|
|
3
3
|
|
|
4
4
|
export async function todos(db, args = {}) {
|
|
5
|
-
const {
|
|
5
|
+
const { text, id, status, source } = args;
|
|
6
|
+
const action = args.action ?? 'list';
|
|
6
7
|
const kind = args.kind;
|
|
7
8
|
|
|
8
9
|
switch (action) {
|
|
@@ -63,6 +64,6 @@ export async function todos(db, args = {}) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
default:
|
|
66
|
-
throw new Error(`Unknown action: ${action}. Use list, add, resolve, or scan.`);
|
|
67
|
+
throw new Error(`Unknown action: "${action}". Use list, add, resolve, or scan.`);
|
|
67
68
|
}
|
|
68
69
|
}
|