@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 ADDED
@@ -0,0 +1,10 @@
1
+ # Normalize line endings to LF for all text files
2
+ * text=auto eol=lf
3
+
4
+ # Explicitly binary — never touch
5
+ *.png binary
6
+ *.jpg binary
7
+ *.jpeg binary
8
+ *.gif binary
9
+ *.ico binary
10
+ *.db binary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misterhuydo/cairn-mcp",
3
- "version": "1.6.2",
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 methods = [...content.matchAll(
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
  }
@@ -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 { action, text, id, status, source } = args;
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
  }