context-mode 0.4.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/plugin.json +29 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.md +304 -0
- package/build/cli.d.ts +10 -0
- package/build/cli.js +193 -0
- package/build/executor.d.ts +27 -0
- package/build/executor.js +255 -0
- package/build/runtime.d.ts +24 -0
- package/build/runtime.js +167 -0
- package/build/server.d.ts +2 -0
- package/build/server.js +457 -0
- package/build/store.d.ts +39 -0
- package/build/store.js +212 -0
- package/package.json +64 -0
- package/skills/context-mode/SKILL.md +124 -0
- package/skills/context-mode/references/anti-patterns.md +257 -0
- package/skills/context-mode/references/patterns-javascript.md +298 -0
- package/skills/context-mode/references/patterns-python.md +304 -0
- package/skills/context-mode/references/patterns-shell.md +277 -0
package/build/store.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ContentStore — FTS5 BM25-based knowledge base for context-mode.
|
|
3
|
+
*
|
|
4
|
+
* Chunks markdown content by headings (keeping code blocks intact),
|
|
5
|
+
* stores in SQLite FTS5, and retrieves via BM25-ranked search.
|
|
6
|
+
*
|
|
7
|
+
* Use for documentation, API references, and any content where
|
|
8
|
+
* you need EXACT text later — not summaries.
|
|
9
|
+
*/
|
|
10
|
+
import Database from "better-sqlite3";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
// ─────────────────────────────────────────────────────────
|
|
15
|
+
// Helpers
|
|
16
|
+
// ─────────────────────────────────────────────────────────
|
|
17
|
+
function sanitizeQuery(query) {
|
|
18
|
+
const words = query
|
|
19
|
+
.replace(/['"(){}[\]*:^~]/g, " ")
|
|
20
|
+
.split(/\s+/)
|
|
21
|
+
.filter((w) => w.length > 0 &&
|
|
22
|
+
!["AND", "OR", "NOT", "NEAR"].includes(w.toUpperCase()));
|
|
23
|
+
if (words.length === 0)
|
|
24
|
+
return '""';
|
|
25
|
+
return words.map((w) => `"${w}"`).join(" ");
|
|
26
|
+
}
|
|
27
|
+
// ─────────────────────────────────────────────────────────
|
|
28
|
+
// ContentStore
|
|
29
|
+
// ─────────────────────────────────────────────────────────
|
|
30
|
+
export class ContentStore {
|
|
31
|
+
#db;
|
|
32
|
+
constructor(dbPath) {
|
|
33
|
+
const path = dbPath ?? join(tmpdir(), `context-mode-${process.pid}.db`);
|
|
34
|
+
this.#db = new Database(path);
|
|
35
|
+
this.#db.pragma("journal_mode = WAL");
|
|
36
|
+
this.#db.pragma("synchronous = NORMAL");
|
|
37
|
+
this.#initSchema();
|
|
38
|
+
}
|
|
39
|
+
// ── Schema ──
|
|
40
|
+
#initSchema() {
|
|
41
|
+
this.#db.exec(`
|
|
42
|
+
CREATE TABLE IF NOT EXISTS sources (
|
|
43
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
44
|
+
label TEXT NOT NULL,
|
|
45
|
+
chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
46
|
+
code_chunk_count INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
indexed_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS chunks USING fts5(
|
|
51
|
+
title,
|
|
52
|
+
content,
|
|
53
|
+
source_id UNINDEXED,
|
|
54
|
+
content_type UNINDEXED,
|
|
55
|
+
tokenize='porter unicode61'
|
|
56
|
+
);
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
// ── Index ──
|
|
60
|
+
index(options) {
|
|
61
|
+
const { content, path, source } = options;
|
|
62
|
+
if (!content && !path) {
|
|
63
|
+
throw new Error("Either content or path must be provided");
|
|
64
|
+
}
|
|
65
|
+
const text = content ?? readFileSync(path, "utf-8");
|
|
66
|
+
const label = source ?? path ?? "untitled";
|
|
67
|
+
const chunks = this.#chunkMarkdown(text);
|
|
68
|
+
if (chunks.length === 0) {
|
|
69
|
+
const insertSource = this.#db.prepare("INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, 0, 0)");
|
|
70
|
+
const info = insertSource.run(label);
|
|
71
|
+
return {
|
|
72
|
+
sourceId: Number(info.lastInsertRowid),
|
|
73
|
+
label,
|
|
74
|
+
totalChunks: 0,
|
|
75
|
+
codeChunks: 0,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const codeChunks = chunks.filter((c) => c.hasCode).length;
|
|
79
|
+
const insertSource = this.#db.prepare("INSERT INTO sources (label, chunk_count, code_chunk_count) VALUES (?, ?, ?)");
|
|
80
|
+
const insertChunk = this.#db.prepare("INSERT INTO chunks (title, content, source_id, content_type) VALUES (?, ?, ?, ?)");
|
|
81
|
+
const transaction = this.#db.transaction(() => {
|
|
82
|
+
const info = insertSource.run(label, chunks.length, codeChunks);
|
|
83
|
+
const sourceId = Number(info.lastInsertRowid);
|
|
84
|
+
for (const chunk of chunks) {
|
|
85
|
+
insertChunk.run(chunk.title, chunk.content, sourceId, chunk.hasCode ? "code" : "prose");
|
|
86
|
+
}
|
|
87
|
+
return sourceId;
|
|
88
|
+
});
|
|
89
|
+
const sourceId = transaction();
|
|
90
|
+
return {
|
|
91
|
+
sourceId,
|
|
92
|
+
label,
|
|
93
|
+
totalChunks: chunks.length,
|
|
94
|
+
codeChunks,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ── Search ──
|
|
98
|
+
search(query, limit = 3) {
|
|
99
|
+
const sanitized = sanitizeQuery(query);
|
|
100
|
+
const stmt = this.#db.prepare(`
|
|
101
|
+
SELECT
|
|
102
|
+
chunks.title,
|
|
103
|
+
chunks.content,
|
|
104
|
+
chunks.content_type,
|
|
105
|
+
sources.label,
|
|
106
|
+
bm25(chunks, 2.0, 1.0) AS rank
|
|
107
|
+
FROM chunks
|
|
108
|
+
JOIN sources ON sources.id = chunks.source_id
|
|
109
|
+
WHERE chunks MATCH ?
|
|
110
|
+
ORDER BY rank
|
|
111
|
+
LIMIT ?
|
|
112
|
+
`);
|
|
113
|
+
const rows = stmt.all(sanitized, limit);
|
|
114
|
+
return rows.map((r) => ({
|
|
115
|
+
title: r.title,
|
|
116
|
+
content: r.content,
|
|
117
|
+
source: r.label,
|
|
118
|
+
rank: r.rank,
|
|
119
|
+
contentType: r.content_type,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
// ── Stats ──
|
|
123
|
+
getStats() {
|
|
124
|
+
const sources = this.#db.prepare("SELECT COUNT(*) as c FROM sources").get()?.c ?? 0;
|
|
125
|
+
const chunks = this.#db
|
|
126
|
+
.prepare("SELECT COUNT(*) as c FROM chunks")
|
|
127
|
+
.get()?.c ?? 0;
|
|
128
|
+
const codeChunks = this.#db
|
|
129
|
+
.prepare("SELECT COUNT(*) as c FROM chunks WHERE content_type = 'code'")
|
|
130
|
+
.get()?.c ?? 0;
|
|
131
|
+
return { sources, chunks, codeChunks };
|
|
132
|
+
}
|
|
133
|
+
// ── Cleanup ──
|
|
134
|
+
close() {
|
|
135
|
+
this.#db.close();
|
|
136
|
+
}
|
|
137
|
+
// ── Chunking ──
|
|
138
|
+
#chunkMarkdown(text) {
|
|
139
|
+
const chunks = [];
|
|
140
|
+
const lines = text.split("\n");
|
|
141
|
+
const headingStack = [];
|
|
142
|
+
let currentContent = [];
|
|
143
|
+
let currentHeading = "";
|
|
144
|
+
const flush = () => {
|
|
145
|
+
const joined = currentContent.join("\n").trim();
|
|
146
|
+
if (joined.length === 0)
|
|
147
|
+
return;
|
|
148
|
+
chunks.push({
|
|
149
|
+
title: this.#buildTitle(headingStack, currentHeading),
|
|
150
|
+
content: joined,
|
|
151
|
+
hasCode: currentContent.some((l) => /^`{3,}/.test(l)),
|
|
152
|
+
});
|
|
153
|
+
currentContent = [];
|
|
154
|
+
};
|
|
155
|
+
let i = 0;
|
|
156
|
+
while (i < lines.length) {
|
|
157
|
+
const line = lines[i];
|
|
158
|
+
// Horizontal rule separator (Context7 uses long dashes)
|
|
159
|
+
if (/^[-_*]{3,}\s*$/.test(line)) {
|
|
160
|
+
flush();
|
|
161
|
+
i++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
// Heading (H1-H4)
|
|
165
|
+
const headingMatch = line.match(/^(#{1,4})\s+(.+)$/);
|
|
166
|
+
if (headingMatch) {
|
|
167
|
+
flush();
|
|
168
|
+
const level = headingMatch[1].length;
|
|
169
|
+
const heading = headingMatch[2].trim();
|
|
170
|
+
// Pop deeper levels from stack
|
|
171
|
+
while (headingStack.length > 0 &&
|
|
172
|
+
headingStack[headingStack.length - 1].level >= level) {
|
|
173
|
+
headingStack.pop();
|
|
174
|
+
}
|
|
175
|
+
headingStack.push({ level, text: heading });
|
|
176
|
+
currentHeading = heading;
|
|
177
|
+
currentContent.push(line);
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Code block — collect entire block as a unit
|
|
182
|
+
const codeMatch = line.match(/^(`{3,})(.*)?$/);
|
|
183
|
+
if (codeMatch) {
|
|
184
|
+
const fence = codeMatch[1];
|
|
185
|
+
const codeLines = [line];
|
|
186
|
+
i++;
|
|
187
|
+
while (i < lines.length) {
|
|
188
|
+
codeLines.push(lines[i]);
|
|
189
|
+
if (lines[i].startsWith(fence) && lines[i].trim() === fence) {
|
|
190
|
+
i++;
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
i++;
|
|
194
|
+
}
|
|
195
|
+
currentContent.push(...codeLines);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// Regular line
|
|
199
|
+
currentContent.push(line);
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
// Flush remaining content
|
|
203
|
+
flush();
|
|
204
|
+
return chunks;
|
|
205
|
+
}
|
|
206
|
+
#buildTitle(headingStack, currentHeading) {
|
|
207
|
+
if (headingStack.length === 0) {
|
|
208
|
+
return currentHeading || "Untitled";
|
|
209
|
+
}
|
|
210
|
+
return headingStack.map((h) => h.text).join(" > ");
|
|
211
|
+
}
|
|
212
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-mode",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Claude Code MCP plugin that saves 94% of your context window. Sandboxed code execution, FTS5 knowledge base, and smart truncation.",
|
|
6
|
+
"author": "Mert Koseoğlu",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"mcp",
|
|
10
|
+
"model-context-protocol",
|
|
11
|
+
"claude",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"context-window",
|
|
14
|
+
"sandbox",
|
|
15
|
+
"code-execution",
|
|
16
|
+
"fts5",
|
|
17
|
+
"bm25"
|
|
18
|
+
],
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/mksglu/claude-context-mode"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/mksglu/claude-context-mode#readme",
|
|
24
|
+
"bugs": "https://github.com/mksglu/claude-context-mode/issues",
|
|
25
|
+
"bin": {
|
|
26
|
+
"context-mode": "./build/server.js"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"build",
|
|
30
|
+
"skills",
|
|
31
|
+
".claude-plugin",
|
|
32
|
+
".mcp.json",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"dev": "npx tsx src/server.ts",
|
|
40
|
+
"setup": "npx tsx src/cli.ts setup",
|
|
41
|
+
"doctor": "npx tsx src/cli.ts doctor",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "npx tsx tests/executor.test.ts",
|
|
44
|
+
"benchmark": "npx tsx tests/benchmark.ts",
|
|
45
|
+
"test:use-cases": "npx tsx tests/use-cases.ts",
|
|
46
|
+
"test:compare": "npx tsx tests/context-comparison.ts",
|
|
47
|
+
"test:ecosystem": "npx tsx tests/ecosystem-benchmark.ts",
|
|
48
|
+
"test:store": "npx tsx tests/store.test.ts",
|
|
49
|
+
"test:all": "npx tsx tests/executor.test.ts && npx tsx tests/store.test.ts && npx tsx tests/use-cases.ts && npx tsx tests/context-comparison.ts && npx tsx tests/benchmark.ts && npx tsx tests/ecosystem-benchmark.ts"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@clack/prompts": "^1.0.1",
|
|
53
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
54
|
+
"better-sqlite3": "^12.6.2",
|
|
55
|
+
"picocolors": "^1.1.1",
|
|
56
|
+
"zod": "^3.25.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
60
|
+
"@types/node": "^22.19.11",
|
|
61
|
+
"tsx": "^4.21.0",
|
|
62
|
+
"typescript": "^5.7.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context-mode
|
|
3
|
+
description: |
|
|
4
|
+
Use context-mode tools (execute, execute_file) instead of Bash/cat when processing
|
|
5
|
+
large outputs. Trigger phrases: "analyze logs", "summarize output", "process data",
|
|
6
|
+
"parse JSON", "filter results", "extract errors", "check build output",
|
|
7
|
+
"analyze dependencies", "process API response", "large file analysis".
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Context Mode: execute & execute_file
|
|
11
|
+
|
|
12
|
+
## When to Use (Decision Tree)
|
|
13
|
+
|
|
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` |
|
|
31
|
+
|
|
32
|
+
Both tools execute code and return an **LLM-generated summary** instead of raw stdout.
|
|
33
|
+
The raw output never enters your context window — only the summary does.
|
|
34
|
+
|
|
35
|
+
## Language Selection Guide
|
|
36
|
+
|
|
37
|
+
| Scenario | Language | Why |
|
|
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 |
|
|
45
|
+
|
|
46
|
+
## Usage Pattern
|
|
47
|
+
|
|
48
|
+
### execute — inline code
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
Tool: execute
|
|
52
|
+
Parameters:
|
|
53
|
+
code: |
|
|
54
|
+
const data = require('fs').readFileSync('package.json', 'utf8');
|
|
55
|
+
const pkg = JSON.parse(data);
|
|
56
|
+
console.log(`Name: ${pkg.name}`);
|
|
57
|
+
console.log(`Dependencies: ${Object.keys(pkg.dependencies || {}).length}`);
|
|
58
|
+
console.log(`DevDependencies: ${Object.keys(pkg.devDependencies || {}).length}`);
|
|
59
|
+
Object.entries(pkg.dependencies || {}).forEach(([k, v]) => console.log(` ${k}: ${v}`));
|
|
60
|
+
language: javascript
|
|
61
|
+
timeout_ms: 10000
|
|
62
|
+
summary_prompt: "List the package name, dependency count, and any outdated patterns"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### execute_file — run existing script
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Tool: execute_file
|
|
69
|
+
Parameters:
|
|
70
|
+
file_path: ./scripts/analyze-bundle.js
|
|
71
|
+
args: ["--format", "summary"]
|
|
72
|
+
timeout_ms: 30000
|
|
73
|
+
summary_prompt: "Report bundle size changes and any chunks exceeding 500KB"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Critical Rules
|
|
77
|
+
|
|
78
|
+
1. **Always print/log output.** The tool captures stdout. No output = empty summary.
|
|
79
|
+
2. **Use `summary_prompt`** to guide what the LLM extracts from the output.
|
|
80
|
+
3. **Set appropriate `timeout_ms`** — network calls need 15000+, file ops need 5000+.
|
|
81
|
+
4. **Print structured data** — JSON.stringify or formatted tables summarize better.
|
|
82
|
+
5. **Don't use for < 20 lines** — Bash is simpler and wastes no LLM call.
|
|
83
|
+
|
|
84
|
+
## Examples by Language
|
|
85
|
+
|
|
86
|
+
### JavaScript: API response analysis
|
|
87
|
+
```javascript
|
|
88
|
+
const resp = await fetch('https://api.example.com/status');
|
|
89
|
+
const data = await resp.json();
|
|
90
|
+
console.log(JSON.stringify(data, null, 2));
|
|
91
|
+
```
|
|
92
|
+
> summary_prompt: "Report service health, any degraded components, and error rates"
|
|
93
|
+
|
|
94
|
+
### Python: Log analysis
|
|
95
|
+
```python
|
|
96
|
+
import re
|
|
97
|
+
with open('/var/log/app.log') as f:
|
|
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)}")
|
|
102
|
+
```
|
|
103
|
+
> summary_prompt: "Categorize errors by type and report frequency of each"
|
|
104
|
+
|
|
105
|
+
### Shell: Build output filtering
|
|
106
|
+
```shell
|
|
107
|
+
npm run build 2>&1
|
|
108
|
+
echo "EXIT_CODE=$?"
|
|
109
|
+
```
|
|
110
|
+
> summary_prompt: "Report success/failure, list any errors or warnings with file locations"
|
|
111
|
+
|
|
112
|
+
## Anti-Patterns (Avoid These)
|
|
113
|
+
|
|
114
|
+
- Using `execute` for `git status` (small output — use Bash)
|
|
115
|
+
- Forgetting `console.log()` / `print()` (produces empty summary)
|
|
116
|
+
- Setting `timeout_ms: 5000` for network requests (will timeout)
|
|
117
|
+
- Loading a 10K-line file into context then asking to summarize (use execute instead)
|
|
118
|
+
|
|
119
|
+
## Reference Files
|
|
120
|
+
|
|
121
|
+
- [JavaScript/TypeScript Patterns](./references/patterns-javascript.md)
|
|
122
|
+
- [Python Patterns](./references/patterns-python.md)
|
|
123
|
+
- [Shell Patterns](./references/patterns-shell.md)
|
|
124
|
+
- [Anti-Patterns & Common Mistakes](./references/anti-patterns.md)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Anti-Patterns: Common Mistakes with execute / execute_file
|
|
2
|
+
|
|
3
|
+
Avoid these pitfalls when using context-mode tools.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Using execute for Small Outputs (< 20 Lines)
|
|
8
|
+
|
|
9
|
+
**Problem:** `execute` adds overhead (LLM summarization call). For small outputs, Bash is faster and cheaper.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
BAD — wasteful use of execute:
|
|
13
|
+
Tool: execute
|
|
14
|
+
code: "echo $(node --version)"
|
|
15
|
+
language: shell
|
|
16
|
+
|
|
17
|
+
GOOD — just use Bash:
|
|
18
|
+
Tool: Bash
|
|
19
|
+
command: node --version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Rule:** If the output fits comfortably in your context window (under ~20 lines), use Bash directly. Reserve `execute` for outputs that would bloat context or need intelligent summarization.
|
|
23
|
+
|
|
24
|
+
More examples of "just use Bash":
|
|
25
|
+
- `git status` — usually 5-10 lines
|
|
26
|
+
- `ls -la` — directory listing
|
|
27
|
+
- `cat .env.example` — small config file
|
|
28
|
+
- `pwd`, `whoami`, `which node`
|
|
29
|
+
- `wc -l src/index.ts` — single line output
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Forgetting to Print Output
|
|
34
|
+
|
|
35
|
+
**Problem:** `execute` captures stdout. If your code doesn't print anything, the summary will be empty or meaningless.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
// BAD — no output:
|
|
39
|
+
const fs = require('fs');
|
|
40
|
+
const data = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
41
|
+
const deps = Object.keys(data.dependencies);
|
|
42
|
+
// Nothing printed! The LLM sees empty stdout.
|
|
43
|
+
|
|
44
|
+
// GOOD — explicit output:
|
|
45
|
+
const fs = require('fs');
|
|
46
|
+
const data = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
47
|
+
const deps = Object.keys(data.dependencies);
|
|
48
|
+
console.log(`Dependencies (${deps.length}):`);
|
|
49
|
+
deps.forEach(d => console.log(` ${d}: ${data.dependencies[d]}`));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
# BAD — computes but never prints:
|
|
54
|
+
with open('data.json') as f:
|
|
55
|
+
data = json.load(f)
|
|
56
|
+
result = [x for x in data if x['status'] == 'error']
|
|
57
|
+
# result is lost — never printed
|
|
58
|
+
|
|
59
|
+
# GOOD — always print results:
|
|
60
|
+
with open('data.json') as f:
|
|
61
|
+
data = json.load(f)
|
|
62
|
+
result = [x for x in data if x['status'] == 'error']
|
|
63
|
+
print(f"Found {len(result)} errors:")
|
|
64
|
+
for r in result:
|
|
65
|
+
print(f" {r['id']}: {r['message']}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Rule:** Every `execute` script must end with print/console.log of the results you want summarized.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 3. Using Bash When JS/Python Would Be Cleaner
|
|
73
|
+
|
|
74
|
+
**Problem:** Complex data processing in Bash quickly becomes unreadable and error-prone.
|
|
75
|
+
|
|
76
|
+
```shell
|
|
77
|
+
# BAD — parsing JSON in Bash is fragile:
|
|
78
|
+
cat data.json | python3 -c "
|
|
79
|
+
import sys, json
|
|
80
|
+
data = json.load(sys.stdin)
|
|
81
|
+
for item in data:
|
|
82
|
+
if item['status'] == 'error':
|
|
83
|
+
print(item['id'], item['message'])
|
|
84
|
+
"
|
|
85
|
+
# If you're already using Python inline, just use language: python
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// GOOD — use the right language for the job:
|
|
90
|
+
// language: javascript
|
|
91
|
+
const data = require('./data.json');
|
|
92
|
+
data.filter(x => x.status === 'error')
|
|
93
|
+
.forEach(x => console.log(`${x.id}: ${x.message}`));
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Rule:** If your Bash script contains inline Python/Node or complex `jq`/`awk` chains, switch to `language: python` or `language: javascript` instead.
|
|
97
|
+
|
|
98
|
+
Signs you should switch from shell:
|
|
99
|
+
- Using `python3 -c` or `node -e` inside the shell script
|
|
100
|
+
- More than 3 pipes chained together
|
|
101
|
+
- Using `jq` for complex JSON transformations
|
|
102
|
+
- Nested loops in Bash
|
|
103
|
+
- String manipulation beyond simple `cut`/`sed`
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 4. Loading Entire Files into Context Then Processing
|
|
108
|
+
|
|
109
|
+
**Problem:** Reading a 10,000-line file with `Read` tool, then asking about it, wastes your entire context window. Use `execute` to process the file and return only the summary.
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
BAD workflow:
|
|
113
|
+
1. Read tool: read 'server.log' (10,000 lines loaded into context)
|
|
114
|
+
2. "Find all errors in this log"
|
|
115
|
+
→ 10,000 lines consumed context for a question that needs ~20 lines of output
|
|
116
|
+
|
|
117
|
+
GOOD workflow:
|
|
118
|
+
1. execute with language: python
|
|
119
|
+
code: |
|
|
120
|
+
with open('server.log') as f:
|
|
121
|
+
errors = [l for l in f if 'ERROR' in l]
|
|
122
|
+
print(f"Total errors: {len(errors)}")
|
|
123
|
+
for e in errors[-20:]:
|
|
124
|
+
print(e.strip())
|
|
125
|
+
summary_prompt: "Categorize errors and report frequency"
|
|
126
|
+
→ Only the summary enters context
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
BAD workflow:
|
|
131
|
+
1. Read tool: read 'package-lock.json' (20,000 lines)
|
|
132
|
+
2. "What version of lodash is installed?"
|
|
133
|
+
|
|
134
|
+
GOOD workflow:
|
|
135
|
+
1. execute with language: javascript
|
|
136
|
+
code: |
|
|
137
|
+
const lock = require('./package-lock.json');
|
|
138
|
+
const find = (deps, name) => {
|
|
139
|
+
if (deps[name]) return deps[name].version;
|
|
140
|
+
for (const [, dep] of Object.entries(deps)) {
|
|
141
|
+
if (dep.dependencies) {
|
|
142
|
+
const v = find(dep.dependencies, name);
|
|
143
|
+
if (v) return v;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
console.log(`lodash: ${find(lock.dependencies, 'lodash') || 'not found'}`);
|
|
148
|
+
summary_prompt: "Report the installed version of lodash"
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Rule:** If a file is over 200 lines and you only need specific data from it, use `execute` to extract what you need rather than reading the whole file into context.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## 5. Not Using JSON.stringify for Structured Output
|
|
156
|
+
|
|
157
|
+
**Problem:** Printing objects without serialization gives `[object Object]` in JavaScript.
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// BAD — prints [object Object]:
|
|
161
|
+
const pkg = require('./package.json');
|
|
162
|
+
console.log(pkg.dependencies);
|
|
163
|
+
// Output: [object Object]
|
|
164
|
+
|
|
165
|
+
// GOOD — serialize properly:
|
|
166
|
+
const pkg = require('./package.json');
|
|
167
|
+
console.log(JSON.stringify(pkg.dependencies, null, 2));
|
|
168
|
+
// Output: { "react": "^18.2.0", "next": "^14.0.0", ... }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
// BAD — loses structure in arrays:
|
|
173
|
+
const items = [{name: 'a', value: 1}, {name: 'b', value: 2}];
|
|
174
|
+
console.log(items);
|
|
175
|
+
// May print unhelpfully
|
|
176
|
+
|
|
177
|
+
// GOOD — format as table:
|
|
178
|
+
const items = [{name: 'a', value: 1}, {name: 'b', value: 2}];
|
|
179
|
+
console.log('Name | Value');
|
|
180
|
+
console.log('------|------');
|
|
181
|
+
items.forEach(i => console.log(`${i.name.padEnd(5)} | ${i.value}`));
|
|
182
|
+
// Or use JSON.stringify:
|
|
183
|
+
console.log(JSON.stringify(items, null, 2));
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Rule:** Always use `JSON.stringify(data, null, 2)` for objects/arrays in JavaScript, or format as a readable table. In Python, use `json.dumps(data, indent=2)` or `pprint.pprint(data)`.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 6. Timeout Too Short for Network Operations
|
|
191
|
+
|
|
192
|
+
**Problem:** Default timeout may be too short for API calls, builds, or test suites.
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
BAD — will timeout on API calls:
|
|
196
|
+
Tool: execute
|
|
197
|
+
code: |
|
|
198
|
+
const resp = await fetch('https://api.slow-service.com/data');
|
|
199
|
+
console.log(await resp.json());
|
|
200
|
+
language: javascript
|
|
201
|
+
timeout_ms: 5000 ← API may take 10+ seconds
|
|
202
|
+
|
|
203
|
+
GOOD — generous timeout for network:
|
|
204
|
+
Tool: execute
|
|
205
|
+
code: |
|
|
206
|
+
const resp = await fetch('https://api.slow-service.com/data');
|
|
207
|
+
console.log(JSON.stringify(await resp.json(), null, 2));
|
|
208
|
+
language: javascript
|
|
209
|
+
timeout_ms: 30000 ← 30 seconds for network calls
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Recommended timeouts:**
|
|
213
|
+
| Operation | timeout_ms |
|
|
214
|
+
|-----------|-----------|
|
|
215
|
+
| File reading/parsing | 5000 - 10000 |
|
|
216
|
+
| Local computation | 10000 |
|
|
217
|
+
| Single API request | 15000 - 30000 |
|
|
218
|
+
| Paginated API calls | 30000 - 60000 |
|
|
219
|
+
| npm install / build | 120000 |
|
|
220
|
+
| Full test suite | 120000 - 300000 |
|
|
221
|
+
|
|
222
|
+
**Rule:** Always consider what your script does and set `timeout_ms` accordingly. Network calls and builds need significantly more time than file operations.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 7. Not Using summary_prompt Effectively
|
|
227
|
+
|
|
228
|
+
**Problem:** Without a good `summary_prompt`, the LLM summarization may focus on irrelevant details.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
BAD — vague or missing summary_prompt:
|
|
232
|
+
summary_prompt: "Summarize this"
|
|
233
|
+
→ May focus on the wrong aspects
|
|
234
|
+
|
|
235
|
+
GOOD — specific and actionable:
|
|
236
|
+
summary_prompt: "Report the count of failing tests, list each failure with its file path and error message, and identify any patterns in the failures"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Tips for effective summary_prompt:**
|
|
240
|
+
- Be specific about what data points you need
|
|
241
|
+
- Ask for counts and metrics, not just descriptions
|
|
242
|
+
- Request actionable insights ("suggest fixes", "identify patterns")
|
|
243
|
+
- Mention the format you want ("list as bullet points", "group by category")
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Summary Checklist
|
|
248
|
+
|
|
249
|
+
Before using `execute`, verify:
|
|
250
|
+
|
|
251
|
+
- [ ] Output will be > 20 lines (otherwise use Bash)
|
|
252
|
+
- [ ] Script prints all results to stdout
|
|
253
|
+
- [ ] Objects are serialized with JSON.stringify / json.dumps
|
|
254
|
+
- [ ] Timeout matches the operation type
|
|
255
|
+
- [ ] Language matches the task (JS for JSON/API, Python for data, Shell for pipes)
|
|
256
|
+
- [ ] summary_prompt is specific and actionable
|
|
257
|
+
- [ ] Not loading a file into context that could be processed inside execute
|