context-mode 0.4.0 → 0.5.0
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/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +127 -17
- package/build/server.js +83 -13
- package/build/store.d.ts +6 -0
- package/build/store.js +71 -0
- package/package.json +1 -1
- package/skills/context-mode/SKILL.md +107 -88
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-context-mode",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Mert Koseoğlu",
|
|
5
|
+
"email": "code.bm.ksglu@gmail.com"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "Claude Code plugins by Mert Koseoğlu",
|
|
9
|
+
"version": "1.0.0"
|
|
10
|
+
},
|
|
11
|
+
"plugins": [
|
|
12
|
+
{
|
|
13
|
+
"name": "context-mode",
|
|
14
|
+
"source": "./",
|
|
15
|
+
"description": "Claude Code MCP plugin that saves 94% of your context window. Sandboxed code execution in 10 languages, FTS5 knowledge base with BM25 ranking, and smart truncation.",
|
|
16
|
+
"version": "0.5.0",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Mert Koseoğlu"
|
|
19
|
+
},
|
|
20
|
+
"category": "development",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"context-window",
|
|
24
|
+
"sandbox",
|
|
25
|
+
"code-execution",
|
|
26
|
+
"fts5",
|
|
27
|
+
"bm25",
|
|
28
|
+
"playwright",
|
|
29
|
+
"context7"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Claude Code MCP plugin that saves 94% of your context window. Sandboxed code execution in 10 languages, FTS5 knowledge base with BM25 ranking, and smart truncation.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Mert Koseoğlu",
|
package/README.md
CHANGED
|
@@ -10,16 +10,16 @@ Context Mode intercepts these operations, processes data in isolated subprocesse
|
|
|
10
10
|
|
|
11
11
|
Claude Code has a 200K token context window. Here's how fast popular MCP servers eat through it:
|
|
12
12
|
|
|
13
|
-
| MCP Server | Tool |
|
|
14
|
-
|
|
15
|
-
| **Playwright** | `browser_snapshot` | 10K-135K tokens
|
|
16
|
-
| **Context7** | `query-docs` | 4K-10K tokens
|
|
17
|
-
| **GitHub** | `list_commits` (30) | 29K-64K tokens | [github-mcp-server#142](https://github.com/github/github-mcp-server/issues/142) |
|
|
18
|
-
| **Sentry** |
|
|
19
|
-
| **Supabase** |
|
|
20
|
-
| **Firecrawl** | `scrape` / `crawl` | 5K-50K+ tokens
|
|
21
|
-
| **Chrome DevTools** |
|
|
22
|
-
| **Fetch** | `fetch` | 5K-50K tokens
|
|
13
|
+
| MCP Server | Tool | Without Context Mode | With Context Mode | Savings | Source |
|
|
14
|
+
|---|---|---|---|---|---|
|
|
15
|
+
| **Playwright** | `browser_snapshot` | 10K-135K tokens | ~20 tokens | **99%** | [playwright-mcp#1233](https://github.com/microsoft/playwright-mcp/issues/1233) |
|
|
16
|
+
| **Context7** | `query-docs` | 4K-10K tokens | ~70 tokens | **98%** | [upstash/context7](https://github.com/upstash/context7) |
|
|
17
|
+
| **GitHub** | `list_commits` (30) | 29K-64K tokens | ~10 tokens | **99%** | [github-mcp-server#142](https://github.com/github/github-mcp-server/issues/142) |
|
|
18
|
+
| **Sentry** | issue analysis | 5K-30K tokens | ~25 tokens | **99%** | [getsentry/sentry-mcp](https://github.com/getsentry/sentry-mcp) |
|
|
19
|
+
| **Supabase** | schema queries | 2K-30K tokens | ~30 tokens | **99%** | [supabase-community/supabase-mcp](https://github.com/supabase-community/supabase-mcp) |
|
|
20
|
+
| **Firecrawl** | `scrape` / `crawl` | 5K-50K+ tokens | ~70 tokens | **99%** | [firecrawl](https://github.com/mendableai/firecrawl) |
|
|
21
|
+
| **Chrome DevTools** | DOM / network | 5K-50K+ tokens | ~25 tokens | **99%** | Community benchmark |
|
|
22
|
+
| **Fetch** | `fetch` | 5K-50K tokens | ~70 tokens | **99%** | Official reference server |
|
|
23
23
|
|
|
24
24
|
**Real measurement** ([Scott Spence, 2025](https://scottspence.com/posts/optimising-mcp-server-context-usage-in-claude-code)): With 81+ MCP tools enabled across multiple servers, **143K of 200K tokens (72%) consumed** — 82K tokens just for MCP tool definitions. Only 28% left for actual work.
|
|
25
25
|
|
|
@@ -44,10 +44,11 @@ Claude Code has a 200K token context window. Here's how fast popular MCP servers
|
|
|
44
44
|
### Option 1: Claude Code Plugin (Recommended)
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
/plugin
|
|
47
|
+
/plugin marketplace add mksglu/claude-context-mode
|
|
48
|
+
/plugin install context-mode@claude-context-mode
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
Installs as a Claude Code plugin with
|
|
51
|
+
Installs as a Claude Code plugin with MCP server + skills bundled. The skill automatically guides Claude to route large outputs through Context Mode.
|
|
51
52
|
|
|
52
53
|
### Option 2: MCP Server Only
|
|
53
54
|
|
|
@@ -57,6 +58,12 @@ claude mcp add context-mode -- npx -y context-mode
|
|
|
57
58
|
|
|
58
59
|
Restart Claude Code. 5 tools are now available.
|
|
59
60
|
|
|
61
|
+
### Option 3: Local Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
claude --plugin-dir ./path/to/context-mode
|
|
65
|
+
```
|
|
66
|
+
|
|
60
67
|
## Tools
|
|
61
68
|
|
|
62
69
|
### `execute` — Run Code in Sandbox
|
|
@@ -68,11 +75,22 @@ Claude calls: execute({ language: "shell", code: "gh pr list --json title,state
|
|
|
68
75
|
Returns: "3" ← 2 bytes instead of 8KB JSON
|
|
69
76
|
```
|
|
70
77
|
|
|
78
|
+
**Intent-driven search** (v0.5.0): When you provide an `intent` parameter and output exceeds 5KB, Context Mode uses BM25 search to return only the relevant sections — instead of blind head/tail truncation.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Claude calls: execute({
|
|
82
|
+
language: "shell",
|
|
83
|
+
code: "cat /var/log/app.log",
|
|
84
|
+
intent: "connection refused database error"
|
|
85
|
+
})
|
|
86
|
+
Returns: only the 3 matching log sections (1.5KB) ← instead of 100KB truncated log
|
|
87
|
+
```
|
|
88
|
+
|
|
71
89
|
Authenticated CLIs work out of the box — `gh`, `aws`, `gcloud`, `kubectl`, `docker` credentials are passed through securely. Bun auto-detected for 3-5x faster JS/TS.
|
|
72
90
|
|
|
73
91
|
### `execute_file` — Process Files Without Loading
|
|
74
92
|
|
|
75
|
-
File contents never enter context. The file is read into a `FILE_CONTENT` variable inside the sandbox.
|
|
93
|
+
File contents never enter context. The file is read into a `FILE_CONTENT` variable inside the sandbox. Also supports `intent` parameter for intent-driven search on large outputs.
|
|
76
94
|
|
|
77
95
|
```
|
|
78
96
|
Claude calls: execute_file({ path: "access.log", language: "python", code: "..." })
|
|
@@ -207,6 +225,31 @@ Tail (40%): Final output with errors/results
|
|
|
207
225
|
|
|
208
226
|
Line-boundary snapping — never cuts mid-line. Error messages at the bottom are always preserved.
|
|
209
227
|
|
|
228
|
+
### Intent-Driven Search (v0.5.0)
|
|
229
|
+
|
|
230
|
+
When `execute` or `execute_file` is called with an `intent` parameter and output exceeds 5KB, Context Mode replaces blind truncation with intelligent BM25 search:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
Traditional truncation:
|
|
234
|
+
stdout (100KB) → head(60%) + tail(40%) → ~100KB in context
|
|
235
|
+
Problem: relevant info in the middle is lost
|
|
236
|
+
|
|
237
|
+
Intent-driven search:
|
|
238
|
+
stdout (100KB) → chunk by lines → in-memory FTS5 → search(intent) → 2-5KB relevant sections
|
|
239
|
+
Result: only what you need enters context
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Tested across 4 real-world scenarios:
|
|
243
|
+
|
|
244
|
+
| Scenario | Smart Truncation | Intent Search | Intent Size | Truncation Size |
|
|
245
|
+
|---|---|---|---|---|
|
|
246
|
+
| Server log error (line 347/500) | **missed** | **found** | 1.5 KB | 5.0 KB |
|
|
247
|
+
| 3 test failures among 200 tests | found 2/3 | **found 3/3** | 2.4 KB | 5.0 KB |
|
|
248
|
+
| 2 build warnings among 300 lines | **missed both** | **found both** | 2.1 KB | 5.0 KB |
|
|
249
|
+
| API auth error (line 743/1000) | **missed** | **found** | 1.2 KB | 4.9 KB |
|
|
250
|
+
|
|
251
|
+
Smart truncation fails on 3 of 4 scenarios because relevant content is in the dropped middle section. Intent search finds the target every time while using 50-75% fewer bytes.
|
|
252
|
+
|
|
210
253
|
### HTML to Markdown Conversion
|
|
211
254
|
|
|
212
255
|
`fetch_and_index` converts HTML in a subprocess (raw HTML never enters context):
|
|
@@ -264,6 +307,72 @@ Typical 45-minute debugging session:
|
|
|
264
307
|
| Source code to edit | Plain `Read` tool | Need full content for edits |
|
|
265
308
|
| Small files (<20 lines) | Plain `Read` tool | Minimal overhead |
|
|
266
309
|
|
|
310
|
+
## Example Prompts
|
|
311
|
+
|
|
312
|
+
Just ask naturally — Claude automatically routes through Context Mode when it saves tokens.
|
|
313
|
+
|
|
314
|
+
### Git & GitHub
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
"Analyze the last 50 commits and find the most frequently changed files"
|
|
318
|
+
"List all open PRs on this repo and summarize their status"
|
|
319
|
+
"Show contributors ranked by commit count this month"
|
|
320
|
+
"Find all commits that touched the auth module in the last 30 days"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Code Analysis
|
|
324
|
+
|
|
325
|
+
```
|
|
326
|
+
"Analyze all TypeScript files in src/ and report function counts per file"
|
|
327
|
+
"Find all TODO and FIXME comments across the codebase"
|
|
328
|
+
"Count lines of code per language in this project"
|
|
329
|
+
"List all exported functions from src/utils/ and their parameter signatures"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Logs & Debugging
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
"Read the access log and break down requests by HTTP status code"
|
|
336
|
+
"Find the top 10 slowest API endpoints from the request log"
|
|
337
|
+
"Parse the error log and group exceptions by type with frequency"
|
|
338
|
+
"Analyze the build output and list all warnings with file locations"
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Test & CI
|
|
342
|
+
|
|
343
|
+
```
|
|
344
|
+
"Run the test suite and give me a pass/fail summary"
|
|
345
|
+
"Analyze test coverage output and find untested files"
|
|
346
|
+
"Check which tests have been flaky in the last 10 CI runs"
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Data & Config
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
"Analyze package-lock.json and find the 10 largest dependencies by size"
|
|
353
|
+
"Parse the CSV export and compute average response time per endpoint"
|
|
354
|
+
"Read the Kubernetes manifests and summarize resource limits per pod"
|
|
355
|
+
"Compare tsconfig.json across packages in this monorepo"
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Documentation Lookup
|
|
359
|
+
|
|
360
|
+
```
|
|
361
|
+
"Fetch the React useEffect docs and find the cleanup pattern"
|
|
362
|
+
"Index the Next.js App Router documentation and search for loading states"
|
|
363
|
+
"Look up the Zod docs and find string validation examples"
|
|
364
|
+
"Fetch the Tailwind docs and search for responsive breakpoint utilities"
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Cloud & Infrastructure
|
|
368
|
+
|
|
369
|
+
```
|
|
370
|
+
"List all S3 buckets and their sizes using AWS CLI"
|
|
371
|
+
"Show running Kubernetes pods and their restart counts"
|
|
372
|
+
"List all Docker containers with their memory and CPU usage"
|
|
373
|
+
"Check the status of all Cloudflare Workers in this account"
|
|
374
|
+
```
|
|
375
|
+
|
|
267
376
|
## Requirements
|
|
268
377
|
|
|
269
378
|
- **Node.js 18+**
|
|
@@ -279,12 +388,13 @@ Typical 45-minute debugging session:
|
|
|
279
388
|
|
|
280
389
|
## Test Suite
|
|
281
390
|
|
|
282
|
-
|
|
391
|
+
99+ tests across 4 suites:
|
|
283
392
|
|
|
284
393
|
| Suite | Tests | Coverage |
|
|
285
394
|
|---|---|---|
|
|
286
395
|
| Executor | 55 | 10 languages, sandbox, truncation, concurrency, timeouts |
|
|
287
|
-
| ContentStore |
|
|
396
|
+
| ContentStore | 40 | FTS5 schema, BM25 ranking, chunking, stemming, plain text indexing |
|
|
397
|
+
| Intent Search | 4 | Smart truncation vs intent-driven search across 4 real-world scenarios |
|
|
288
398
|
| MCP Integration | 24 | JSON-RPC protocol, all 5 tools, fetch_and_index, errors |
|
|
289
399
|
|
|
290
400
|
## Development
|
|
@@ -295,8 +405,8 @@ cd claude-context-mode
|
|
|
295
405
|
npm install
|
|
296
406
|
npm run build
|
|
297
407
|
npm test # executor (55 tests)
|
|
298
|
-
npm run test:store # FTS5/BM25 (
|
|
299
|
-
npm run test:all # all suites (
|
|
408
|
+
npm run test:store # FTS5/BM25 (40 tests)
|
|
409
|
+
npm run test:all # all suites (99+ tests)
|
|
300
410
|
```
|
|
301
411
|
|
|
302
412
|
## License
|
package/build/server.js
CHANGED
|
@@ -9,7 +9,7 @@ const runtimes = detectRuntimes();
|
|
|
9
9
|
const available = getAvailableLanguages(runtimes);
|
|
10
10
|
const server = new McpServer({
|
|
11
11
|
name: "context-mode",
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.5.0",
|
|
13
13
|
});
|
|
14
14
|
const executor = new PolyglotExecutor({ runtimes });
|
|
15
15
|
// Lazy singleton — no DB overhead unless index/search is used
|
|
@@ -53,8 +53,14 @@ server.registerTool("execute", {
|
|
|
53
53
|
.optional()
|
|
54
54
|
.default(30000)
|
|
55
55
|
.describe("Max execution time in ms"),
|
|
56
|
+
intent: z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("What you're looking for in the output. When provided and output is large (>5KB), " +
|
|
60
|
+
"returns only matching sections via BM25 search instead of truncated output. " +
|
|
61
|
+
"Example: 'find failing tests', 'HTTP 500 errors', 'memory usage statistics'."),
|
|
56
62
|
}),
|
|
57
|
-
}, async ({ language, code, timeout }) => {
|
|
63
|
+
}, async ({ language, code, timeout, intent }) => {
|
|
58
64
|
try {
|
|
59
65
|
const result = await executor.execute({ language, code, timeout });
|
|
60
66
|
if (result.timedOut) {
|
|
@@ -69,19 +75,34 @@ server.registerTool("execute", {
|
|
|
69
75
|
};
|
|
70
76
|
}
|
|
71
77
|
if (result.exitCode !== 0) {
|
|
78
|
+
const output = `Exit code: ${result.exitCode}\n\nstdout:\n${result.stdout}\n\nstderr:\n${result.stderr}`;
|
|
79
|
+
if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{ type: "text", text: intentSearch(output, intent) },
|
|
83
|
+
],
|
|
84
|
+
isError: true,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
72
87
|
return {
|
|
73
88
|
content: [
|
|
74
|
-
{
|
|
75
|
-
type: "text",
|
|
76
|
-
text: `Exit code: ${result.exitCode}\n\nstdout:\n${result.stdout}\n\nstderr:\n${result.stderr}`,
|
|
77
|
-
},
|
|
89
|
+
{ type: "text", text: output },
|
|
78
90
|
],
|
|
79
91
|
isError: true,
|
|
80
92
|
};
|
|
81
93
|
}
|
|
94
|
+
const stdout = result.stdout || "(no output)";
|
|
95
|
+
// Intent-driven search: if intent provided and output is large enough
|
|
96
|
+
if (intent && intent.trim().length > 0 && Buffer.byteLength(stdout) > INTENT_SEARCH_THRESHOLD) {
|
|
97
|
+
return {
|
|
98
|
+
content: [
|
|
99
|
+
{ type: "text", text: intentSearch(stdout, intent) },
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
82
103
|
return {
|
|
83
104
|
content: [
|
|
84
|
-
{ type: "text", text:
|
|
105
|
+
{ type: "text", text: stdout },
|
|
85
106
|
],
|
|
86
107
|
};
|
|
87
108
|
}
|
|
@@ -111,6 +132,36 @@ function indexStdout(stdout, source) {
|
|
|
111
132
|
};
|
|
112
133
|
}
|
|
113
134
|
// ─────────────────────────────────────────────────────────
|
|
135
|
+
// Helper: intent-driven search on execution output
|
|
136
|
+
// ─────────────────────────────────────────────────────────
|
|
137
|
+
const INTENT_SEARCH_THRESHOLD = 5_000; // bytes — ~80-100 lines
|
|
138
|
+
function intentSearch(stdout, intent, maxResults = 5) {
|
|
139
|
+
const store = new ContentStore(":memory:");
|
|
140
|
+
try {
|
|
141
|
+
const totalLines = stdout.split("\n").length;
|
|
142
|
+
const totalBytes = Buffer.byteLength(stdout);
|
|
143
|
+
store.indexPlainText(stdout, "exec-output");
|
|
144
|
+
const results = store.search(intent, maxResults);
|
|
145
|
+
if (results.length === 0) {
|
|
146
|
+
return (`[Intent search: no matches for "${intent}" in ${totalLines}-line output. Returning full output.]\n\n` +
|
|
147
|
+
stdout);
|
|
148
|
+
}
|
|
149
|
+
const totalChunks = store.getStats().chunks;
|
|
150
|
+
const header = `[Intent search: ${results.length} of ${totalChunks} sections matched "${intent}" from ${totalLines}-line output (${(totalBytes / 1024).toFixed(1)}KB)]`;
|
|
151
|
+
const formatted = results
|
|
152
|
+
.map((r, i) => {
|
|
153
|
+
const matchLabel = i === 0 ? " (best match)" : "";
|
|
154
|
+
return `--- ${r.title}${matchLabel} ---\n${r.content}`;
|
|
155
|
+
})
|
|
156
|
+
.join("\n\n");
|
|
157
|
+
const footer = `[Full output: ${totalLines} lines / ${(totalBytes / 1024).toFixed(1)}KB. Re-run without intent to see raw output.]`;
|
|
158
|
+
return `${header}\n\n${formatted}\n\n${footer}`;
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
store.close();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// ─────────────────────────────────────────────────────────
|
|
114
165
|
// Tool: execute_file
|
|
115
166
|
// ─────────────────────────────────────────────────────────
|
|
116
167
|
server.registerTool("execute_file", {
|
|
@@ -142,8 +193,13 @@ server.registerTool("execute_file", {
|
|
|
142
193
|
.optional()
|
|
143
194
|
.default(30000)
|
|
144
195
|
.describe("Max execution time in ms"),
|
|
196
|
+
intent: z
|
|
197
|
+
.string()
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("What you're looking for in the output. When provided and output is large (>5KB), " +
|
|
200
|
+
"returns only matching sections via BM25 search instead of truncated output."),
|
|
145
201
|
}),
|
|
146
|
-
}, async ({ path, language, code, timeout }) => {
|
|
202
|
+
}, async ({ path, language, code, timeout, intent }) => {
|
|
147
203
|
try {
|
|
148
204
|
const result = await executor.executeFile({
|
|
149
205
|
path,
|
|
@@ -163,19 +219,33 @@ server.registerTool("execute_file", {
|
|
|
163
219
|
};
|
|
164
220
|
}
|
|
165
221
|
if (result.exitCode !== 0) {
|
|
222
|
+
const output = `Error processing ${path} (exit ${result.exitCode}):\n${result.stderr || result.stdout}`;
|
|
223
|
+
if (intent && intent.trim().length > 0 && Buffer.byteLength(output) > INTENT_SEARCH_THRESHOLD) {
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{ type: "text", text: intentSearch(output, intent) },
|
|
227
|
+
],
|
|
228
|
+
isError: true,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
166
231
|
return {
|
|
167
232
|
content: [
|
|
168
|
-
{
|
|
169
|
-
type: "text",
|
|
170
|
-
text: `Error processing ${path} (exit ${result.exitCode}):\n${result.stderr || result.stdout}`,
|
|
171
|
-
},
|
|
233
|
+
{ type: "text", text: output },
|
|
172
234
|
],
|
|
173
235
|
isError: true,
|
|
174
236
|
};
|
|
175
237
|
}
|
|
238
|
+
const stdout = result.stdout || "(no output)";
|
|
239
|
+
if (intent && intent.trim().length > 0 && Buffer.byteLength(stdout) > INTENT_SEARCH_THRESHOLD) {
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{ type: "text", text: intentSearch(stdout, intent) },
|
|
243
|
+
],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
176
246
|
return {
|
|
177
247
|
content: [
|
|
178
|
-
{ type: "text", text:
|
|
248
|
+
{ type: "text", text: stdout },
|
|
179
249
|
],
|
|
180
250
|
};
|
|
181
251
|
}
|
package/build/store.d.ts
CHANGED
|
@@ -33,6 +33,12 @@ export declare class ContentStore {
|
|
|
33
33
|
path?: string;
|
|
34
34
|
source?: string;
|
|
35
35
|
}): IndexResult;
|
|
36
|
+
/**
|
|
37
|
+
* Index plain-text output (logs, build output, test results) by splitting
|
|
38
|
+
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
39
|
+
* look for headings — it chunks by line count with overlap.
|
|
40
|
+
*/
|
|
41
|
+
indexPlainText(content: string, source: string, linesPerChunk?: number): IndexResult;
|
|
36
42
|
search(query: string, limit?: number): SearchResult[];
|
|
37
43
|
getStats(): StoreStats;
|
|
38
44
|
close(): void;
|
package/build/store.js
CHANGED
|
@@ -94,6 +94,42 @@ export class ContentStore {
|
|
|
94
94
|
codeChunks,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
+
// ── Index Plain Text ──
|
|
98
|
+
/**
|
|
99
|
+
* Index plain-text output (logs, build output, test results) by splitting
|
|
100
|
+
* into fixed-size line groups. Unlike markdown indexing, this does not
|
|
101
|
+
* look for headings — it chunks by line count with overlap.
|
|
102
|
+
*/
|
|
103
|
+
indexPlainText(content, source, linesPerChunk = 20) {
|
|
104
|
+
if (!content || content.trim().length === 0) {
|
|
105
|
+
const insertSource = this.#db.prepare("INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, 0, 0)");
|
|
106
|
+
const info = insertSource.run(source);
|
|
107
|
+
return {
|
|
108
|
+
sourceId: Number(info.lastInsertRowid),
|
|
109
|
+
label: source,
|
|
110
|
+
totalChunks: 0,
|
|
111
|
+
codeChunks: 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const chunks = this.#chunkPlainText(content, linesPerChunk);
|
|
115
|
+
const insertSource = this.#db.prepare("INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)");
|
|
116
|
+
const insertChunk = this.#db.prepare("INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)");
|
|
117
|
+
const transaction = this.#db.transaction(() => {
|
|
118
|
+
const info = insertSource.run(source, chunks.length, 0);
|
|
119
|
+
const sourceId = Number(info.lastInsertRowid);
|
|
120
|
+
for (const chunk of chunks) {
|
|
121
|
+
insertChunk.run(chunk.title, chunk.content, sourceId, "prose");
|
|
122
|
+
}
|
|
123
|
+
return sourceId;
|
|
124
|
+
});
|
|
125
|
+
const sourceId = transaction();
|
|
126
|
+
return {
|
|
127
|
+
sourceId,
|
|
128
|
+
label: source,
|
|
129
|
+
totalChunks: chunks.length,
|
|
130
|
+
codeChunks: 0,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
97
133
|
// ── Search ──
|
|
98
134
|
search(query, limit = 3) {
|
|
99
135
|
const sanitized = sanitizeQuery(query);
|
|
@@ -203,6 +239,41 @@ export class ContentStore {
|
|
|
203
239
|
flush();
|
|
204
240
|
return chunks;
|
|
205
241
|
}
|
|
242
|
+
#chunkPlainText(text, linesPerChunk) {
|
|
243
|
+
// Try blank-line splitting first for naturally-sectioned output
|
|
244
|
+
const sections = text.split(/\n\s*\n/);
|
|
245
|
+
if (sections.length >= 3 &&
|
|
246
|
+
sections.length <= 200 &&
|
|
247
|
+
sections.every((s) => Buffer.byteLength(s) < 5000)) {
|
|
248
|
+
return sections
|
|
249
|
+
.map((section, i) => ({
|
|
250
|
+
title: `Section ${i + 1}`,
|
|
251
|
+
content: section.trim(),
|
|
252
|
+
}))
|
|
253
|
+
.filter((s) => s.content.length > 0);
|
|
254
|
+
}
|
|
255
|
+
const lines = text.split("\n");
|
|
256
|
+
// Small enough for a single chunk
|
|
257
|
+
if (lines.length <= linesPerChunk) {
|
|
258
|
+
return [{ title: "Output", content: text }];
|
|
259
|
+
}
|
|
260
|
+
// Fixed-size line groups with 2-line overlap
|
|
261
|
+
const chunks = [];
|
|
262
|
+
const overlap = 2;
|
|
263
|
+
const step = Math.max(linesPerChunk - overlap, 1);
|
|
264
|
+
for (let i = 0; i < lines.length; i += step) {
|
|
265
|
+
const slice = lines.slice(i, i + linesPerChunk);
|
|
266
|
+
if (slice.length === 0)
|
|
267
|
+
break;
|
|
268
|
+
const startLine = i + 1;
|
|
269
|
+
const endLine = Math.min(i + slice.length, lines.length);
|
|
270
|
+
chunks.push({
|
|
271
|
+
title: `Lines ${startLine}-${endLine}`,
|
|
272
|
+
content: slice.join("\n"),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return chunks;
|
|
276
|
+
}
|
|
206
277
|
#buildTitle(headingStack, currentHeading) {
|
|
207
278
|
if (headingStack.length === 0) {
|
|
208
279
|
return currentHeading || "Untitled";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Claude Code MCP plugin that saves 94% of your context window. Sandboxed code execution, FTS5 knowledge base, and smart truncation.",
|
|
6
6
|
"author": "Mert Koseoğlu",
|
|
@@ -1,120 +1,139 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: context-mode
|
|
3
3
|
description: |
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
ALWAYS use context-mode tools instead of Bash/curl/cat when output MIGHT exceed 20 lines.
|
|
5
|
+
This includes: API requests (curl, fetch, gh, aws), file reading (cat, head, tail),
|
|
6
|
+
command output (npm test, git log, docker ps, kubectl), log analysis, data processing,
|
|
7
|
+
and ANY operation where you cannot guarantee the output will be small.
|
|
8
|
+
When in doubt, use context-mode — the cost of routing through it is near zero,
|
|
9
|
+
but the cost of flooding context with 50KB+ of raw data is catastrophic.
|
|
8
10
|
---
|
|
9
11
|
|
|
10
|
-
# Context Mode:
|
|
12
|
+
# Context Mode: Default for All Large Output
|
|
11
13
|
|
|
12
|
-
##
|
|
14
|
+
## MANDATORY RULE
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
Will the command output > 20 lines?
|
|
16
|
-
├── YES → Will you process/filter/summarize that output?
|
|
17
|
-
│ ├── YES → Use execute or execute_file
|
|
18
|
-
│ └── NO → Use Bash (you need raw output)
|
|
19
|
-
└── NO → Use Bash (small output fits in context)
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
**Rule of thumb:** If you would pipe Bash output through grep/awk/jq to reduce it,
|
|
23
|
-
use `execute` or `execute_file` instead — the LLM summary is better.
|
|
24
|
-
|
|
25
|
-
## Quick Reference
|
|
26
|
-
|
|
27
|
-
| Tool | Purpose | Key Parameters |
|
|
28
|
-
|------|---------|---------------|
|
|
29
|
-
| `execute` | Run inline code, get LLM summary | `code`, `language`, `timeout_ms`, `summary_prompt` |
|
|
30
|
-
| `execute_file` | Run a script file, get LLM summary | `file_path`, `args`, `timeout_ms`, `summary_prompt` |
|
|
16
|
+
**Before using Bash, curl, cat, or any command that produces output, ask yourself:**
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
The raw output never enters your context window — only the summary does.
|
|
18
|
+
> "Could this output be more than 20 lines?"
|
|
34
19
|
|
|
35
|
-
|
|
20
|
+
If YES or UNCERTAIN → use `execute` or `execute_file`. Not Bash. Not curl. Not cat.
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
|----------|----------|-----|
|
|
39
|
-
| HTTP requests, JSON APIs | `javascript` | Native fetch, JSON.parse |
|
|
40
|
-
| Data analysis, CSV, math | `python` | pandas, csv module, statistics |
|
|
41
|
-
| Piping commands, grep, find | `shell` | Native OS tools |
|
|
42
|
-
| TypeScript project analysis | `javascript` | Can require/import project files |
|
|
43
|
-
| Log file filtering | `shell` | grep/awk are purpose-built |
|
|
44
|
-
| File comparison | `python` | difflib is excellent |
|
|
22
|
+
**This is not optional.** Every KB of unnecessary context reduces the quality and speed of the entire session.
|
|
45
23
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
### execute — inline code
|
|
24
|
+
## Decision Tree
|
|
49
25
|
|
|
50
26
|
```
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
27
|
+
About to run a command / read a file / call an API?
|
|
28
|
+
│
|
|
29
|
+
├── Output is GUARANTEED small (<20 lines)?
|
|
30
|
+
│ └── Use Bash (git status, pwd, ls, echo, etc.)
|
|
31
|
+
│
|
|
32
|
+
├── Output MIGHT be large or you're UNSURE?
|
|
33
|
+
│ └── Use context-mode execute or execute_file
|
|
34
|
+
│
|
|
35
|
+
├── Fetching web documentation or HTML page?
|
|
36
|
+
│ └── Use fetch_and_index → search
|
|
37
|
+
│
|
|
38
|
+
├── Processing output from another MCP tool (Playwright, Context7, etc.)?
|
|
39
|
+
│ └── Use index → search
|
|
40
|
+
│
|
|
41
|
+
└── Reading a file to analyze/summarize (not edit)?
|
|
42
|
+
└── Use execute_file (file loads into FILE_CONTENT, not context)
|
|
63
43
|
```
|
|
64
44
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
45
|
+
## When to Use Each Tool
|
|
46
|
+
|
|
47
|
+
| Situation | Tool | Example |
|
|
48
|
+
|-----------|------|---------|
|
|
49
|
+
| Hit an API endpoint | `execute` | `fetch('http://localhost:3000/api/orders')` |
|
|
50
|
+
| Run CLI that returns data | `execute` | `gh pr list`, `aws s3 ls`, `kubectl get pods` |
|
|
51
|
+
| Run tests | `execute` | `npm test`, `pytest`, `go test ./...` |
|
|
52
|
+
| Git operations | `execute` | `git log --oneline -50`, `git diff HEAD~5` |
|
|
53
|
+
| Docker/K8s inspection | `execute` | `docker stats --no-stream`, `kubectl describe pod` |
|
|
54
|
+
| Read a log file | `execute_file` | Parse access.log, error.log, build output |
|
|
55
|
+
| Read a data file | `execute_file` | Analyze CSV, JSON, YAML, XML |
|
|
56
|
+
| Read source code to analyze | `execute_file` | Count functions, find patterns, extract metrics |
|
|
57
|
+
| Fetch web docs | `fetch_and_index` | Index React/Next.js/Zod docs, then search |
|
|
58
|
+
| Process large MCP output | `index` → `search` | Index Playwright snapshot, then query elements |
|
|
59
|
+
|
|
60
|
+
## Automatic Triggers
|
|
61
|
+
|
|
62
|
+
Use context-mode for ANY of these, without being asked:
|
|
63
|
+
|
|
64
|
+
- **API debugging**: "hit this endpoint", "call the API", "check the response", "find the bug in the response"
|
|
65
|
+
- **Log analysis**: "check the logs", "what errors", "read access.log", "debug the 500s"
|
|
66
|
+
- **Test runs**: "run the tests", "check if tests pass", "test suite output"
|
|
67
|
+
- **Git history**: "show recent commits", "git log", "what changed", "diff between branches"
|
|
68
|
+
- **Data inspection**: "look at the CSV", "parse the JSON", "analyze the config"
|
|
69
|
+
- **Infrastructure**: "list containers", "check pods", "S3 buckets", "show running services"
|
|
70
|
+
- **Dependency audit**: "check dependencies", "outdated packages", "security audit"
|
|
71
|
+
- **Build output**: "build the project", "check for warnings", "compile errors"
|
|
72
|
+
- **Code metrics**: "count lines", "find TODOs", "function count", "analyze codebase"
|
|
73
|
+
- **Web docs lookup**: "look up the docs", "check the API reference", "find examples"
|
|
74
|
+
|
|
75
|
+
## Language Selection
|
|
76
|
+
|
|
77
|
+
| Situation | Language | Why |
|
|
78
|
+
|-----------|----------|-----|
|
|
79
|
+
| HTTP/API calls, JSON | `javascript` | Native fetch, JSON.parse, async/await |
|
|
80
|
+
| Data analysis, CSV, stats | `python` | csv, statistics, collections, re |
|
|
81
|
+
| Shell commands with pipes | `shell` | grep, awk, jq, native tools |
|
|
82
|
+
| File pattern matching | `shell` | find, wc, sort, uniq |
|
|
75
83
|
|
|
76
84
|
## Critical Rules
|
|
77
85
|
|
|
78
|
-
1. **Always print
|
|
79
|
-
2. **
|
|
80
|
-
3. **
|
|
81
|
-
4. **
|
|
82
|
-
5. **
|
|
86
|
+
1. **Always console.log/print your findings.** stdout is all that enters context. No output = wasted call.
|
|
87
|
+
2. **Write analysis code, not just data dumps.** Don't `console.log(JSON.stringify(data))` — analyze first, print findings.
|
|
88
|
+
3. **Be specific in output.** Print bug details with IDs, line numbers, exact values — not just counts.
|
|
89
|
+
4. **For files you need to EDIT**: Use the normal Read tool. context-mode is for analysis, not editing.
|
|
90
|
+
5. **For tiny outputs (<5 lines guaranteed)**: Use Bash. Don't over-engineer `git status` through context-mode.
|
|
83
91
|
|
|
84
|
-
## Examples
|
|
92
|
+
## Examples
|
|
85
93
|
|
|
86
|
-
###
|
|
94
|
+
### Debug an API endpoint
|
|
87
95
|
```javascript
|
|
88
|
-
const resp = await fetch('
|
|
89
|
-
const
|
|
90
|
-
|
|
96
|
+
const resp = await fetch('http://localhost:3000/api/orders');
|
|
97
|
+
const { orders } = await resp.json();
|
|
98
|
+
|
|
99
|
+
const bugs = [];
|
|
100
|
+
const negQty = orders.filter(o => o.quantity < 0);
|
|
101
|
+
if (negQty.length) bugs.push(`Negative qty: ${negQty.map(o => o.id).join(', ')}`);
|
|
102
|
+
|
|
103
|
+
const nullFields = orders.filter(o => !o.product || !o.customer);
|
|
104
|
+
if (nullFields.length) bugs.push(`Null fields: ${nullFields.map(o => o.id).join(', ')}`);
|
|
105
|
+
|
|
106
|
+
console.log(`${orders.length} orders, ${bugs.length} bugs found:`);
|
|
107
|
+
bugs.forEach(b => console.log(`- ${b}`));
|
|
91
108
|
```
|
|
92
|
-
> summary_prompt: "Report service health, any degraded components, and error rates"
|
|
93
109
|
|
|
94
|
-
###
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
errors = [l for l in f if 'ERROR' in l]
|
|
99
|
-
for e in errors[-50:]:
|
|
100
|
-
print(e.strip())
|
|
101
|
-
print(f"\nTotal errors: {len(errors)}")
|
|
110
|
+
### Analyze test output
|
|
111
|
+
```shell
|
|
112
|
+
npm test 2>&1
|
|
113
|
+
echo "EXIT=$?"
|
|
102
114
|
```
|
|
103
|
-
> summary_prompt: "Categorize errors by type and report frequency of each"
|
|
104
115
|
|
|
105
|
-
###
|
|
116
|
+
### Check GitHub PRs
|
|
106
117
|
```shell
|
|
107
|
-
|
|
108
|
-
|
|
118
|
+
gh pr list --json number,title,state,reviewDecision --jq '.[] | "\(.number) [\(.state)] \(.title) — \(.reviewDecision // "no review")"'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Read and analyze a large file
|
|
122
|
+
```python
|
|
123
|
+
# FILE_CONTENT is pre-loaded by execute_file
|
|
124
|
+
import json
|
|
125
|
+
data = json.loads(FILE_CONTENT)
|
|
126
|
+
print(f"Records: {len(data)}")
|
|
127
|
+
# ... analyze and print findings
|
|
109
128
|
```
|
|
110
|
-
> summary_prompt: "Report success/failure, list any errors or warnings with file locations"
|
|
111
129
|
|
|
112
|
-
## Anti-Patterns
|
|
130
|
+
## Anti-Patterns
|
|
113
131
|
|
|
114
|
-
- Using `
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
-
|
|
132
|
+
- Using `curl http://api/endpoint` via Bash → 50KB floods context. Use `execute` with fetch instead.
|
|
133
|
+
- Using `cat large-file.json` via Bash → entire file in context. Use `execute_file` instead.
|
|
134
|
+
- Using `gh pr list` via Bash → raw JSON in context. Use `execute` with `--jq` filter instead.
|
|
135
|
+
- Piping Bash output through `| head -20` → you lose the rest. Use `execute` to analyze ALL data and print summary.
|
|
136
|
+
- Running `npm test` via Bash → full test output in context. Use `execute` to capture and summarize.
|
|
118
137
|
|
|
119
138
|
## Reference Files
|
|
120
139
|
|