token-pilot 0.8.3 → 0.9.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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +13 -10
- package/dist/ast-index/client.d.ts +15 -1
- package/dist/ast-index/client.js +179 -0
- package/dist/ast-index/types.d.ts +27 -1
- package/dist/ast-index/types.js +1 -1
- package/dist/core/project-detector.d.ts +42 -0
- package/dist/core/project-detector.js +362 -0
- package/dist/core/validation.d.ts +30 -4
- package/dist/core/validation.js +67 -8
- package/dist/handlers/find-usages.d.ts +3 -3
- package/dist/handlers/find-usages.js +88 -13
- package/dist/handlers/module-info.d.ts +9 -0
- package/dist/handlers/module-info.js +123 -0
- package/dist/handlers/outline.d.ts +1 -3
- package/dist/handlers/outline.js +51 -20
- package/dist/handlers/project-overview.d.ts +2 -1
- package/dist/handlers/project-overview.js +146 -107
- package/dist/server.js +46 -6
- package/package.json +1 -1
|
@@ -1,78 +1,109 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export async function handleProjectOverview(projectRoot, astIndex) {
|
|
1
|
+
import { detectProject } from '../core/project-detector.js';
|
|
2
|
+
export async function handleProjectOverview(args, projectRoot, astIndex) {
|
|
4
3
|
const lines = [];
|
|
5
|
-
// 1.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (projectInfo.description)
|
|
10
|
-
lines.push(` ${projectInfo.description}`);
|
|
11
|
-
lines.push('');
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
lines.push(`PROJECT: ${basename(projectRoot)}`);
|
|
15
|
-
lines.push('');
|
|
16
|
-
}
|
|
17
|
-
// 2. ast-index map — directory structure with file counts and symbol kinds
|
|
4
|
+
// 1. Dual detection: ast-index + config scanner
|
|
5
|
+
let astIndexType;
|
|
6
|
+
let mapData = null;
|
|
7
|
+
let convData = null;
|
|
18
8
|
if (astIndex.isAvailable() && !astIndex.isOversized() && !astIndex.isDisabled()) {
|
|
19
|
-
|
|
9
|
+
[mapData, convData] = await Promise.all([
|
|
20
10
|
astIndex.map(),
|
|
21
11
|
astIndex.conventions(),
|
|
22
12
|
]);
|
|
23
13
|
if (mapData) {
|
|
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
|
-
|
|
14
|
+
astIndexType = mapData.project_type;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const detection = await detectProject(projectRoot, astIndexType);
|
|
18
|
+
// Determine which sections to include
|
|
19
|
+
const include = args.include ?? ['stack', 'ci', 'quality', 'architecture'];
|
|
20
|
+
const showStack = include.includes('stack');
|
|
21
|
+
const showCI = include.includes('ci');
|
|
22
|
+
const showQuality = include.includes('quality');
|
|
23
|
+
const showArch = include.includes('architecture');
|
|
24
|
+
// 2. Project identity
|
|
25
|
+
lines.push(`PROJECT: ${detection.projectName} v${detection.projectVersion}`);
|
|
26
|
+
if (detection.projectDescription)
|
|
27
|
+
lines.push(` ${detection.projectDescription}`);
|
|
28
|
+
lines.push('');
|
|
29
|
+
// 3. TYPE — dual detection
|
|
30
|
+
if (showStack) {
|
|
31
|
+
if (astIndexType) {
|
|
32
|
+
lines.push(`TYPE (ast-index): ${astIndexType}${mapData ? ` (${mapData.file_count} files)` : ''}`);
|
|
33
|
+
}
|
|
34
|
+
if (detection.configStacks.length > 0) {
|
|
35
|
+
const configLine = formatConfigStacks(detection);
|
|
36
|
+
lines.push(`TYPE (config): ${configLine}`);
|
|
37
|
+
}
|
|
38
|
+
if (detection.configStacks.length === 0 && !astIndexType) {
|
|
39
|
+
lines.push('TYPE: unknown (no config files found)');
|
|
40
|
+
}
|
|
41
|
+
// Confidence
|
|
42
|
+
lines.push(`CONFIDENCE: ${detection.confidence}${getConfidenceHint(detection)}`);
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
45
|
+
// 4. Architecture & frameworks (from ast-index conventions)
|
|
46
|
+
if (showArch && convData) {
|
|
47
|
+
if (convData.architecture.length > 0) {
|
|
48
|
+
lines.push(`ARCHITECTURE: ${convData.architecture.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
// Merge framework info: ast-index conventions + config detection
|
|
51
|
+
const fwList = buildFrameworkList(convData, detection);
|
|
52
|
+
if (fwList.length > 0) {
|
|
53
|
+
lines.push(`FRAMEWORKS: ${fwList.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
if (convData.naming_patterns.length > 0) {
|
|
56
|
+
const patterns = convData.naming_patterns
|
|
57
|
+
.slice(0, 8)
|
|
58
|
+
.map(p => `${p.suffix}(${p.count})`)
|
|
59
|
+
.join(', ');
|
|
60
|
+
lines.push(`PATTERNS: ${patterns}`);
|
|
61
|
+
}
|
|
62
|
+
lines.push('');
|
|
63
|
+
}
|
|
64
|
+
// 5. Quality tools
|
|
65
|
+
if (showQuality && detection.qualityTools.length > 0) {
|
|
66
|
+
lines.push(`QUALITY: ${detection.qualityTools.join(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
// 6. CI pipelines
|
|
69
|
+
if (showCI && detection.ciPipelines.length > 0) {
|
|
70
|
+
lines.push(`CI: ${detection.ciPipelines.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
// Docker
|
|
73
|
+
if (showCI && detection.hasDocker) {
|
|
74
|
+
lines.push('DOCKER: yes');
|
|
75
|
+
}
|
|
76
|
+
if ((showQuality && detection.qualityTools.length > 0) || (showCI && detection.ciPipelines.length > 0)) {
|
|
77
|
+
lines.push('');
|
|
78
|
+
}
|
|
79
|
+
// 7. Directory map (from ast-index)
|
|
80
|
+
if (showArch && mapData) {
|
|
81
|
+
lines.push('MAP:');
|
|
82
|
+
for (const group of mapData.groups) {
|
|
83
|
+
const kinds = group.kinds
|
|
84
|
+
? ' — ' + Object.entries(group.kinds).map(([k, v]) => `${v} ${k}`).join(', ')
|
|
85
|
+
: '';
|
|
86
|
+
lines.push(` ${group.path} (${group.file_count} files${kinds})`);
|
|
58
87
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
else if (showArch && !mapData && astIndex.isAvailable() && !astIndex.isDisabled() && !astIndex.isOversized()) {
|
|
91
|
+
// Fallback to stats
|
|
92
|
+
try {
|
|
93
|
+
const statsText = await astIndex.stats();
|
|
94
|
+
if (statsText) {
|
|
95
|
+
const filesMatch = statsText.match(/Files:\s*(\d+)/);
|
|
96
|
+
const symbolsMatch = statsText.match(/Symbols:\s*(\d+)/);
|
|
97
|
+
if (filesMatch)
|
|
98
|
+
lines.push(`Files indexed: ${filesMatch[1]}`);
|
|
99
|
+
if (symbolsMatch)
|
|
100
|
+
lines.push(`Symbols: ${symbolsMatch[1]}`);
|
|
101
|
+
lines.push('');
|
|
72
102
|
}
|
|
73
|
-
catch { /* ignore */ }
|
|
74
103
|
}
|
|
104
|
+
catch { /* ignore */ }
|
|
75
105
|
}
|
|
106
|
+
// 8. Degradation warnings
|
|
76
107
|
if (astIndex.isDisabled()) {
|
|
77
108
|
lines.push('⚠ ast-index: project root not detected. Call smart_read() on any project file first.');
|
|
78
109
|
lines.push(' Working tools: smart_read, smart_read_many, outline, read_symbol, read_range');
|
|
@@ -88,47 +119,55 @@ export async function handleProjectOverview(projectRoot, astIndex) {
|
|
|
88
119
|
lines.push('HINT: Use smart_read() on files, find_usages() for symbol references, outline() for directory overview.');
|
|
89
120
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
90
121
|
}
|
|
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
|
-
|
|
122
|
+
// ──────────────────────────────────────────────
|
|
123
|
+
// Formatters
|
|
124
|
+
// ──────────────────────────────────────────────
|
|
125
|
+
function formatConfigStacks(detection) {
|
|
126
|
+
if (detection.configStacks.length === 0)
|
|
127
|
+
return 'unknown';
|
|
128
|
+
const parts = [];
|
|
129
|
+
for (const stack of detection.configStacks) {
|
|
130
|
+
let part = stack.type;
|
|
131
|
+
if (stack.langVersion)
|
|
132
|
+
part = stack.langVersion;
|
|
133
|
+
if (stack.framework)
|
|
134
|
+
part += ` (${stack.framework})`;
|
|
135
|
+
parts.push(part);
|
|
136
|
+
}
|
|
137
|
+
if (detection.primaryStack && detection.configStacks.length > 1) {
|
|
138
|
+
// Put primary first, mark others
|
|
139
|
+
const primaryIdx = detection.configStacks.indexOf(detection.primaryStack);
|
|
140
|
+
if (primaryIdx > 0) {
|
|
141
|
+
const [primary] = parts.splice(primaryIdx, 1);
|
|
142
|
+
parts.unshift(primary);
|
|
143
|
+
}
|
|
144
|
+
return parts[0] + (parts.length > 1 ? ` + ${parts.slice(1).join(', ')}` : '');
|
|
145
|
+
}
|
|
146
|
+
return parts.join(', ');
|
|
147
|
+
}
|
|
148
|
+
function getConfidenceHint(detection) {
|
|
149
|
+
if (detection.confidence === 'low') {
|
|
150
|
+
return ` — ast-index and config files disagree on project type`;
|
|
151
|
+
}
|
|
152
|
+
if (detection.confidence === 'medium' && detection.configStacks.length > 1) {
|
|
153
|
+
return ` — multi-stack project detected`;
|
|
154
|
+
}
|
|
155
|
+
return '';
|
|
156
|
+
}
|
|
157
|
+
function buildFrameworkList(convData, detection) {
|
|
158
|
+
const fwSet = new Set();
|
|
159
|
+
// From ast-index conventions
|
|
160
|
+
for (const [category, frameworks] of Object.entries(convData.frameworks)) {
|
|
161
|
+
for (const fw of frameworks) {
|
|
162
|
+
fwSet.add(`${fw.name} (${category})`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// From config detection (may have version info that conventions don't)
|
|
166
|
+
for (const stack of detection.configStacks) {
|
|
167
|
+
if (stack.framework && !Array.from(fwSet).some(f => f.includes(stack.framework.split(' ')[0]))) {
|
|
168
|
+
fwSet.add(stack.framework);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return Array.from(fwSet);
|
|
133
172
|
}
|
|
134
173
|
//# sourceMappingURL=project-overview.js.map
|
package/dist/server.js
CHANGED
|
@@ -27,9 +27,10 @@ import { handleReadForEdit } from './handlers/read-for-edit.js';
|
|
|
27
27
|
import { handleRelatedFiles } from './handlers/related-files.js';
|
|
28
28
|
import { handleOutline } from './handlers/outline.js';
|
|
29
29
|
import { handleCodeAudit } from './handlers/code-audit.js';
|
|
30
|
+
import { handleModuleInfo } from './handlers/module-info.js';
|
|
30
31
|
import { detectContextMode } from './integration/context-mode-detector.js';
|
|
31
32
|
import { estimateTokens } from './core/token-estimator.js';
|
|
32
|
-
import { resolveSafePath, validateSmartReadArgs, validateReadSymbolArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, } from './core/validation.js';
|
|
33
|
+
import { resolveSafePath, validateSmartReadArgs, validateReadSymbolArgs, validateReadRangeArgs, validateReadDiffArgs, validateFindUsagesArgs, validateSmartReadManyArgs, validateReadForEditArgs, validateRelatedFilesArgs, validateOutlineArgs, validateFindUnusedArgs, validateCodeAuditArgs, validateProjectOverviewArgs, validateModuleInfoArgs, } from './core/validation.js';
|
|
33
34
|
export async function createServer(projectRoot, options) {
|
|
34
35
|
const config = await loadConfig(projectRoot);
|
|
35
36
|
const astIndex = new AstIndexClient(projectRoot, config.astIndex.timeout, {
|
|
@@ -198,6 +199,7 @@ export async function createServer(projectRoot, options) {
|
|
|
198
199
|
'• Text pattern search/counting → Grep (regex, count mode)',
|
|
199
200
|
'• Security audit → Grep for: password, token, secret, credential, hardcoded, api_key, TODO.*security',
|
|
200
201
|
'• Deep dive into specific code → read_symbol (after finding issues)',
|
|
202
|
+
'• Module architecture → module_info (deps, dependents, public API, unused deps)',
|
|
201
203
|
'',
|
|
202
204
|
'WORKFLOW: project_overview → smart_read → read_symbol → read_for_edit → edit → read_diff',
|
|
203
205
|
].join('\n'),
|
|
@@ -291,21 +293,31 @@ export async function createServer(projectRoot, options) {
|
|
|
291
293
|
// --- Search & navigation ---
|
|
292
294
|
{
|
|
293
295
|
name: 'find_usages',
|
|
294
|
-
description: 'Use INSTEAD OF Grep/ripgrep for finding symbol references. Semantic search across the project — groups results by: definitions, imports, usages.',
|
|
296
|
+
description: 'Use INSTEAD OF Grep/ripgrep for finding symbol references. Semantic search across the project — groups results by: definitions, imports, usages. (v1.1: added scope, kind, limit, lang filters)',
|
|
295
297
|
inputSchema: {
|
|
296
298
|
type: 'object',
|
|
297
299
|
properties: {
|
|
298
300
|
symbol: { type: 'string', description: 'Symbol name to find usages of' },
|
|
301
|
+
scope: { type: 'string', description: 'Filter results by path prefix (e.g., "src/Domain/")' },
|
|
302
|
+
kind: { type: 'string', enum: ['definitions', 'imports', 'usages', 'all'], description: 'Show only specific section (default: "all")' },
|
|
303
|
+
limit: { type: 'number', description: 'Max results per category (default: 50, max: 500)' },
|
|
304
|
+
lang: { type: 'string', description: 'Filter by language/extension (e.g., "php", "typescript")' },
|
|
299
305
|
},
|
|
300
306
|
required: ['symbol'],
|
|
301
307
|
},
|
|
302
308
|
},
|
|
303
309
|
{
|
|
304
310
|
name: 'project_overview',
|
|
305
|
-
description: 'START HERE for unfamiliar codebases. Shows project type, architecture, framework detection,
|
|
311
|
+
description: 'START HERE for unfamiliar codebases. Shows project type (dual-detection: ast-index + config files), architecture, framework detection, quality tools, CI, directory map. (v1.1: added include filter)',
|
|
306
312
|
inputSchema: {
|
|
307
313
|
type: 'object',
|
|
308
|
-
properties: {
|
|
314
|
+
properties: {
|
|
315
|
+
include: {
|
|
316
|
+
type: 'array',
|
|
317
|
+
items: { type: 'string', enum: ['stack', 'ci', 'quality', 'architecture'] },
|
|
318
|
+
description: 'Sections to include (default: all). Use ["stack"] for quick type check, ["quality","ci"] for tooling overview.',
|
|
319
|
+
},
|
|
320
|
+
},
|
|
309
321
|
},
|
|
310
322
|
},
|
|
311
323
|
{
|
|
@@ -321,11 +333,13 @@ export async function createServer(projectRoot, options) {
|
|
|
321
333
|
},
|
|
322
334
|
{
|
|
323
335
|
name: 'outline',
|
|
324
|
-
description: 'Use INSTEAD OF listing dir + reading each file. One call returns all symbols (classes, functions, methods, routes) for every code file in a directory.',
|
|
336
|
+
description: 'Use INSTEAD OF listing dir + reading each file. One call returns all symbols (classes, functions, methods, routes) for every code file in a directory. (v1.1: added recursive, max_depth)',
|
|
325
337
|
inputSchema: {
|
|
326
338
|
type: 'object',
|
|
327
339
|
properties: {
|
|
328
340
|
path: { type: 'string', description: 'Directory path' },
|
|
341
|
+
recursive: { type: 'boolean', description: 'Recursively outline subdirectories (default: false)' },
|
|
342
|
+
max_depth: { type: 'number', description: 'Max recursion depth when recursive=true (default: 2, max: 5)' },
|
|
329
343
|
},
|
|
330
344
|
required: ['path'],
|
|
331
345
|
},
|
|
@@ -371,6 +385,22 @@ export async function createServer(projectRoot, options) {
|
|
|
371
385
|
required: ['check'],
|
|
372
386
|
},
|
|
373
387
|
},
|
|
388
|
+
{
|
|
389
|
+
name: 'module_info',
|
|
390
|
+
description: 'Analyze module dependencies, dependents, public API, and unused deps. Use for architecture understanding and dependency cleanup.',
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: 'object',
|
|
393
|
+
properties: {
|
|
394
|
+
module: { type: 'string', description: 'Module name or path pattern (e.g., "auth", "src/Domain/")' },
|
|
395
|
+
check: {
|
|
396
|
+
type: 'string',
|
|
397
|
+
enum: ['deps', 'dependents', 'api', 'unused-deps', 'all'],
|
|
398
|
+
description: 'What to check: "deps" (dependencies), "dependents" (who depends on this), "api" (public symbols), "unused-deps" (dead dependencies), "all" (everything). Default: "all"',
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
required: ['module'],
|
|
402
|
+
},
|
|
403
|
+
},
|
|
374
404
|
],
|
|
375
405
|
}));
|
|
376
406
|
// Helper: get real full-file token count for honest analytics
|
|
@@ -476,7 +506,8 @@ export async function createServer(projectRoot, options) {
|
|
|
476
506
|
return usagesResult;
|
|
477
507
|
}
|
|
478
508
|
case 'project_overview': {
|
|
479
|
-
const
|
|
509
|
+
const overviewArgs = validateProjectOverviewArgs(args);
|
|
510
|
+
const overviewResult = await handleProjectOverview(overviewArgs, projectRoot, astIndex);
|
|
480
511
|
const overviewText = overviewResult.content[0]?.text ?? '';
|
|
481
512
|
overviewResult.content[0] = { type: 'text', text: `TOKEN PILOT v${pkgVersion}\n\n${overviewText}` };
|
|
482
513
|
const ovTokens = estimateTokens(overviewResult.content[0].text);
|
|
@@ -513,6 +544,15 @@ export async function createServer(projectRoot, options) {
|
|
|
513
544
|
analytics.record({ tool: 'code_audit', path: auditArgs.check, tokensReturned: estimateTokens(auditText), tokensWouldBe: estimateTokens(auditText), timestamp: Date.now() });
|
|
514
545
|
return auditResult;
|
|
515
546
|
}
|
|
547
|
+
case 'module_info': {
|
|
548
|
+
const moduleArgs = validateModuleInfoArgs(args);
|
|
549
|
+
const moduleResult = await handleModuleInfo(moduleArgs, projectRoot, astIndex);
|
|
550
|
+
const moduleText = moduleResult.content[0]?.text ?? '';
|
|
551
|
+
// Estimate: manual analysis would require reading all module files + grepping deps
|
|
552
|
+
const moduleWouldBe = estimateTokens(moduleText) * 5;
|
|
553
|
+
analytics.record({ tool: 'module_info', path: moduleArgs.module, tokensReturned: estimateTokens(moduleText), tokensWouldBe: moduleWouldBe, timestamp: Date.now() });
|
|
554
|
+
return moduleResult;
|
|
555
|
+
}
|
|
516
556
|
default:
|
|
517
557
|
return {
|
|
518
558
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "token-pilot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Save 60-80% tokens when AI reads code — MCP server for token-efficient code navigation, AST-aware structural reading instead of dumping full files into context window",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|