@misterhuydo/cairn-mcp 1.2.1 → 1.2.4

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
@@ -1,270 +1,309 @@
1
- # Cairn MCP
2
-
3
- > Persistent polyglot knowledge graph for Claude Code. Index once, query forever.
4
-
5
- Claude is stateless — every session starts at zero. On a multi-repo codebase with Java backends, TypeScript frontends, and Vue components, that means 50% of every session is archaeology: finding where things live before any real work can begin.
6
-
7
- **Cairn fixes this.** It indexes your project into a local SQLite knowledge graph (stored in `.cairn/`) and exposes 8 MCP tools that give Claude instant, persistent memory across sessions.
8
-
9
- ---
10
-
11
- ## Install
12
-
13
- ```bash
14
- npm install -g @misterhuydo/cairn-mcp
15
- ```
16
-
17
- **Requirements:** Node.js >= 22.15.0
18
-
19
- ---
20
-
21
- ## Setup
22
-
23
- Add to `~/.claude.json`:
24
-
25
- ```json
26
- {
27
- "mcpServers": {
28
- "cairn": {
29
- "command": "cairn-mcp"
30
- }
31
- }
32
- }
33
- ```
34
-
35
- Restart Claude Code. Done.
36
-
37
- ---
38
-
39
- ## How it works
40
-
41
- 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/`.
42
-
43
- No root paths needed. No global config. Just `cd` to your project and go.
44
-
45
- ---
46
-
47
- ## Tools
48
-
49
- ### `cairn_maintain` — Index your project
50
- Run once at the start of each session. The index persists in `.cairn/index.db`.
51
-
52
- ```
53
- cairn_maintain()
54
- cairn_maintain({ languages: ["typescript", "vue"] }) // limit to specific languages
55
- ```
56
-
57
- ```json
58
- {
59
- "repos_indexed": 1,
60
- "files_by_language": { "java": 412, "typescript": 287, "vue": 94 },
61
- "symbols_total": 6841,
62
- "security_findings": 47,
63
- "duration_ms": 4200
64
- }
65
- ```
66
-
67
- ---
68
-
69
- ### `cairn_search` — Find anything across all languages
70
- ```
71
- cairn_search({ query: "user authentication", language: "typescript" })
72
- ```
73
-
74
- ```json
75
- [
76
- { "lang": "typescript", "kind": "function", "fqn": "src/composables/useAuth.ts::useAuth" },
77
- { "lang": "vue", "kind": "component", "fqn": "src/components/LoginForm.vue::LoginForm" },
78
- { "lang": "java", "kind": "class", "fqn": "com.example.auth.UserAuthenticator" }
79
- ]
80
- ```
81
-
82
- ---
83
-
84
- ### `cairn_bundle` — Minified source snapshot for Claude to read
85
- Strips comments and empty lines, writes a compressed `### File:` bundle. Use after `cairn_search` to give Claude readable source without blowing the context window.
86
-
87
- ```
88
- cairn_bundle({ filter_paths: ["src/components/checkout"], bundle_name: "checkout" })
89
- ```
90
-
91
- ```json
92
- {
93
- "bundle_path": "/your/project/.cairn/bundles/checkout.txt",
94
- "original_kb": 284,
95
- "compressed_kb": 91,
96
- "reduction_pct": 68
97
- }
98
- ```
99
-
100
- ---
101
-
102
- ### `cairn_describe` — Summarize a module
103
- ```
104
- cairn_describe({ path: "src/components/checkout" })
105
- ```
106
-
107
- ```json
108
- {
109
- "languages": ["vue", "typescript"],
110
- "symbols": {
111
- "component": ["CheckoutForm", "OrderSummary", "PaymentStep"],
112
- "function": ["useCheckout", "usePayment"]
113
- },
114
- "imports_from": ["src/store/cart.ts", "src/api/orders.ts"],
115
- "imported_by": ["src/views/CartView.vue"],
116
- "external_deps": ["stripe", "@stripe/stripe-js"]
117
- }
118
- ```
119
-
120
- ---
121
-
122
- ### `cairn_code_graph` — Dependency health
123
- ```
124
- cairn_code_graph({ mode: "instability" })
125
- ```
126
-
127
- ```json
128
- {
129
- "modules": [
130
- { "name": "src/views", "instability": 1.0, "status": "safe_to_refactor" },
131
- { "name": "src/composables", "instability": 0.4, "status": "review_before_change" },
132
- { "name": "src/utils", "instability": 0.1, "status": "load_bearing" }
133
- ]
134
- }
135
- ```
136
-
137
- Modes: `instability` · `health` · `cycles`
138
-
139
- ---
140
-
141
- ### `cairn_security` — Vulnerability scan
142
- ```
143
- cairn_security({ severity: "HIGH" })
144
- ```
145
-
146
- ```json
147
- {
148
- "total_findings": 12,
149
- "by_language": { "typescript": 7, "java": 5 },
150
- "findings": [
151
- { "severity": "HIGH", "cwe": "CWE-79", "file": "src/components/UserProfile.vue", "line": 84, "rule": "XSS via innerHTML" },
152
- { "severity": "HIGH", "cwe": "CWE-611", "file": "backend/src/.../XmlParser.java", "line": 47, "rule": "XXE" }
153
- ]
154
- }
155
- ```
156
-
157
- Covers: XSS · XXE · SQL injection · command injection · weak crypto · hardcoded secrets · open redirect · unsafe deserialization
158
-
159
- ---
160
-
161
- ### `cairn_checkpoint` — Save session state
162
- Tell Cairn what you were working on so the next session can pick up where you left off.
163
-
164
- ```
165
- cairn_checkpoint({
166
- message: "Added multi-currency to CheckoutForm, still need PaymentStep",
167
- active_files: ["src/components/checkout/CheckoutForm.vue"],
168
- notes: ["CurrencyService expects ISO 4217 codes, not symbols"]
169
- })
170
- ```
171
-
172
- ```json
173
- {
174
- "saved": true,
175
- "checkpoint_at": "2026-02-25T14:30:00Z",
176
- "active_files_tracked": 1,
177
- "notes_saved": 1
178
- }
179
- ```
180
-
181
- ---
182
-
183
- ### `cairn_resume` — Restore session + smart re-index
184
- Call instead of `cairn_maintain` when resuming work. Detects which files changed and only re-indexes those.
185
-
186
- ```
187
- cairn_resume()
188
- ```
189
-
190
- ```json
191
- {
192
- "last_checkpoint": "2026-02-25T14:30:00Z",
193
- "message": "Added multi-currency to CheckoutForm, still need PaymentStep",
194
- "index_status": "incremental",
195
- "files_reindexed": 2,
196
- "files_unchanged": 845,
197
- "changed_since_checkpoint": [
198
- { "file": "src/components/checkout/PaymentStep.vue", "change": "modified" }
199
- ],
200
- "active_files": ["src/components/checkout/CheckoutForm.vue"],
201
- "notes": ["CurrencyService expects ISO 4217 codes, not symbols"],
202
- "resume_summary": "Last session you were adding multi-currency support..."
203
- }
204
- ```
205
-
206
- ---
207
-
208
- ## Supported Languages
209
-
210
- | Language | What Cairn extracts |
211
- |---|---|
212
- | Java | packages, classes, interfaces, enums, records, methods |
213
- | TypeScript / JavaScript | classes, interfaces, functions, types, enums |
214
- | Vue | components, composables, script block symbols |
215
- | Python | classes, functions, decorators |
216
- | SQL | tables, views, stored procedures |
217
- | XML / HTML | bean ids, component names |
218
- | Config (YAML, properties, .env) | top-level keys |
219
- | Markdown | headings |
220
- | Build files (pom.xml, package.json, build.gradle) | dependencies |
221
-
222
- ---
223
-
224
- ## Typical session
225
-
226
- **Fresh start:**
227
- ```
228
- 1. cairn_maintain → index project (persists between sessions)
229
- 2. cairn_search → find the symbols you need
230
- 3. cairn_bundle → get a readable snapshot of relevant files
231
- 4. cairn_describe → understand a module before modifying it
232
- 5. cairn_security → check for issues before a PR
233
- 6. cairn_checkpoint → save what you were working on
234
- ```
235
-
236
- **Resuming work:**
237
- ```
238
- 1. cairn_resume → restore session + incremental re-index
239
- 2. cairn_search → continue where you left off
240
- ```
241
-
242
- ## Complete session lifecycle
243
-
244
- ```
245
- ─── START OF SESSION ──────────────────────────────────────────
246
- You: "Hey Claude, please resume and continue"
247
- Claude: cairn_resume
248
- "Last session: adding multi-currency to checkout.
249
- PaymentStep.vue was modified since checkpoint (2 files changed, re-indexed).
250
- Notes: CurrencyService expects ISO 4217 codes. PaymentStep EUR bug not fixed yet.
251
- Ready to continue."
252
-
253
- ─── DURING SESSION ────────────────────────────────────────────
254
- Claude uses: cairn_search, cairn_bundle, cairn_code_graph, cairn_security
255
- as needed — all reading from .cairn/index.db in cwd
256
-
257
- ─── END OF SESSION ────────────────────────────────────────────
258
- 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."
264
- ```
265
-
266
- ---
267
-
268
- ## License
269
-
270
- MIT
1
+ # Cairn MCP
2
+
3
+ > Persistent polyglot knowledge graph for Claude Code. Index once, query forever.
4
+
5
+ Claude is stateless — every session starts at zero. On a multi-repo codebase with Java backends, TypeScript frontends, and Vue components, that means 50% of every session is archaeology: finding where things live before any real work can begin.
6
+
7
+ **Cairn fixes this.** It indexes your project into a local SQLite knowledge graph (stored in `.cairn/`) and exposes 8 MCP tools that give Claude instant, persistent memory across sessions.
8
+
9
+ ---
10
+
11
+ # Setup
12
+ - See how-to-use.md
13
+
14
+ ```bash
15
+ npm install -g @misterhuydo/cairn-mcp
16
+ ```
17
+
18
+ **Requirements:** Node.js >= 22.15.0
19
+
20
+ ---
21
+
22
+ ## Setup
23
+
24
+ Add to `~/.claude.json`:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "cairn": {
30
+ "command": "cairn-mcp"
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ Restart Claude Code. Done.
37
+
38
+ ---
39
+
40
+ ## Passive hooks (recommended)
41
+
42
+ Install once and Cairn works automatically — no need to call `cairn_bundle` or `cairn_checkpoint` manually.
43
+
44
+ **Global (all projects):**
45
+
46
+ ```bash
47
+ cairn install-hooks --global
48
+ ```
49
+
50
+ **Project-only:**
51
+
52
+ ```bash
53
+ cairn install-hooks
54
+ ```
55
+
56
+ | Hook | Trigger | Effect |
57
+ |---|---|---|
58
+ | `PreToolUse[Read]` | Every file read | Source files compressed ~68% before Claude sees them |
59
+ | `Stop` | Every response | Session auto-saved to `.cairn/session.json` |
60
+ | `UserPromptSubmit` | First message of session | Claude reminded of prior session, prompted to call `cairn_resume` |
61
+
62
+ ## How it works
63
+
64
+ 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/`.
65
+
66
+ No root paths needed. No global config. Just `cd` to your project and go.
67
+
68
+ ---
69
+
70
+ ## Tools
71
+
72
+ ### `cairn_maintain` — Index your project
73
+ Run once at the start of each session. The index persists in `.cairn/index.db`.
74
+
75
+ ```
76
+ cairn_maintain()
77
+ cairn_maintain({ languages: ["typescript", "vue"] }) // limit to specific languages
78
+ ```
79
+
80
+ ```json
81
+ {
82
+ "repos_indexed": 1,
83
+ "files_by_language": { "java": 412, "typescript": 287, "vue": 94 },
84
+ "symbols_total": 6841,
85
+ "security_findings": 47,
86
+ "duration_ms": 4200
87
+ }
88
+ ```
89
+
90
+ ---
91
+
92
+ ### `cairn_search` — Find anything across all languages
93
+ ```
94
+ cairn_search({ query: "user authentication", language: "typescript" })
95
+ ```
96
+
97
+ ```json
98
+ [
99
+ { "lang": "typescript", "kind": "function", "fqn": "src/composables/useAuth.ts::useAuth" },
100
+ { "lang": "vue", "kind": "component", "fqn": "src/components/LoginForm.vue::LoginForm" },
101
+ { "lang": "java", "kind": "class", "fqn": "com.example.auth.UserAuthenticator" }
102
+ ]
103
+ ```
104
+
105
+ ---
106
+
107
+ ### `cairn_bundle` — Minified source snapshot for Claude to read
108
+ Strips comments and empty lines, writes a compressed `### File:` bundle. Use after `cairn_search` to give Claude readable source without blowing the context window.
109
+
110
+ ```
111
+ cairn_bundle({ filter_paths: ["src/components/checkout"], bundle_name: "checkout" })
112
+ ```
113
+
114
+ ```json
115
+ {
116
+ "bundle_path": "/your/project/.cairn/bundles/checkout.txt",
117
+ "original_kb": 284,
118
+ "compressed_kb": 91,
119
+ "reduction_pct": 68
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ### `cairn_describe` — Summarize a module
126
+ ```
127
+ cairn_describe({ path: "src/components/checkout" })
128
+ ```
129
+
130
+ ```json
131
+ {
132
+ "languages": ["vue", "typescript"],
133
+ "symbols": {
134
+ "component": ["CheckoutForm", "OrderSummary", "PaymentStep"],
135
+ "function": ["useCheckout", "usePayment"]
136
+ },
137
+ "imports_from": ["src/store/cart.ts", "src/api/orders.ts"],
138
+ "imported_by": ["src/views/CartView.vue"],
139
+ "external_deps": ["stripe", "@stripe/stripe-js"]
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ### `cairn_code_graph` — Dependency health
146
+ ```
147
+ cairn_code_graph({ mode: "instability" })
148
+ ```
149
+
150
+ ```json
151
+ {
152
+ "modules": [
153
+ { "name": "src/views", "instability": 1.0, "status": "safe_to_refactor" },
154
+ { "name": "src/composables", "instability": 0.4, "status": "review_before_change" },
155
+ { "name": "src/utils", "instability": 0.1, "status": "load_bearing" }
156
+ ]
157
+ }
158
+ ```
159
+
160
+ Modes: `instability` · `health` · `cycles`
161
+
162
+ ---
163
+
164
+ ### `cairn_security` — Vulnerability scan
165
+ ```
166
+ cairn_security({ severity: "HIGH" })
167
+ ```
168
+
169
+ ```json
170
+ {
171
+ "total_findings": 12,
172
+ "by_language": { "typescript": 7, "java": 5 },
173
+ "findings": [
174
+ { "severity": "HIGH", "cwe": "CWE-79", "file": "src/components/UserProfile.vue", "line": 84, "rule": "XSS via innerHTML" },
175
+ { "severity": "HIGH", "cwe": "CWE-611", "file": "backend/src/.../XmlParser.java", "line": 47, "rule": "XXE" }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ Covers: XSS · XXE · SQL injection · command injection · weak crypto · hardcoded secrets · open redirect · unsafe deserialization
181
+
182
+ ---
183
+
184
+ ### `cairn_minify` Minify a single file
185
+ Minify one source file on demand. Returns compressed content with a `[cairn: N → M lines]` header. Useful when passive hooks are not installed.
186
+
187
+ ```
188
+ cairn_minify({ file_path: "/abs/path/to/Service.java" })
189
+ ```
190
+
191
+ ---
192
+
193
+ ### `cairn_checkpoint` Save session state
194
+ Tell Cairn what you were working on so the next session can pick up where you left off.
195
+
196
+ ```
197
+ cairn_checkpoint({
198
+ message: "Added multi-currency to CheckoutForm, still need PaymentStep",
199
+ active_files: ["src/components/checkout/CheckoutForm.vue"],
200
+ notes: ["CurrencyService expects ISO 4217 codes, not symbols"]
201
+ })
202
+ ```
203
+
204
+ ```json
205
+ {
206
+ "saved": true,
207
+ "checkpoint_at": "2026-02-25T14:30:00Z",
208
+ "active_files_tracked": 1,
209
+ "notes_saved": 1
210
+ }
211
+ ```
212
+
213
+ ---
214
+
215
+ ### `cairn_resume` Restore session + smart re-index
216
+ Call instead of `cairn_maintain` when resuming work. Detects which files changed and only re-indexes those.
217
+
218
+ ```
219
+ cairn_resume()
220
+ ```
221
+
222
+ ```json
223
+ {
224
+ "last_checkpoint": "2026-02-25T14:30:00Z",
225
+ "message": "Added multi-currency to CheckoutForm, still need PaymentStep",
226
+ "index_status": "incremental",
227
+ "files_reindexed": 2,
228
+ "files_unchanged": 845,
229
+ "changed_since_checkpoint": [
230
+ { "file": "src/components/checkout/PaymentStep.vue", "change": "modified" }
231
+ ],
232
+ "active_files": ["src/components/checkout/CheckoutForm.vue"],
233
+ "notes": ["CurrencyService expects ISO 4217 codes, not symbols"],
234
+ "resume_summary": "Last session you were adding multi-currency support..."
235
+ }
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Supported Languages
241
+
242
+ | Language | What Cairn extracts |
243
+ |---|---|
244
+ | Java | packages, classes, interfaces, enums, records, methods |
245
+ | TypeScript / JavaScript | classes, interfaces, functions, types, enums |
246
+ | Vue | components, composables, script block symbols |
247
+ | Python | classes, functions, decorators |
248
+ | SQL | tables, views, stored procedures |
249
+ | XML / HTML | bean ids, component names |
250
+ | Config (YAML, properties, .env) | top-level keys |
251
+ | Markdown | headings |
252
+ | Build files (pom.xml, package.json, build.gradle) | dependencies |
253
+
254
+ ---
255
+
256
+ ## Typical session
257
+
258
+ **With passive hooks (recommended):**
259
+ ```
260
+ 1. cairn_maintain → index project
261
+ 2. cairn_search → find the symbols you need
262
+ 3. cairn_describe → understand a module before modifying it
263
+ 4. cairn_security check for issues before a PR
264
+ (reads auto-compressed, session auto-saved — nothing else needed)
265
+ ```
266
+
267
+ **Without hooks:**
268
+ ```
269
+ 1. cairn_maintain → index project (persists between sessions)
270
+ 2. cairn_search → find the symbols you need
271
+ 3. cairn_bundle → get a readable snapshot of relevant files
272
+ 4. cairn_describe → understand a module before modifying it
273
+ 5. cairn_security → check for issues before a PR
274
+ 6. cairn_checkpoint → save what you were working on
275
+ ```
276
+
277
+ **Resuming work:**
278
+ ```
279
+ 1. cairn_resume → restore session + incremental re-index
280
+ 2. cairn_search → continue where you left off
281
+ ```
282
+
283
+ ## Complete session lifecycle
284
+
285
+ ```
286
+ ─── START OF SESSION ──────────────────────────────────────────
287
+ You: "Hey Claude, please resume and continue"
288
+ Claude: cairn_resume
289
+ → "Last session: adding multi-currency to checkout.
290
+ PaymentStep.vue was modified since checkpoint (2 files changed, re-indexed).
291
+ Notes: CurrencyService expects ISO 4217 codes. PaymentStep EUR bug not fixed yet.
292
+ Ready to continue."
293
+
294
+ ─── DURING SESSION ────────────────────────────────────────────
295
+ Claude uses: cairn_search, cairn_code_graph, cairn_security
296
+ as needed — all reading from .cairn/index.db in cwd
297
+ (file reads auto-compressed by PreToolUse hook)
298
+
299
+ ─── END OF SESSION ────────────────────────────────────────────
300
+ You: "Ok let's stop here for today"
301
+ (Stop hook fires → .cairn/session.json auto-saved)
302
+ → Next session: cairn_resume picks up automatically
303
+ ```
304
+
305
+ ---
306
+
307
+ ## License
308
+
309
+ MIT
package/bin/cairn-cli.js CHANGED
@@ -1,111 +1,177 @@
1
- #!/usr/bin/env node
2
- import fs from 'fs';
3
- import os from 'os';
4
- import path from 'path';
5
- import { LANGUAGE_MAP } from '../src/indexer/fileWalker.js';
6
- import { minifyContent } from '../src/bundler/minifier.js';
7
- import { minifyVue } from '../src/bundler/vueMinifier.js';
8
- import { checkpoint } from '../src/tools/checkpoint.js';
9
-
10
- const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
11
-
12
- const subcommand = process.argv[2];
13
-
14
- if (subcommand === 'minify') {
15
- let stdinData = '';
16
- process.stdin.setEncoding('utf8');
17
- process.stdin.on('data', chunk => { stdinData += chunk; });
18
- process.stdin.on('end', () => {
19
- let filePath;
20
- try {
21
- const input = JSON.parse(stdinData);
22
- filePath = input?.tool_input?.file_path;
23
- } catch {
24
- process.exit(0);
25
- }
26
-
27
- if (!filePath) process.exit(0);
28
-
29
- const ext = path.extname(filePath).toLowerCase();
30
- const lang = LANGUAGE_MAP[ext];
31
-
32
- if (!lang || !MINIFY_LANGS.has(lang)) process.exit(0);
33
-
34
- let content;
35
- try {
36
- content = fs.readFileSync(filePath, 'utf8');
37
- } catch {
38
- process.exit(0);
39
- }
40
-
41
- const originalLines = content.split('\n').length;
42
-
43
- const minified = lang === 'vue'
44
- ? minifyVue(content)
45
- : minifyContent(content, lang);
46
-
47
- const minifiedLines = minified.split('\n').length;
48
- const pct = originalLines > 0 ? Math.round((1 - minifiedLines / originalLines) * 100) : 0;
49
-
50
- process.stdout.write(`[cairn: ${originalLines} ${minifiedLines} lines (-${pct}%)]\n${minified}\n`);
51
- process.exit(2);
52
- });
53
-
54
- } else if (subcommand === 'checkpoint' && process.argv[3] === '--auto') {
55
- const message = `Auto-checkpoint at ${new Date().toISOString()}`;
56
- checkpoint(null, { message, active_files: [], notes: [] });
57
- process.exit(0);
58
-
59
- } else if (subcommand === 'install-hooks') {
60
- const isGlobal = process.argv.includes('--global');
61
- const settingsDir = isGlobal
62
- ? path.join(os.homedir(), '.claude')
63
- : path.join(process.cwd(), '.claude');
64
- const settingsPath = path.join(settingsDir, 'settings.json');
65
-
66
- let settings = {};
67
- if (fs.existsSync(settingsPath)) {
68
- try {
69
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
70
- } catch {
71
- settings = {};
72
- }
73
- }
74
-
75
- settings.hooks = settings.hooks || {};
76
-
77
- // Add PreToolUse hook for Read
78
- const preHooks = settings.hooks.PreToolUse || [];
79
- const hasMinify = preHooks.some(h =>
80
- h.matcher === 'Read' && h.hooks?.some(hh => hh.command === 'cairn minify')
81
- );
82
- if (!hasMinify) {
83
- preHooks.push({ matcher: 'Read', hooks: [{ type: 'command', command: 'cairn minify' }] });
84
- settings.hooks.PreToolUse = preHooks;
85
- }
86
-
87
- // Add Stop hook for auto-checkpoint
88
- const stopHooks = settings.hooks.Stop || [];
89
- const hasCheckpoint = stopHooks.some(h =>
90
- h.hooks?.some(hh => hh.command === 'cairn checkpoint --auto')
91
- );
92
- if (!hasCheckpoint) {
93
- stopHooks.push({ hooks: [{ type: 'command', command: 'cairn checkpoint --auto' }] });
94
- settings.hooks.Stop = stopHooks;
95
- }
96
-
97
- fs.mkdirSync(settingsDir, { recursive: true });
98
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
99
-
100
- const scope = isGlobal ? 'global (~/.claude/settings.json)' : 'project (.claude/settings.json)';
101
- console.log(`Cairn hooks installed in ${scope}`);
102
- console.log('');
103
- console.log('Active hooks:');
104
- console.log(' PreToolUse[Read] cairn minify (compress source files)');
105
- console.log(' Stop → cairn checkpoint --auto (auto-save session)');
106
- process.exit(0);
107
-
108
- } else {
109
- console.error('Usage: cairn <minify|checkpoint --auto|install-hooks>');
110
- process.exit(1);
111
- }
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { LANGUAGE_MAP } from '../src/indexer/fileWalker.js';
6
+ import { minifyContent } from '../src/bundler/minifier.js';
7
+ import { minifyVue } from '../src/bundler/vueMinifier.js';
8
+ import { checkpoint } from '../src/tools/checkpoint.js';
9
+
10
+ const MINIFY_LANGS = new Set(['java', 'typescript', 'javascript', 'vue', 'python', 'sql']);
11
+
12
+ const CAIRN_DIR = path.join(os.homedir(), '.cairn');
13
+ const MODE_FILE = path.join(CAIRN_DIR, 'mode');
14
+
15
+ function getCurrentMode() {
16
+ try {
17
+ return fs.readFileSync(MODE_FILE, 'utf8').trim();
18
+ } catch {
19
+ return 'read';
20
+ }
21
+ }
22
+
23
+ function resetMode() {
24
+ try {
25
+ fs.writeFileSync(MODE_FILE, 'read', 'utf8');
26
+ } catch { }
27
+ }
28
+
29
+ process.on('uncaughtException', (e) => {
30
+ process.stderr.write(`cairn: ${e.message}\n`);
31
+ process.exit(0);
32
+ });
33
+
34
+ const subcommand = process.argv[2];
35
+
36
+ if (subcommand === 'minify') {
37
+ let stdinData = '';
38
+ process.stdin.setEncoding('utf8');
39
+ process.stdin.on('data', chunk => { stdinData += chunk; });
40
+ process.stdin.on('end', () => {
41
+ let filePath;
42
+ try {
43
+ const input = JSON.parse(stdinData);
44
+ filePath = input?.tool_input?.file_path;
45
+ } catch {
46
+ process.exit(0);
47
+ }
48
+ if (!filePath) process.exit(0);
49
+
50
+ // Check mode: if 'edit', let Read proceed with full content (exit 0), then reset to 'read'
51
+ const mode = getCurrentMode();
52
+ if (mode === 'edit') {
53
+ resetMode();
54
+ process.exit(0);
55
+ }
56
+
57
+ const ext = path.extname(filePath).toLowerCase();
58
+ const lang = LANGUAGE_MAP[ext];
59
+ if (!lang || !MINIFY_LANGS.has(lang)) process.exit(0);
60
+ let content;
61
+ try {
62
+ content = fs.readFileSync(filePath, 'utf8');
63
+ } catch {
64
+ process.exit(0);
65
+ }
66
+ const originalLines = content.split('\n').length;
67
+ const minified = lang === 'vue'
68
+ ? minifyVue(content)
69
+ : minifyContent(content, lang);
70
+ const minifiedLines = minified.split('\n').length;
71
+ const pct = originalLines > 0 ? Math.round((1 - minifiedLines / originalLines) * 100) : 0;
72
+ process.stderr.write(`[cairn: ${originalLines} → ${minifiedLines} lines (-${pct}%)]\n${minified}\n`);
73
+ process.exit(2);
74
+ });
75
+ } else if (subcommand === 'set-mode') {
76
+ const mode = process.argv[3];
77
+ if (mode !== 'edit' && mode !== 'read') {
78
+ console.error('Usage: cairn set-mode <edit|read>');
79
+ process.exit(1);
80
+ }
81
+ try {
82
+ fs.mkdirSync(CAIRN_DIR, { recursive: true });
83
+ fs.writeFileSync(MODE_FILE, mode, 'utf8');
84
+ console.log(`cairn: mode set to '${mode}'`);
85
+ if (mode === 'edit') {
86
+ console.log('Next Read call will return full content (auto-resets to read after).');
87
+ }
88
+ } catch (e) {
89
+ console.error(`cairn: failed to write mode file: ${e.message}`);
90
+ process.exit(1);
91
+ }
92
+ process.exit(0);
93
+ } else if (subcommand === 'checkpoint' && process.argv[3] === '--auto') {
94
+ const message = `Auto-checkpoint at ${new Date().toISOString()}`;
95
+ let existingNotes = [];
96
+ try {
97
+ const cairnDir = path.join(process.cwd(), '.cairn');
98
+ const sessionPath = path.join(cairnDir, 'session.json');
99
+ if (fs.existsSync(sessionPath)) {
100
+ const existing = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
101
+ existingNotes = existing.notes || [];
102
+ }
103
+ } catch { }
104
+ checkpoint(null, { message, active_files: [], notes: existingNotes });
105
+ process.exit(0);
106
+ } else if (subcommand === 'resume-hint') {
107
+ const cairnDir = path.join(process.cwd(), '.cairn');
108
+ const sessionPath = path.join(cairnDir, 'session.json');
109
+ if (!fs.existsSync(sessionPath)) process.exit(0);
110
+ const lockPath = path.join(cairnDir, '.hint-lock');
111
+ const LOCK_TTL_MS = 30 * 60 * 1000;
112
+ if (fs.existsSync(lockPath)) {
113
+ const age = Date.now() - fs.statSync(lockPath).mtimeMs;
114
+ if (age < LOCK_TTL_MS) process.exit(0);
115
+ }
116
+ const session = JSON.parse(fs.readFileSync(sessionPath, 'utf8'));
117
+ const when = new Date(session.checkpoint_at).toLocaleString();
118
+ process.stdout.write(`[cairn] Prior session: "${session.message}" (${when}). Call cairn_resume to restore prior context.\n`);
119
+ fs.writeFileSync(lockPath, new Date().toISOString(), 'utf8');
120
+ process.exit(0);
121
+ } else if (subcommand === 'install-hooks') {
122
+ const isGlobal = process.argv.includes('--global');
123
+ const settingsDir = isGlobal
124
+ ? path.join(os.homedir(), '.claude')
125
+ : path.join(process.cwd(), '.claude');
126
+ const settingsPath = path.join(settingsDir, 'settings.json');
127
+ let settings = {};
128
+ if (fs.existsSync(settingsPath)) {
129
+ try {
130
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
131
+ } catch {
132
+ settings = {};
133
+ }
134
+ }
135
+ settings.hooks = settings.hooks || {};
136
+ const preHooks = settings.hooks.PreToolUse || [];
137
+ const hasMinify = preHooks.some(h =>
138
+ h.matcher === 'Read' && h.hooks?.some(hh => hh.command === 'cairn minify')
139
+ );
140
+ if (!hasMinify) {
141
+ preHooks.push({ matcher: 'Read', hooks: [{ type: 'command', command: 'cairn minify' }] });
142
+ settings.hooks.PreToolUse = preHooks;
143
+ }
144
+ const stopHooks = settings.hooks.Stop || [];
145
+ const hasCheckpoint = stopHooks.some(h =>
146
+ h.hooks?.some(hh => hh.command === 'cairn checkpoint --auto')
147
+ );
148
+ if (!hasCheckpoint) {
149
+ stopHooks.push({ hooks: [{ type: 'command', command: 'cairn checkpoint --auto' }] });
150
+ settings.hooks.Stop = stopHooks;
151
+ }
152
+ const submitHooks = settings.hooks.UserPromptSubmit || [];
153
+ const hasResumeHint = submitHooks.some(h =>
154
+ h.hooks?.some(hh => hh.command === 'cairn resume-hint')
155
+ );
156
+ if (!hasResumeHint) {
157
+ submitHooks.push({ hooks: [{ type: 'command', command: 'cairn resume-hint' }] });
158
+ settings.hooks.UserPromptSubmit = submitHooks;
159
+ }
160
+ fs.mkdirSync(settingsDir, { recursive: true });
161
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
162
+ const scope = isGlobal ? 'global (~/.claude/settings.json)' : 'project (.claude/settings.json)';
163
+ console.log(`Cairn hooks installed in ${scope}`);
164
+ console.log('');
165
+ console.log('Active hooks:');
166
+ console.log(' PreToolUse[Read] → cairn minify (compress source files)');
167
+ console.log(' Stop → cairn checkpoint --auto (auto-save session)');
168
+ console.log(' UserPromptSubmit → cairn resume-hint (remind Claude of prior session)');
169
+ console.log('');
170
+ console.log('Mode commands:');
171
+ console.log(' cairn set-mode edit (next Read returns full content, for Edit tool use)');
172
+ console.log(' cairn set-mode read (default: minified content)');
173
+ process.exit(0);
174
+ } else {
175
+ console.error('Usage: cairn <minify|set-mode <edit|read>|checkpoint --auto|resume-hint|install-hooks [--global]>');
176
+ process.exit(1);
177
+ }
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/how-to-use.md CHANGED
@@ -1,5 +1,50 @@
1
+ # Claude Global Instructions
2
+
3
+ ## MCP Tools — Cairn Session Lifecycle
4
+
5
+ Always use cairn MCP tools to manage project context across sessions.
6
+
7
+ ### Start of Session
8
+ When starting work on a project, always run:
9
+ 1. `cairn_resume` — restores last session context and incrementally re-indexes changed files
10
+
11
+ If it's a **fresh project** with no prior session:
12
+ 1. `cairn_maintain` — index the project (persists between sessions)
13
+
14
+ ### During Session
15
+ Use these tools as needed throughout the session:
16
+ - `cairn_search` — find symbols, functions, or files before reading or modifying code
17
+ - `cairn_bundle` — get a readable snapshot of relevant files before making changes
18
+ - `cairn_describe` — understand a module before modifying it
19
+ - `cairn_security` — check for security issues (always run before a PR)
20
+
21
+ **Prefer cairn tools over manual file browsing or grep when available.**
22
+
23
+ ### End of Session
24
+ When the user signals they are done (e.g. "let's stop", "that's enough for today", "wrap up"), always run:
25
+ - `cairn_checkpoint` — save session state with:
26
+ - `message`: summary of what was accomplished
27
+ - `active_files`: files that were modified or are in progress
28
+ - `notes`: important context, known bugs, or next steps
29
+
30
+ ---
31
+
32
+ ## Example Session
33
+
34
+ **Resuming:**
35
+ > User: "Hey Claude, please resume and continue"
36
+ > Claude runs `cairn_resume` → reads context → continues work
37
+
38
+ **Ending:**
39
+ > User: "Ok let's stop here for today"
40
+ > Claude runs `cairn_checkpoint` with a meaningful summary and notes → confirms session saved
41
+
42
+ ---
43
+
1
44
  # Cairn — Quick Start
2
45
 
46
+ > **Requirements:** Node.js >= 22.15.0
47
+
3
48
  ## 1. Install globally
4
49
 
5
50
  ```bash
@@ -32,12 +77,21 @@ What this sets up:
32
77
  |---|---|
33
78
  | `PreToolUse[Read]` | Every source file read is silently compressed before Claude sees it (~68% fewer tokens for `.java/.ts/.js/.vue/.py/.sql`) |
34
79
  | `Stop` | Session auto-saved to `.cairn/session.json` every time Claude finishes responding |
80
+ | `UserPromptSubmit` | On the first message of a new session, Claude is reminded of the prior session and prompted to call `cairn_resume` |
35
81
 
36
82
  After this you never need to call `cairn_bundle` or `cairn_checkpoint` — Claude reads files normally and compression + checkpointing happen transparently in the background.
37
83
 
38
84
  ## 3. Register the MCP server with Claude Code
39
85
 
40
- Add to `~/.claude.json` (create it if it doesn't exist):
86
+ Add to your `~/.claude.json` and restart Claude Code. You should see 8 cairn tools available.
87
+
88
+ **File location by platform:**
89
+
90
+ | Platform | Path |
91
+ |---|---|
92
+ | Windows | `C:\\Users\\<you>\\.claude.json` |
93
+ | macOS | `~/.claude.json` |
94
+ | Linux | `~/.claude.json` |
41
95
 
42
96
  ```json
43
97
  {
@@ -51,12 +105,54 @@ Add to `~/.claude.json` (create it if it doesn't exist):
51
105
 
52
106
  > If you already have other MCP servers, just add `"cairn": { ... }` inside the existing `"mcpServers"` block.
53
107
 
54
- Restart Claude Code. You should see 8 cairn tools available.
108
+ ## 4. (Optional) Configure Claude via CLAUDE.md
109
+
110
+ If you prefer Claude to manage session lifecycle explicitly rather than via hooks, create a global `CLAUDE.md` file. This is an alternative to passive hooks — you don't need both.
111
+
112
+ **File location by platform:**
113
+
114
+ | Platform | Path |
115
+ |---|---|
116
+ | Windows | `C:\\Users\\<you>\\CLAUDE.md` |
117
+ | macOS | `~/CLAUDE.md` |
118
+ | Linux | `~/CLAUDE.md` |
119
+
120
+ Copy and save the following content into that file:
121
+
122
+ ````markdown
123
+ ## MCP Tools — Cairn Session Lifecycle
124
+
125
+ Always use cairn MCP tools to manage project context across sessions.
126
+
127
+ ### Start of Session
128
+ When starting work on a project, always run:
129
+ 1. `cairn_resume` — restores last session context and incrementally re-indexes changed files
55
130
 
56
- ## 4. Index your project
131
+ If it's a fresh project with no prior session:
132
+ 1. `cairn_maintain` — index the project (persists between sessions)
133
+
134
+ ### During Session
135
+ Use these tools as needed throughout the session:
136
+ - `cairn_search` — find symbols, functions, or files before reading or modifying code
137
+ - `cairn_bundle` — get a readable snapshot of relevant files before making changes
138
+ - `cairn_describe` — understand a module before modifying it
139
+ - `cairn_security` — check for security issues (always run before a PR)
140
+
141
+ Prefer cairn tools over manual file browsing or grep when available.
142
+
143
+ ### End of Session
144
+ When the user signals they are done (e.g. "let's stop", "that's enough for today", "wrap up"), always run:
145
+ - `cairn_checkpoint` — save session state with:
146
+ - `message`: summary of what was accomplished
147
+ - `active_files`: files that were modified or are in progress
148
+ - `notes`: important context, known bugs, or next steps
149
+ ````
150
+
151
+ ## 5. Index your project
57
152
 
58
153
  Run Claude Code from your project root, then:
59
154
 
155
+
60
156
  ```
61
157
  cairn_maintain
62
158
  ```
@@ -65,7 +161,7 @@ No paths needed — Cairn works like git and finds your project from the current
65
161
 
66
162
  Run this once at the start of each session (or use `cairn_resume` to pick up where you left off).
67
163
 
68
- ## 5. Tools at a glance
164
+ ## 6. Tools at a glance
69
165
 
70
166
  | Tool | What to use it for |
71
167
  |---|---|
@@ -80,7 +176,7 @@ Run this once at the start of each session (or use `cairn_resume` to pick up whe
80
176
 
81
177
  > `cairn_bundle` and `cairn_checkpoint` are called automatically when passive hooks are installed. They remain available for explicit use when needed.
82
178
 
83
- ## 6. Typical session
179
+ ## 7. Typical session
84
180
 
85
181
  **With passive hooks (recommended):**
86
182
  ```
@@ -107,7 +203,7 @@ Run this once at the start of each session (or use `cairn_resume` to pick up whe
107
203
  2. cairn_search → continue where you left off
108
204
  ```
109
205
 
110
- ## 7. Complete session lifecycle
206
+ ## 8. Complete session lifecycle
111
207
 
112
208
  ```
113
209
  ─── START OF SESSION ──────────────────────────────────────────
@@ -128,7 +224,3 @@ You: "Ok let's stop here for today"
128
224
  (Stop hook fires → .cairn/session.json auto-saved)
129
225
  → Next session: cairn_resume picks up automatically
130
226
  ```
131
-
132
- ---
133
-
134
- **Requirements:** Node.js >= 22.15.0
package/index.js CHANGED
@@ -11,6 +11,7 @@ import { security } from './src/tools/security.js';
11
11
  import { bundle } from './src/tools/bundle.js';
12
12
  import { checkpoint } from './src/tools/checkpoint.js';
13
13
  import { resume } from './src/tools/resume.js';
14
+ import { minify } from './src/tools/minify.js';
14
15
 
15
16
  const db = openDB();
16
17
 
@@ -143,6 +144,17 @@ Typical workflow: cairn_search → find files → cairn_bundle those paths → C
143
144
  properties: {},
144
145
  },
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
+ },
146
158
  ],
147
159
  }));
148
160
 
@@ -157,6 +169,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
157
169
  case 'cairn_bundle': return await bundle(db, args);
158
170
  case 'cairn_checkpoint': return checkpoint(db, args);
159
171
  case 'cairn_resume': return await resume(db);
172
+ case 'cairn_minify': return minify(db, args);
160
173
  default: throw new Error(`Unknown tool: ${name}`);
161
174
  }
162
175
  });
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.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
+ "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
+ }