@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 +309 -270
- package/bin/cairn-cli.js +177 -111
- package/bin/cairn.js +1 -1
- package/how-to-use.md +102 -10
- package/index.js +13 -0
- package/package.json +44 -44
- package/src/tools/minify.js +40 -0
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
```
|
|
147
|
-
{
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
"
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
],
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
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`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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.
|
|
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
|
+
}
|