@misterhuydo/cairn-mcp 1.2.1 → 1.2.2

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 CHANGED
@@ -36,6 +36,28 @@ Restart Claude Code. Done.
36
36
 
37
37
  ---
38
38
 
39
+ ## Passive hooks (recommended)
40
+
41
+ Install once and Cairn works automatically — no need to call `cairn_bundle` or `cairn_checkpoint` manually.
42
+
43
+ **Global (all projects):**
44
+
45
+ ```bash
46
+ cairn install-hooks --global
47
+ ```
48
+
49
+ **Project-only:**
50
+
51
+ ```bash
52
+ cairn install-hooks
53
+ ```
54
+
55
+ | Hook | Trigger | Effect |
56
+ |---|---|---|
57
+ | `PreToolUse[Read]` | Every file read | Source files compressed ~68% before Claude sees them |
58
+ | `Stop` | Every response | Session auto-saved to `.cairn/session.json` |
59
+ | `UserPromptSubmit` | First message of session | Claude reminded of prior session, prompted to call `cairn_resume` |
60
+
39
61
  ## How it works
40
62
 
41
63
  Cairn works like git — it looks for `.cairn/` in your current working directory. Run Claude Code from your project root and Cairn automatically stores the index at `.cairn/index.db` and bundles at `.cairn/bundles/`.
@@ -158,6 +180,15 @@ Covers: XSS · XXE · SQL injection · command injection · weak crypto · hardc
158
180
 
159
181
  ---
160
182
 
183
+ ### `cairn_minify` — Minify a single file
184
+ Minify one source file on demand. Returns compressed content with a `[cairn: N → M lines]` header. Useful when passive hooks are not installed.
185
+
186
+ ```
187
+ cairn_minify({ file_path: "/abs/path/to/Service.java" })
188
+ ```
189
+
190
+ ---
191
+
161
192
  ### `cairn_checkpoint` — Save session state
162
193
  Tell Cairn what you were working on so the next session can pick up where you left off.
163
194
 
@@ -223,7 +254,16 @@ cairn_resume()
223
254
 
224
255
  ## Typical session
225
256
 
226
- **Fresh start:**
257
+ **With passive hooks (recommended):**
258
+ ```
259
+ 1. cairn_maintain → index project
260
+ 2. cairn_search → find the symbols you need
261
+ 3. cairn_describe → understand a module before modifying it
262
+ 4. cairn_security → check for issues before a PR
263
+ (reads auto-compressed, session auto-saved — nothing else needed)
264
+ ```
265
+
266
+ **Without hooks:**
227
267
  ```
228
268
  1. cairn_maintain → index project (persists between sessions)
229
269
  2. cairn_search → find the symbols you need
@@ -251,16 +291,14 @@ Claude: cairn_resume
251
291
  Ready to continue."
252
292
 
253
293
  ─── DURING SESSION ────────────────────────────────────────────
254
- Claude uses: cairn_search, cairn_bundle, cairn_code_graph, cairn_security
294
+ Claude uses: cairn_search, cairn_code_graph, cairn_security
255
295
  as needed — all reading from .cairn/index.db in cwd
296
+ (file reads auto-compressed by PreToolUse hook)
256
297
 
257
298
  ─── END OF SESSION ────────────────────────────────────────────
258
299
  You: "Ok let's stop here for today"
259
- Claude: cairn_checkpoint
260
- message="Fixed EUR formatting in PaymentStep, multi-currency complete"
261
- active_files=["src/components/checkout/PaymentStep.vue"]
262
- notes=["Still need to add JPY — no decimal places", "QA needs to test AED"]
263
- → "Session saved. Resume anytime with cairn_resume."
300
+ (Stop hook fires → .cairn/session.json auto-saved)
301
+ Next session: cairn_resume picks up automatically
264
302
  ```
265
303
 
266
304
  ---
package/bin/cairn-cli.js CHANGED
@@ -9,6 +9,12 @@ import { checkpoint } from '../src/tools/checkpoint.js';
9
9
 
10
10
  const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
11
11
 
12
+ // Ensure any unexpected crash in a hook subcommand passes through rather than blocking
13
+ process.on('uncaughtException', (e) => {
14
+ process.stderr.write(`cairn: ${e.message}\n`);
15
+ process.exit(0);
16
+ });
17
+
12
18
  const subcommand = process.argv[2];
13
19
 
14
20
  if (subcommand === 'minify') {
@@ -56,6 +62,27 @@ if (subcommand === 'minify') {
56
62
  checkpoint(null, { message, active_files: [], notes: [] });
57
63
  process.exit(0);
58
64
 
65
+ } else if (subcommand === 'resume-hint') {
66
+ const cairnDir = path.join(process.cwd(), '.cairn');
67
+ const sessionPath = path.join(cairnDir, 'session.json');
68
+
69
+ if (!fs.existsSync(sessionPath)) process.exit(0);
70
+
71
+ // Rate-limit: show at most once every 30 minutes to avoid spamming mid-session
72
+ const lockPath = path.join(cairnDir, '.hint-lock');
73
+ const LOCK_TTL_MS = 30 * 60 * 1000;
74
+ if (fs.existsSync(lockPath)) {
75
+ const age = Date.now() - fs.statSync(lockPath).mtimeMs;
76
+ if (age < LOCK_TTL_MS) process.exit(0);
77
+ }
78
+
79
+ const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
80
+ const when = new Date(session.checkpoint_at).toLocaleString();
81
+ process.stdout.write(`[cairn] Prior session: "${session.message}" (${when}). Call cairn_resume to restore prior context.\n`);
82
+
83
+ fs.writeFileSync(lockPath, new Date().toISOString(), 'utf8');
84
+ process.exit(0);
85
+
59
86
  } else if (subcommand === 'install-hooks') {
60
87
  const isGlobal = process.argv.includes('--global');
61
88
  const settingsDir = isGlobal
@@ -74,7 +101,7 @@ if (subcommand === 'minify') {
74
101
 
75
102
  settings.hooks = settings.hooks || {};
76
103
 
77
- // Add PreToolUse hook for Read
104
+ // PreToolUse[Read] cairn minify
78
105
  const preHooks = settings.hooks.PreToolUse || [];
79
106
  const hasMinify = preHooks.some(h =>
80
107
  h.matcher === 'Read' && h.hooks?.some(hh => hh.command === 'cairn minify')
@@ -84,7 +111,7 @@ if (subcommand === 'minify') {
84
111
  settings.hooks.PreToolUse = preHooks;
85
112
  }
86
113
 
87
- // Add Stop hook for auto-checkpoint
114
+ // Stop cairn checkpoint --auto
88
115
  const stopHooks = settings.hooks.Stop || [];
89
116
  const hasCheckpoint = stopHooks.some(h =>
90
117
  h.hooks?.some(hh => hh.command === 'cairn checkpoint --auto')
@@ -94,6 +121,16 @@ if (subcommand === 'minify') {
94
121
  settings.hooks.Stop = stopHooks;
95
122
  }
96
123
 
124
+ // UserPromptSubmit → cairn resume-hint
125
+ const submitHooks = settings.hooks.UserPromptSubmit || [];
126
+ const hasResumeHint = submitHooks.some(h =>
127
+ h.hooks?.some(hh => hh.command === 'cairn resume-hint')
128
+ );
129
+ if (!hasResumeHint) {
130
+ submitHooks.push({ hooks: [{ type: 'command', command: 'cairn resume-hint' }] });
131
+ settings.hooks.UserPromptSubmit = submitHooks;
132
+ }
133
+
97
134
  fs.mkdirSync(settingsDir, { recursive: true });
98
135
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
99
136
 
@@ -101,11 +138,12 @@ if (subcommand === 'minify') {
101
138
  console.log(`Cairn hooks installed in ${scope}`);
102
139
  console.log('');
103
140
  console.log('Active hooks:');
104
- console.log(' PreToolUse[Read] → cairn minify (compress source files)');
105
- console.log(' Stop → cairn checkpoint --auto (auto-save session)');
141
+ console.log(' PreToolUse[Read] → cairn minify (compress source files)');
142
+ console.log(' Stop → cairn checkpoint --auto (auto-save session)');
143
+ console.log(' UserPromptSubmit → cairn resume-hint (remind Claude of prior session)');
106
144
  process.exit(0);
107
145
 
108
146
  } else {
109
- console.error('Usage: cairn <minify|checkpoint --auto|install-hooks>');
147
+ console.error('Usage: cairn <minify|checkpoint --auto|resume-hint|install-hooks [--global]>');
110
148
  process.exit(1);
111
149
  }
package/bin/cairn.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import { spawn } from 'child_process';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { dirname, join } from 'path';
package/index.js CHANGED
@@ -1,165 +1,178 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
-
5
- import { openDB } from './src/graph/db.js';
6
- import { maintain } from './src/tools/maintain.js';
7
- import { search } from './src/tools/search.js';
8
- import { describe } from './src/tools/describe.js';
9
- import { codeGraph } from './src/tools/codeGraph.js';
10
- import { security } from './src/tools/security.js';
11
- import { bundle } from './src/tools/bundle.js';
12
- import { checkpoint } from './src/tools/checkpoint.js';
13
- import { resume } from './src/tools/resume.js';
14
-
15
- const db = openDB();
16
-
17
- const server = new Server(
18
- { name: 'cairn', version: '1.0.0' },
19
- { capabilities: { tools: {} } }
20
- );
21
-
22
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
23
- tools: [
24
- {
25
- name: 'cairn_maintain',
26
- description: 'Index the current project into Cairn\'s polyglot knowledge graph. Supports Java, TypeScript, JavaScript, Vue, Python, SQL, config, and Markdown. Indexes from the current working directory. Run at session start or after major changes.',
27
- inputSchema: {
28
- type: 'object',
29
- properties: {
30
- languages: {
31
- type: 'array', items: { type: 'string' },
32
- description: 'Optional: limit to specific languages e.g. ["java","typescript"]',
33
- },
34
- },
35
- },
36
- },
37
- {
38
- name: 'cairn_search',
39
- description: 'Search the codebase by concept across all languages. Returns ranked symbols with scores.',
40
- inputSchema: {
41
- type: 'object',
42
- properties: {
43
- query: { type: 'string' },
44
- limit: { type: 'number', default: 10 },
45
- language: { type: 'string', description: 'Optional: filter by language' },
46
- kind: { type: 'string', description: 'Optional: class/function/component/table/...' },
47
- },
48
- required: ['query'],
49
- },
50
- },
51
- {
52
- name: 'cairn_describe',
53
- description: 'Describe what a directory/module does: symbols, responsibilities, upstream/downstream deps.',
54
- inputSchema: {
55
- type: 'object',
56
- properties: {
57
- path: { type: 'string', description: 'Directory or file path to describe' },
58
- },
59
- required: ['path'],
60
- },
61
- },
62
- {
63
- name: 'cairn_code_graph',
64
- description: 'Analyze module dependencies and instability metrics across all languages. Use before refactoring.',
65
- inputSchema: {
66
- type: 'object',
67
- properties: {
68
- module: { type: 'string', description: 'Optional: scope to a specific module/package' },
69
- mode: { type: 'string', enum: ['instability', 'health', 'cycles'] },
70
- },
71
- required: ['mode'],
72
- },
73
- },
74
- {
75
- name: 'cairn_security',
76
- description: 'Scan for security vulnerabilities across all languages (XSS, XXE, SQLi, weak crypto, hardcoded secrets, etc.)',
77
- inputSchema: {
78
- type: 'object',
79
- properties: {
80
- paths: { type: 'array', items: { type: 'string' }, description: 'Optional: scope to specific paths' },
81
- severity: { type: 'string', enum: ['HIGH', 'MEDIUM', 'LOW', 'ALL'], default: 'HIGH' },
82
- language: { type: 'string', description: 'Optional: scope to a specific language' },
83
- },
84
- },
85
- },
86
- {
87
- name: 'cairn_bundle',
88
- description: `Produce a minified single-file snapshot of source code for Claude to read.
89
- Strips comments, empty lines, and optionally styles. Returns the bundle file path and compression stats.
90
- Use AFTER cairn_search to get a readable version of the files Claude needs to work with.
91
- Typical workflow: cairn_search find files cairn_bundle those paths Claude reads bundle.`,
92
- inputSchema: {
93
- type: 'object',
94
- properties: {
95
- bundle_name: {
96
- type: 'string', default: 'default',
97
- description: 'Name for the bundle file (saved to .cairn/bundles/<name>.txt)',
98
- },
99
- filter_paths: {
100
- type: 'array', items: { type: 'string' },
101
- description: 'Optional: only include files under these relative paths',
102
- },
103
- filter_language: {
104
- type: 'string',
105
- description: 'Optional: only bundle this language (java/typescript/vue/python/...)',
106
- },
107
- no_comments: { type: 'boolean', default: true, description: 'Strip code comments' },
108
- no_empty_lines: { type: 'boolean', default: true, description: 'Remove empty lines' },
109
- no_style: { type: 'boolean', default: false, description: 'Skip <style> blocks in Vue files' },
110
- aggressive: { type: 'boolean', default: false, description: 'Aggressive whitespace/punctuation minification' },
111
- only_changed: { type: 'boolean', default: false, description: 'Incremental: only re-bundle files changed since last run' },
112
- max_size_kb: { type: 'number', default: 800, description: 'Safety cap in KB to avoid context window overflow' },
113
- },
114
- },
115
- },
116
- {
117
- name: 'cairn_checkpoint',
118
- description: 'Save current session state to .cairn/session.json so it can be restored next session with cairn_resume.',
119
- inputSchema: {
120
- type: 'object',
121
- properties: {
122
- message: {
123
- type: 'string',
124
- description: 'What you were working on — will be shown on resume',
125
- },
126
- active_files: {
127
- type: 'array', items: { type: 'string' },
128
- description: 'Optional: absolute paths to files actively being worked on',
129
- },
130
- notes: {
131
- type: 'array', items: { type: 'string' },
132
- description: 'Optional: things Claude should remember next session',
133
- },
134
- },
135
- required: ['message'],
136
- },
137
- },
138
- {
139
- name: 'cairn_resume',
140
- description: 'Restore the last saved session state. Detects which files changed since the checkpoint and incrementally re-indexes only those files. Call at the start of a session instead of cairn_maintain when resuming work.',
141
- inputSchema: {
142
- type: 'object',
143
- properties: {},
144
- },
145
- },
146
- ],
147
- }));
148
-
149
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
150
- const { name, arguments: args } = request.params;
151
- switch (name) {
152
- case 'cairn_maintain': return await maintain(db, args);
153
- case 'cairn_search': return search(db, args);
154
- case 'cairn_describe': return describe(db, args);
155
- case 'cairn_code_graph': return codeGraph(db, args);
156
- case 'cairn_security': return security(db, args);
157
- case 'cairn_bundle': return await bundle(db, args);
158
- case 'cairn_checkpoint': return checkpoint(db, args);
159
- case 'cairn_resume': return await resume(db);
160
- default: throw new Error(`Unknown tool: ${name}`);
161
- }
162
- });
163
-
164
- const transport = new StdioServerTransport();
165
- await server.connect(transport);
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
4
+
5
+ import { openDB } from './src/graph/db.js';
6
+ import { maintain } from './src/tools/maintain.js';
7
+ import { search } from './src/tools/search.js';
8
+ import { describe } from './src/tools/describe.js';
9
+ import { codeGraph } from './src/tools/codeGraph.js';
10
+ import { security } from './src/tools/security.js';
11
+ import { bundle } from './src/tools/bundle.js';
12
+ import { checkpoint } from './src/tools/checkpoint.js';
13
+ import { resume } from './src/tools/resume.js';
14
+ import { minify } from './src/tools/minify.js';
15
+
16
+ const db = openDB();
17
+
18
+ const server = new Server(
19
+ { name: 'cairn', version: '1.0.0' },
20
+ { capabilities: { tools: {} } }
21
+ );
22
+
23
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
24
+ tools: [
25
+ {
26
+ name: 'cairn_maintain',
27
+ description: 'Index the current project into Cairn\'s polyglot knowledge graph. Supports Java, TypeScript, JavaScript, Vue, Python, SQL, config, and Markdown. Indexes from the current working directory. Run at session start or after major changes.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ languages: {
32
+ type: 'array', items: { type: 'string' },
33
+ description: 'Optional: limit to specific languages e.g. ["java","typescript"]',
34
+ },
35
+ },
36
+ },
37
+ },
38
+ {
39
+ name: 'cairn_search',
40
+ description: 'Search the codebase by concept across all languages. Returns ranked symbols with scores.',
41
+ inputSchema: {
42
+ type: 'object',
43
+ properties: {
44
+ query: { type: 'string' },
45
+ limit: { type: 'number', default: 10 },
46
+ language: { type: 'string', description: 'Optional: filter by language' },
47
+ kind: { type: 'string', description: 'Optional: class/function/component/table/...' },
48
+ },
49
+ required: ['query'],
50
+ },
51
+ },
52
+ {
53
+ name: 'cairn_describe',
54
+ description: 'Describe what a directory/module does: symbols, responsibilities, upstream/downstream deps.',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ path: { type: 'string', description: 'Directory or file path to describe' },
59
+ },
60
+ required: ['path'],
61
+ },
62
+ },
63
+ {
64
+ name: 'cairn_code_graph',
65
+ description: 'Analyze module dependencies and instability metrics across all languages. Use before refactoring.',
66
+ inputSchema: {
67
+ type: 'object',
68
+ properties: {
69
+ module: { type: 'string', description: 'Optional: scope to a specific module/package' },
70
+ mode: { type: 'string', enum: ['instability', 'health', 'cycles'] },
71
+ },
72
+ required: ['mode'],
73
+ },
74
+ },
75
+ {
76
+ name: 'cairn_security',
77
+ description: 'Scan for security vulnerabilities across all languages (XSS, XXE, SQLi, weak crypto, hardcoded secrets, etc.)',
78
+ inputSchema: {
79
+ type: 'object',
80
+ properties: {
81
+ paths: { type: 'array', items: { type: 'string' }, description: 'Optional: scope to specific paths' },
82
+ severity: { type: 'string', enum: ['HIGH', 'MEDIUM', 'LOW', 'ALL'], default: 'HIGH' },
83
+ language: { type: 'string', description: 'Optional: scope to a specific language' },
84
+ },
85
+ },
86
+ },
87
+ {
88
+ name: 'cairn_bundle',
89
+ description: `Produce a minified single-file snapshot of source code for Claude to read.
90
+ Strips comments, empty lines, and optionally styles. Returns the bundle file path and compression stats.
91
+ Use AFTER cairn_search to get a readable version of the files Claude needs to work with.
92
+ Typical workflow: cairn_search → find files → cairn_bundle those paths → Claude reads bundle.`,
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ bundle_name: {
97
+ type: 'string', default: 'default',
98
+ description: 'Name for the bundle file (saved to .cairn/bundles/<name>.txt)',
99
+ },
100
+ filter_paths: {
101
+ type: 'array', items: { type: 'string' },
102
+ description: 'Optional: only include files under these relative paths',
103
+ },
104
+ filter_language: {
105
+ type: 'string',
106
+ description: 'Optional: only bundle this language (java/typescript/vue/python/...)',
107
+ },
108
+ no_comments: { type: 'boolean', default: true, description: 'Strip code comments' },
109
+ no_empty_lines: { type: 'boolean', default: true, description: 'Remove empty lines' },
110
+ no_style: { type: 'boolean', default: false, description: 'Skip <style> blocks in Vue files' },
111
+ aggressive: { type: 'boolean', default: false, description: 'Aggressive whitespace/punctuation minification' },
112
+ only_changed: { type: 'boolean', default: false, description: 'Incremental: only re-bundle files changed since last run' },
113
+ max_size_kb: { type: 'number', default: 800, description: 'Safety cap in KB to avoid context window overflow' },
114
+ },
115
+ },
116
+ },
117
+ {
118
+ name: 'cairn_checkpoint',
119
+ description: 'Save current session state to .cairn/session.json so it can be restored next session with cairn_resume.',
120
+ inputSchema: {
121
+ type: 'object',
122
+ properties: {
123
+ message: {
124
+ type: 'string',
125
+ description: 'What you were working on — will be shown on resume',
126
+ },
127
+ active_files: {
128
+ type: 'array', items: { type: 'string' },
129
+ description: 'Optional: absolute paths to files actively being worked on',
130
+ },
131
+ notes: {
132
+ type: 'array', items: { type: 'string' },
133
+ description: 'Optional: things Claude should remember next session',
134
+ },
135
+ },
136
+ required: ['message'],
137
+ },
138
+ },
139
+ {
140
+ name: 'cairn_resume',
141
+ description: 'Restore the last saved session state. Detects which files changed since the checkpoint and incrementally re-indexes only those files. Call at the start of a session instead of cairn_maintain when resuming work.',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {},
145
+ },
146
+ },
147
+ {
148
+ name: 'cairn_minify',
149
+ description: 'Minify a single source file and return compressed content. Strips comments and empty lines. Useful when passive hooks are not installed. Supports java, typescript, javascript, vue, python, sql.',
150
+ inputSchema: {
151
+ type: 'object',
152
+ properties: {
153
+ file_path: { type: 'string', description: 'Absolute path to the file to minify' },
154
+ },
155
+ required: ['file_path'],
156
+ },
157
+ },
158
+ ],
159
+ }));
160
+
161
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
162
+ const { name, arguments: args } = request.params;
163
+ switch (name) {
164
+ case 'cairn_maintain': return await maintain(db, args);
165
+ case 'cairn_search': return search(db, args);
166
+ case 'cairn_describe': return describe(db, args);
167
+ case 'cairn_code_graph': return codeGraph(db, args);
168
+ case 'cairn_security': return security(db, args);
169
+ case 'cairn_bundle': return await bundle(db, args);
170
+ case 'cairn_checkpoint': return checkpoint(db, args);
171
+ case 'cairn_resume': return await resume(db);
172
+ case 'cairn_minify': return minify(db, args);
173
+ default: throw new Error(`Unknown tool: ${name}`);
174
+ }
175
+ });
176
+
177
+ const transport = new StdioServerTransport();
178
+ await server.connect(transport);
package/package.json CHANGED
@@ -1,44 +1,44 @@
1
- {
2
- "name": "@misterhuydo/cairn-mcp",
3
- "version": "1.2.1",
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
- "type": "module",
6
- "main": "index.js",
7
- "bin": {
8
- "cairn-mcp": "bin/cairn-mcp.js",
9
- "cairn": "bin/cairn.js"
10
- },
11
- "scripts": {
12
- "start": "node --experimental-sqlite index.js"
13
- },
14
- "engines": {
15
- "node": ">=22.15.0"
16
- },
17
- "keywords": [
18
- "mcp",
19
- "claude",
20
- "claude-code",
21
- "knowledge-graph",
22
- "codebase-search",
23
- "polyglot",
24
- "java",
25
- "typescript",
26
- "vue",
27
- "python",
28
- "sqlite",
29
- "developer-tools"
30
- ],
31
- "license": "MIT",
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/misterhuydo/Cairn.git"
35
- },
36
- "homepage": "https://github.com/misterhuydo/Cairn#readme",
37
- "bugs": {
38
- "url": "https://github.com/misterhuydo/Cairn/issues"
39
- },
40
- "dependencies": {
41
- "@modelcontextprotocol/sdk": "^1.0.0",
42
- "fast-glob": "^3.3.0"
43
- }
44
- }
1
+ {
2
+ "name": "@misterhuydo/cairn-mcp",
3
+ "version": "1.2.2",
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
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "cairn-mcp": "bin/cairn-mcp.js",
9
+ "cairn": "bin/cairn.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node --experimental-sqlite index.js"
13
+ },
14
+ "engines": {
15
+ "node": ">=22.15.0"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "claude",
20
+ "claude-code",
21
+ "knowledge-graph",
22
+ "codebase-search",
23
+ "polyglot",
24
+ "java",
25
+ "typescript",
26
+ "vue",
27
+ "python",
28
+ "sqlite",
29
+ "developer-tools"
30
+ ],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/misterhuydo/Cairn.git"
35
+ },
36
+ "homepage": "https://github.com/misterhuydo/Cairn#readme",
37
+ "bugs": {
38
+ "url": "https://github.com/misterhuydo/Cairn/issues"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.0.0",
42
+ "fast-glob": "^3.3.0"
43
+ }
44
+ }
@@ -0,0 +1,40 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { LANGUAGE_MAP } from '../indexer/fileWalker.js';
4
+ import { minifyContent } from '../bundler/minifier.js';
5
+ import { minifyVue } from '../bundler/vueMinifier.js';
6
+
7
+ const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
8
+
9
+ export function minify(_db, { file_path }) {
10
+ if (!file_path) throw new Error('file_path is required');
11
+
12
+ const ext = path.extname(file_path).toLowerCase();
13
+ const lang = LANGUAGE_MAP[ext];
14
+
15
+ if (!lang || !MINIFY_LANGS.has(lang)) {
16
+ return {
17
+ content: [{
18
+ type: 'text',
19
+ text: JSON.stringify({ skipped: true, reason: `language '${lang ?? ext}' is not minified` }),
20
+ }],
21
+ };
22
+ }
23
+
24
+ const content = fs.readFileSync(file_path, 'utf8');
25
+ const originalLines = content.split('\n').length;
26
+
27
+ const minified = lang === 'vue'
28
+ ? minifyVue(content)
29
+ : minifyContent(content, lang);
30
+
31
+ const minifiedLines = minified.split('\n').length;
32
+ const pct = originalLines > 0 ? Math.round((1 - minifiedLines / originalLines) * 100) : 0;
33
+
34
+ return {
35
+ content: [{
36
+ type: 'text',
37
+ text: `[cairn: ${originalLines} → ${minifiedLines} lines (-${pct}%)]\n${minified}`,
38
+ }],
39
+ };
40
+ }