@milo4jo/contextkit 0.5.1 → 0.5.6
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 +59 -1
- package/dist/commands/doctor.d.ts +3 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +254 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/graph.d.ts +10 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +289 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/select.d.ts.map +1 -1
- package/dist/commands/select.js +9 -0
- package/dist/commands/select.js.map +1 -1
- package/dist/commands/symbol.d.ts +9 -0
- package/dist/commands/symbol.d.ts.map +1 -0
- package/dist/commands/symbol.js +420 -0
- package/dist/commands/symbol.js.map +1 -0
- package/dist/db/index.d.ts +1 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -0
- package/dist/db/index.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/indexer/chunker.d.ts +10 -1
- package/dist/indexer/chunker.d.ts.map +1 -1
- package/dist/indexer/chunker.js +123 -2
- package/dist/indexer/chunker.js.map +1 -1
- package/dist/indexer/index.d.ts.map +1 -1
- package/dist/indexer/index.js +3 -2
- package/dist/indexer/index.js.map +1 -1
- package/dist/mcp-server.js +0 -0
- package/dist/parsers/index.d.ts +26 -6
- package/dist/parsers/index.d.ts.map +1 -1
- package/dist/parsers/index.js +71 -12
- package/dist/parsers/index.js.map +1 -1
- package/dist/parsers/markdown.d.ts +29 -0
- package/dist/parsers/markdown.d.ts.map +1 -0
- package/dist/parsers/markdown.js +142 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/parsers/tree-sitter.d.ts +32 -0
- package/dist/parsers/tree-sitter.d.ts.map +1 -0
- package/dist/parsers/tree-sitter.js +356 -0
- package/dist/parsers/tree-sitter.js.map +1 -0
- package/dist/selector/formatter.d.ts +4 -1
- package/dist/selector/formatter.d.ts.map +1 -1
- package/dist/selector/formatter.js +214 -6
- package/dist/selector/formatter.js.map +1 -1
- package/dist/selector/index.d.ts +4 -0
- package/dist/selector/index.d.ts.map +1 -1
- package/dist/selector/index.js +3 -1
- package/dist/selector/index.js.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/@milo4jo/contextkit)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
|
|
10
|
-
**🆕 v0.5:**
|
|
10
|
+
**🆕 v0.5.5:** Symbol search & call graph! Find code by name, trace dependencies across your codebase.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -185,10 +185,57 @@ contextkit select "query" --format plain # Plain text, no formatting
|
|
|
185
185
|
# Include imported files (follows dependency graph)
|
|
186
186
|
contextkit select "query" --include-imports
|
|
187
187
|
|
|
188
|
+
# Repo map mode (signatures only, saves tokens)
|
|
189
|
+
contextkit select "query" --mode map
|
|
190
|
+
|
|
188
191
|
# Pipe to clipboard (macOS)
|
|
189
192
|
contextkit select "query" --format plain | pbcopy
|
|
190
193
|
```
|
|
191
194
|
|
|
195
|
+
### `contextkit symbol`
|
|
196
|
+
|
|
197
|
+
Search for code by symbol name (faster than semantic search when you know the name).
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Find a function or class by name
|
|
201
|
+
contextkit symbol "UserService"
|
|
202
|
+
|
|
203
|
+
# Exact match only
|
|
204
|
+
contextkit symbol "handleAuth" --exact
|
|
205
|
+
|
|
206
|
+
# Limit results
|
|
207
|
+
contextkit symbol "parse" --limit 10
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**Output:**
|
|
211
|
+
```
|
|
212
|
+
📄 src/services/user.ts
|
|
213
|
+
│ ◆ UserService (line 12)
|
|
214
|
+
│ export class UserService
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `contextkit graph`
|
|
218
|
+
|
|
219
|
+
Show call relationships for a function.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
contextkit graph "handlePayment"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Output:**
|
|
226
|
+
```
|
|
227
|
+
🎯 Call graph for: handlePayment
|
|
228
|
+
|
|
229
|
+
📥 Callers (2):
|
|
230
|
+
← processOrder (src/orders/service.ts:45)
|
|
231
|
+
← checkout (src/cart/checkout.ts:89)
|
|
232
|
+
|
|
233
|
+
📤 Calls (3):
|
|
234
|
+
→ validateCard (src/payments/validation.ts)
|
|
235
|
+
→ chargeCard (src/payments/stripe.ts)
|
|
236
|
+
→ sendReceipt (src/notifications/email.ts)
|
|
237
|
+
```
|
|
238
|
+
|
|
192
239
|
---
|
|
193
240
|
|
|
194
241
|
## 🤖 MCP Server (Claude Desktop Integration)
|
|
@@ -294,6 +341,17 @@ settings:
|
|
|
294
341
|
|
|
295
342
|
---
|
|
296
343
|
|
|
344
|
+
## 📚 Documentation
|
|
345
|
+
|
|
346
|
+
- **[Getting Started Guide](./docs/getting-started.md)** — Detailed walkthrough
|
|
347
|
+
- **[MCP Setup Guide](./docs/mcp-setup.md)** — Claude Desktop integration
|
|
348
|
+
- **[Examples](./examples/README.md)** — Real-world use cases
|
|
349
|
+
- [Bug Investigation](./examples/bug-investigation.md)
|
|
350
|
+
- [Scripting & Automation](./examples/scripting.md)
|
|
351
|
+
- **[Architecture](./docs/ARCHITECTURE.md)** — How ContextKit works
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
297
355
|
## Technical Details
|
|
298
356
|
|
|
299
357
|
### How Selection Works
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmSpC,eAAO,MAAM,aAAa,SAGN,CAAC"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { existsSync, statSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig, getDbPath } from '../config/index.js';
|
|
6
|
+
import { openDatabase } from '../db/index.js';
|
|
7
|
+
import { writeMessage } from '../utils/streams.js';
|
|
8
|
+
const CHECK_OK = chalk.green('✓');
|
|
9
|
+
const CHECK_WARN = chalk.yellow('⚠');
|
|
10
|
+
const CHECK_ERROR = chalk.red('✗');
|
|
11
|
+
function formatResult(result) {
|
|
12
|
+
const icon = result.status === 'ok' ? CHECK_OK : result.status === 'warn' ? CHECK_WARN : CHECK_ERROR;
|
|
13
|
+
let output = `${icon} ${result.name}: ${result.message}`;
|
|
14
|
+
if (result.detail) {
|
|
15
|
+
output += chalk.dim(`\n ${result.detail}`);
|
|
16
|
+
}
|
|
17
|
+
return output;
|
|
18
|
+
}
|
|
19
|
+
async function checkNodeVersion() {
|
|
20
|
+
const version = process.version;
|
|
21
|
+
const major = parseInt(version.slice(1).split('.')[0], 10);
|
|
22
|
+
if (major >= 18) {
|
|
23
|
+
return {
|
|
24
|
+
name: 'Node.js version',
|
|
25
|
+
status: 'ok',
|
|
26
|
+
message: version,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: 'Node.js version',
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: `${version} (requires >= 18)`,
|
|
33
|
+
detail: 'Upgrade Node.js: https://nodejs.org/',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function checkConfig() {
|
|
37
|
+
try {
|
|
38
|
+
const config = await loadConfig();
|
|
39
|
+
const sourceCount = config.sources?.length || 0;
|
|
40
|
+
if (sourceCount === 0) {
|
|
41
|
+
return {
|
|
42
|
+
name: 'Configuration',
|
|
43
|
+
status: 'warn',
|
|
44
|
+
message: 'No sources configured',
|
|
45
|
+
detail: 'Run: contextkit source add ./src',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: 'Configuration',
|
|
50
|
+
status: 'ok',
|
|
51
|
+
message: `${sourceCount} source(s) configured`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return {
|
|
56
|
+
name: 'Configuration',
|
|
57
|
+
status: 'error',
|
|
58
|
+
message: 'Not initialized',
|
|
59
|
+
detail: 'Run: contextkit init',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function checkDatabase() {
|
|
64
|
+
try {
|
|
65
|
+
const dbPath = getDbPath();
|
|
66
|
+
if (!existsSync(dbPath)) {
|
|
67
|
+
return {
|
|
68
|
+
name: 'Index database',
|
|
69
|
+
status: 'warn',
|
|
70
|
+
message: 'Not indexed yet',
|
|
71
|
+
detail: 'Run: contextkit index',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const stats = statSync(dbPath);
|
|
75
|
+
const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
|
|
76
|
+
const db = openDatabase();
|
|
77
|
+
const chunks = db.prepare('SELECT COUNT(*) as count FROM chunks').get();
|
|
78
|
+
const files = db.prepare('SELECT COUNT(*) as count FROM files').get();
|
|
79
|
+
db.close();
|
|
80
|
+
return {
|
|
81
|
+
name: 'Index database',
|
|
82
|
+
status: 'ok',
|
|
83
|
+
message: `${chunks.count} chunks, ${files.count} files (${sizeMB} MB)`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return {
|
|
88
|
+
name: 'Index database',
|
|
89
|
+
status: 'error',
|
|
90
|
+
message: 'Database error',
|
|
91
|
+
detail: error instanceof Error ? error.message : 'Unknown error',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function checkEmbeddings() {
|
|
96
|
+
try {
|
|
97
|
+
const dbPath = getDbPath();
|
|
98
|
+
if (!existsSync(dbPath)) {
|
|
99
|
+
return {
|
|
100
|
+
name: 'Embeddings',
|
|
101
|
+
status: 'warn',
|
|
102
|
+
message: 'No index yet',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const db = openDatabase();
|
|
106
|
+
const result = db.prepare('SELECT COUNT(*) as count FROM chunks WHERE embedding IS NOT NULL').get();
|
|
107
|
+
const total = db.prepare('SELECT COUNT(*) as count FROM chunks').get();
|
|
108
|
+
db.close();
|
|
109
|
+
if (result.count === 0) {
|
|
110
|
+
return {
|
|
111
|
+
name: 'Embeddings',
|
|
112
|
+
status: 'warn',
|
|
113
|
+
message: 'No embeddings generated',
|
|
114
|
+
detail: 'Run: contextkit index --full',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const coverage = ((result.count / total.count) * 100).toFixed(0);
|
|
118
|
+
return {
|
|
119
|
+
name: 'Embeddings',
|
|
120
|
+
status: 'ok',
|
|
121
|
+
message: `${result.count}/${total.count} chunks (${coverage}%)`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
name: 'Embeddings',
|
|
127
|
+
status: 'error',
|
|
128
|
+
message: 'Error checking embeddings',
|
|
129
|
+
detail: error instanceof Error ? error.message : 'Unknown error',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function checkDiskSpace() {
|
|
134
|
+
try {
|
|
135
|
+
const dbPath = getDbPath();
|
|
136
|
+
const dir = join(dbPath, '..');
|
|
137
|
+
if (!existsSync(dir)) {
|
|
138
|
+
return {
|
|
139
|
+
name: 'Disk space',
|
|
140
|
+
status: 'ok',
|
|
141
|
+
message: 'Not yet initialized',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
// Simple check - just report db size
|
|
145
|
+
if (existsSync(dbPath)) {
|
|
146
|
+
const stats = statSync(dbPath);
|
|
147
|
+
const sizeMB = stats.size / 1024 / 1024;
|
|
148
|
+
if (sizeMB > 500) {
|
|
149
|
+
return {
|
|
150
|
+
name: 'Disk space',
|
|
151
|
+
status: 'warn',
|
|
152
|
+
message: `Database is ${sizeMB.toFixed(0)} MB`,
|
|
153
|
+
detail: 'Consider: contextkit cache clear',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
name: 'Disk space',
|
|
159
|
+
status: 'ok',
|
|
160
|
+
message: 'OK',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return {
|
|
165
|
+
name: 'Disk space',
|
|
166
|
+
status: 'ok',
|
|
167
|
+
message: 'Could not check',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function checkQueryCache() {
|
|
172
|
+
try {
|
|
173
|
+
const dbPath = getDbPath();
|
|
174
|
+
if (!existsSync(dbPath)) {
|
|
175
|
+
return {
|
|
176
|
+
name: 'Query cache',
|
|
177
|
+
status: 'ok',
|
|
178
|
+
message: 'No cache yet',
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
const db = openDatabase();
|
|
182
|
+
// Check if query_cache table exists
|
|
183
|
+
const tableExists = db
|
|
184
|
+
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='query_cache'")
|
|
185
|
+
.get();
|
|
186
|
+
if (!tableExists) {
|
|
187
|
+
db.close();
|
|
188
|
+
return {
|
|
189
|
+
name: 'Query cache',
|
|
190
|
+
status: 'ok',
|
|
191
|
+
message: 'Not enabled',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
const result = db.prepare('SELECT COUNT(*) as count FROM query_cache').get();
|
|
195
|
+
db.close();
|
|
196
|
+
return {
|
|
197
|
+
name: 'Query cache',
|
|
198
|
+
status: 'ok',
|
|
199
|
+
message: `${result.count} cached queries`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return {
|
|
204
|
+
name: 'Query cache',
|
|
205
|
+
status: 'ok',
|
|
206
|
+
message: 'Could not check',
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function runDoctor(options) {
|
|
211
|
+
const checks = [];
|
|
212
|
+
writeMessage(chalk.bold('\n🩺 ContextKit Doctor\n'));
|
|
213
|
+
writeMessage('Running diagnostics...\n');
|
|
214
|
+
// Run all checks
|
|
215
|
+
checks.push(await checkNodeVersion());
|
|
216
|
+
checks.push(await checkConfig());
|
|
217
|
+
checks.push(await checkDatabase());
|
|
218
|
+
checks.push(await checkEmbeddings());
|
|
219
|
+
checks.push(await checkQueryCache());
|
|
220
|
+
checks.push(await checkDiskSpace());
|
|
221
|
+
if (options.json) {
|
|
222
|
+
console.log(JSON.stringify(checks, null, 2));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
// Output results
|
|
226
|
+
for (const result of checks) {
|
|
227
|
+
writeMessage(formatResult(result));
|
|
228
|
+
}
|
|
229
|
+
// Summary
|
|
230
|
+
const errors = checks.filter((c) => c.status === 'error').length;
|
|
231
|
+
const warnings = checks.filter((c) => c.status === 'warn').length;
|
|
232
|
+
writeMessage('');
|
|
233
|
+
if (errors > 0) {
|
|
234
|
+
writeMessage(chalk.red(`\n${errors} error(s) found. Fix these issues to use ContextKit.`));
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
else if (warnings > 0) {
|
|
238
|
+
writeMessage(chalk.yellow(`\n${warnings} warning(s). ContextKit will work but may be limited.`));
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
writeMessage(chalk.green('\n✓ All checks passed! ContextKit is ready to use.'));
|
|
242
|
+
}
|
|
243
|
+
// Quick tips
|
|
244
|
+
writeMessage(chalk.dim('\nQuick commands:'));
|
|
245
|
+
writeMessage(chalk.dim(' contextkit init # Initialize project'));
|
|
246
|
+
writeMessage(chalk.dim(' contextkit source add . # Add sources'));
|
|
247
|
+
writeMessage(chalk.dim(' contextkit index # Build index'));
|
|
248
|
+
writeMessage(chalk.dim(' contextkit select "query" # Find context\n'));
|
|
249
|
+
}
|
|
250
|
+
export const doctorCommand = new Command('doctor')
|
|
251
|
+
.description('Diagnose ContextKit setup and configuration')
|
|
252
|
+
.option('--json', 'Output as JSON')
|
|
253
|
+
.action(runDoctor);
|
|
254
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call Graph Command
|
|
3
|
+
*
|
|
4
|
+
* Analyze function call relationships:
|
|
5
|
+
* - What functions call a given function (callers)
|
|
6
|
+
* - What functions a given function calls (callees)
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
export declare const graphCommand: Command;
|
|
10
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/commands/graph.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsUpC,eAAO,MAAM,YAAY,SA4BrB,CAAC"}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Call Graph Command
|
|
3
|
+
*
|
|
4
|
+
* Analyze function call relationships:
|
|
5
|
+
* - What functions call a given function (callers)
|
|
6
|
+
* - What functions a given function calls (callees)
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { ensureInitialized } from '../config/index.js';
|
|
10
|
+
import { openDatabase } from '../db/index.js';
|
|
11
|
+
import { writeData, writeMessage, writeWarning } from '../utils/streams.js';
|
|
12
|
+
import { formatDim } from '../utils/format.js';
|
|
13
|
+
import { getGlobalOpts } from '../utils/cli.js';
|
|
14
|
+
/** Extract function definitions from content */
|
|
15
|
+
function extractFunctionDefs(content, filePath) {
|
|
16
|
+
const functions = new Map();
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const line = lines[i];
|
|
21
|
+
const trimmed = line.trim();
|
|
22
|
+
// TypeScript/JavaScript
|
|
23
|
+
if (ext === 'ts' || ext === 'tsx' || ext === 'js' || ext === 'jsx') {
|
|
24
|
+
// function name()
|
|
25
|
+
const funcMatch = trimmed.match(/^(export\s+)?(async\s+)?function\s+(\w+)/);
|
|
26
|
+
if (funcMatch) {
|
|
27
|
+
const name = funcMatch[3];
|
|
28
|
+
const endLine = findBlockEnd(lines, i);
|
|
29
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// const name = () =>
|
|
33
|
+
const arrowMatch = trimmed.match(/^(export\s+)?(const|let|var)\s+(\w+)\s*=\s*(async\s+)?\(/);
|
|
34
|
+
if (arrowMatch) {
|
|
35
|
+
const name = arrowMatch[3];
|
|
36
|
+
const endLine = findBlockEnd(lines, i);
|
|
37
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Method in class: name() or async name()
|
|
41
|
+
const methodMatch = trimmed.match(/^(public|private|protected|static|async)?\s*(async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/);
|
|
42
|
+
if (methodMatch && !['if', 'while', 'for', 'switch', 'catch'].includes(methodMatch[3])) {
|
|
43
|
+
const name = methodMatch[3];
|
|
44
|
+
const endLine = findBlockEnd(lines, i);
|
|
45
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Python
|
|
50
|
+
if (ext === 'py') {
|
|
51
|
+
const defMatch = trimmed.match(/^(async\s+)?def\s+(\w+)/);
|
|
52
|
+
if (defMatch) {
|
|
53
|
+
const name = defMatch[2];
|
|
54
|
+
const endLine = findPythonBlockEnd(lines, i);
|
|
55
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Go
|
|
60
|
+
if (ext === 'go') {
|
|
61
|
+
const funcMatch = trimmed.match(/^func\s+(?:\([^)]+\)\s+)?(\w+)/);
|
|
62
|
+
if (funcMatch) {
|
|
63
|
+
const name = funcMatch[1];
|
|
64
|
+
const endLine = findBlockEnd(lines, i);
|
|
65
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Rust
|
|
70
|
+
if (ext === 'rs') {
|
|
71
|
+
const fnMatch = trimmed.match(/^(pub\s+)?(async\s+)?fn\s+(\w+)/);
|
|
72
|
+
if (fnMatch) {
|
|
73
|
+
const name = fnMatch[3];
|
|
74
|
+
const endLine = findBlockEnd(lines, i);
|
|
75
|
+
functions.set(name, { startLine: i + 1, endLine });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return functions;
|
|
81
|
+
}
|
|
82
|
+
/** Find function calls in a range of lines */
|
|
83
|
+
function findFunctionCalls(content, startLine, endLine, knownFunctions) {
|
|
84
|
+
const calls = [];
|
|
85
|
+
const lines = content.split('\n');
|
|
86
|
+
// Simple regex to find function calls: name(
|
|
87
|
+
const callPattern = /\b(\w+)\s*\(/g;
|
|
88
|
+
for (let i = startLine - 1; i < Math.min(endLine, lines.length); i++) {
|
|
89
|
+
const line = lines[i];
|
|
90
|
+
const trimmed = line.trim();
|
|
91
|
+
// Skip comments
|
|
92
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('*')) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = callPattern.exec(line)) !== null) {
|
|
97
|
+
const name = match[1];
|
|
98
|
+
// Skip keywords and common non-function patterns
|
|
99
|
+
const keywords = ['if', 'else', 'while', 'for', 'switch', 'catch', 'return', 'throw', 'new', 'typeof', 'import', 'export', 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'async', 'await', 'try', 'finally'];
|
|
100
|
+
if (keywords.includes(name))
|
|
101
|
+
continue;
|
|
102
|
+
// Only include if it's a known function or looks like a function call
|
|
103
|
+
if (knownFunctions.has(name) || name[0] === name[0].toLowerCase()) {
|
|
104
|
+
calls.push({
|
|
105
|
+
name,
|
|
106
|
+
line: i + 1,
|
|
107
|
+
context: trimmed.substring(0, 80),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return calls;
|
|
113
|
+
}
|
|
114
|
+
/** Find block end (brace-delimited) */
|
|
115
|
+
function findBlockEnd(lines, startIdx) {
|
|
116
|
+
let braceCount = 0;
|
|
117
|
+
let started = false;
|
|
118
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
119
|
+
const line = lines[i];
|
|
120
|
+
for (const char of line) {
|
|
121
|
+
if (char === '{') {
|
|
122
|
+
braceCount++;
|
|
123
|
+
started = true;
|
|
124
|
+
}
|
|
125
|
+
else if (char === '}') {
|
|
126
|
+
braceCount--;
|
|
127
|
+
if (started && braceCount === 0) {
|
|
128
|
+
return i + 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return lines.length;
|
|
134
|
+
}
|
|
135
|
+
/** Find Python block end (indentation-based) */
|
|
136
|
+
function findPythonBlockEnd(lines, startIdx) {
|
|
137
|
+
const startLine = lines[startIdx];
|
|
138
|
+
const startIndent = startLine.length - startLine.trimStart().length;
|
|
139
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
140
|
+
const line = lines[i];
|
|
141
|
+
const trimmed = line.trim();
|
|
142
|
+
if (!trimmed)
|
|
143
|
+
continue;
|
|
144
|
+
const indent = line.length - line.trimStart().length;
|
|
145
|
+
if (indent <= startIndent) {
|
|
146
|
+
return i;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return lines.length;
|
|
150
|
+
}
|
|
151
|
+
/** Build call graph for a target function */
|
|
152
|
+
function buildCallGraph(db, targetName, options) {
|
|
153
|
+
const { sources } = options;
|
|
154
|
+
// Get all chunks
|
|
155
|
+
let sql = 'SELECT file_path, content FROM chunks';
|
|
156
|
+
const params = [];
|
|
157
|
+
if (sources && sources.length > 0) {
|
|
158
|
+
sql += ' WHERE source_id IN (' + sources.map(() => '?').join(',') + ')';
|
|
159
|
+
params.push(...sources);
|
|
160
|
+
}
|
|
161
|
+
const rows = db.prepare(sql).all(...params);
|
|
162
|
+
// Build function index (file -> functions)
|
|
163
|
+
const functionsByFile = new Map();
|
|
164
|
+
const allFunctions = new Set();
|
|
165
|
+
for (const row of rows) {
|
|
166
|
+
const funcs = extractFunctionDefs(row.content, row.file_path);
|
|
167
|
+
if (funcs.size > 0) {
|
|
168
|
+
functionsByFile.set(row.file_path, funcs);
|
|
169
|
+
for (const name of funcs.keys()) {
|
|
170
|
+
allFunctions.add(name);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Find callers (who calls targetName)
|
|
175
|
+
const callers = [];
|
|
176
|
+
for (const row of rows) {
|
|
177
|
+
const fileFuncs = functionsByFile.get(row.file_path);
|
|
178
|
+
if (!fileFuncs)
|
|
179
|
+
continue;
|
|
180
|
+
for (const [funcName, { startLine, endLine }] of fileFuncs) {
|
|
181
|
+
const calls = findFunctionCalls(row.content, startLine, endLine, allFunctions);
|
|
182
|
+
for (const call of calls) {
|
|
183
|
+
if (call.name === targetName && funcName !== targetName) {
|
|
184
|
+
callers.push({
|
|
185
|
+
file: row.file_path,
|
|
186
|
+
function: funcName,
|
|
187
|
+
line: call.line,
|
|
188
|
+
context: call.context,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Find callees (what targetName calls)
|
|
195
|
+
const callees = [];
|
|
196
|
+
for (const row of rows) {
|
|
197
|
+
const fileFuncs = functionsByFile.get(row.file_path);
|
|
198
|
+
if (!fileFuncs)
|
|
199
|
+
continue;
|
|
200
|
+
const targetFunc = fileFuncs.get(targetName);
|
|
201
|
+
if (!targetFunc)
|
|
202
|
+
continue;
|
|
203
|
+
const calls = findFunctionCalls(row.content, targetFunc.startLine, targetFunc.endLine, allFunctions);
|
|
204
|
+
for (const call of calls) {
|
|
205
|
+
if (call.name !== targetName) {
|
|
206
|
+
// Find where this function is defined
|
|
207
|
+
let definedIn = '';
|
|
208
|
+
for (const [file, funcs] of functionsByFile) {
|
|
209
|
+
if (funcs.has(call.name)) {
|
|
210
|
+
definedIn = file;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
callees.push({
|
|
215
|
+
file: definedIn || '(external)',
|
|
216
|
+
function: call.name,
|
|
217
|
+
line: call.line,
|
|
218
|
+
context: call.context,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Deduplicate
|
|
224
|
+
const uniqueCallers = Array.from(new Map(callers.map(c => [`${c.file}:${c.function}`, c])).values());
|
|
225
|
+
const uniqueCallees = Array.from(new Map(callees.map(c => [`${c.file}:${c.function}`, c])).values());
|
|
226
|
+
return {
|
|
227
|
+
target: targetName,
|
|
228
|
+
callers: uniqueCallers,
|
|
229
|
+
callees: uniqueCallees,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/** Format call graph output */
|
|
233
|
+
function formatCallGraph(result, json) {
|
|
234
|
+
if (json) {
|
|
235
|
+
return JSON.stringify(result, null, 2);
|
|
236
|
+
}
|
|
237
|
+
const lines = [];
|
|
238
|
+
lines.push(`🎯 Call graph for: ${result.target}`);
|
|
239
|
+
lines.push('');
|
|
240
|
+
// Callers
|
|
241
|
+
lines.push(`📥 Callers (${result.callers.length}):`);
|
|
242
|
+
if (result.callers.length === 0) {
|
|
243
|
+
lines.push(' (none found)');
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
for (const caller of result.callers) {
|
|
247
|
+
lines.push(` ← ${caller.function} (${caller.file}:${caller.line})`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
lines.push('');
|
|
251
|
+
// Callees
|
|
252
|
+
lines.push(`📤 Calls (${result.callees.length}):`);
|
|
253
|
+
if (result.callees.length === 0) {
|
|
254
|
+
lines.push(' (none found)');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
for (const callee of result.callees) {
|
|
258
|
+
const location = callee.file === '(external)' ? '(external)' : `${callee.file}`;
|
|
259
|
+
lines.push(` → ${callee.function} (${location})`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}
|
|
264
|
+
export const graphCommand = new Command('graph')
|
|
265
|
+
.description('Show call graph for a function')
|
|
266
|
+
.argument('<function>', 'Function name to analyze')
|
|
267
|
+
.option('-s, --sources <sources>', 'Filter sources (comma-separated)')
|
|
268
|
+
.action(async (functionName, options) => {
|
|
269
|
+
ensureInitialized();
|
|
270
|
+
const opts = getGlobalOpts(graphCommand);
|
|
271
|
+
const sources = options.sources
|
|
272
|
+
? options.sources.split(',').map((s) => s.trim())
|
|
273
|
+
: undefined;
|
|
274
|
+
const db = openDatabase();
|
|
275
|
+
try {
|
|
276
|
+
const result = buildCallGraph(db, functionName, { sources });
|
|
277
|
+
if (result.callers.length === 0 && result.callees.length === 0) {
|
|
278
|
+
writeWarning(`No call relationships found for "${functionName}"`);
|
|
279
|
+
writeMessage(formatDim('Make sure the function exists and the codebase is indexed.'));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const output = formatCallGraph(result, opts.json ?? false);
|
|
283
|
+
writeData(output);
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
db.close();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
//# sourceMappingURL=graph.js.map
|