token-pilot 0.8.3 → 0.10.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 +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +37 -0
- package/README.md +17 -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 +47 -4
- package/dist/core/validation.js +111 -8
- package/dist/handlers/explore-area.d.ts +9 -0
- package/dist/handlers/explore-area.js +280 -0
- 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 +7 -3
- package/dist/handlers/outline.js +52 -21
- package/dist/handlers/project-overview.d.ts +2 -1
- package/dist/handlers/project-overview.js +146 -107
- package/dist/handlers/smart-diff.d.ts +35 -0
- package/dist/handlers/smart-diff.js +257 -0
- package/dist/server.js +98 -7
- package/package.json +1 -1
package/dist/handlers/outline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, stat } from 'node:fs/promises';
|
|
2
2
|
import { resolve, basename, relative } from 'node:path';
|
|
3
3
|
import { resolveSafePath } from '../core/validation.js';
|
|
4
|
-
const CODE_EXTENSIONS = new Set([
|
|
4
|
+
export const CODE_EXTENSIONS = new Set([
|
|
5
5
|
'ts', 'tsx', 'js', 'jsx', 'mjs', 'py', 'go', 'rs', 'java', 'kt', 'kts',
|
|
6
6
|
'swift', 'cs', 'cpp', 'cc', 'cxx', 'hpp', 'c', 'h', 'php', 'rb', 'scala',
|
|
7
7
|
'dart', 'lua', 'sh', 'bash', 'sql', 'r', 'vue', 'svelte', 'pl', 'pm',
|
|
@@ -22,7 +22,28 @@ export async function handleOutline(args, projectRoot, astIndex) {
|
|
|
22
22
|
}],
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
-
|
|
25
|
+
const recursive = args.recursive ?? false;
|
|
26
|
+
const maxDepth = args.max_depth ?? 2;
|
|
27
|
+
const sections = [];
|
|
28
|
+
await outlineDir(absPath, sections, 0, recursive ? maxDepth : 0, projectRoot, astIndex);
|
|
29
|
+
sections.push('HINT: Use outline(path) on subdirs, smart_read(path) for file structure, read_symbol(path, symbol) for source code.');
|
|
30
|
+
return { content: [{ type: 'text', text: sections.join('\n') }] };
|
|
31
|
+
}
|
|
32
|
+
/** Max output lines to prevent runaway recursive outlines */
|
|
33
|
+
const MAX_OUTLINE_LINES = 500;
|
|
34
|
+
/**
|
|
35
|
+
* Outline a single directory. When depth < maxDepth and recursive,
|
|
36
|
+
* recurse into subdirectories. Otherwise show file counts only.
|
|
37
|
+
*/
|
|
38
|
+
export async function outlineDir(absPath, sections, depth, maxDepth, projectRoot, astIndex) {
|
|
39
|
+
// Guard: stop if output is already too large
|
|
40
|
+
if (sections.length >= MAX_OUTLINE_LINES) {
|
|
41
|
+
if (!sections[sections.length - 1]?.startsWith('⚠')) {
|
|
42
|
+
sections.push(`⚠ Output truncated at ${MAX_OUTLINE_LINES} lines. Use outline() on specific subdirectories for details.`);
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// List code files and subdirectories
|
|
26
47
|
const entries = await readdir(absPath, { withFileTypes: true });
|
|
27
48
|
const codeFiles = [];
|
|
28
49
|
const subdirs = [];
|
|
@@ -38,53 +59,63 @@ export async function handleOutline(args, projectRoot, astIndex) {
|
|
|
38
59
|
}
|
|
39
60
|
}
|
|
40
61
|
if (codeFiles.length === 0 && subdirs.length === 0) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}],
|
|
46
|
-
};
|
|
62
|
+
if (depth === 0) {
|
|
63
|
+
sections.push(`No code files or subdirectories found in "${relative(projectRoot, absPath) || '.'}".`);
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
47
66
|
}
|
|
48
67
|
// Sort
|
|
49
68
|
codeFiles.sort();
|
|
50
69
|
subdirs.sort();
|
|
51
70
|
const relDir = relative(projectRoot, absPath) || '.';
|
|
71
|
+
const indent = ' '.repeat(depth);
|
|
52
72
|
const totalLabel = codeFiles.length > 0 ? `${codeFiles.length} files` : '';
|
|
53
73
|
const subLabel = subdirs.length > 0 ? `${subdirs.length} subdirs` : '';
|
|
54
74
|
const countLabel = [totalLabel, subLabel].filter(Boolean).join(', ');
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
sections.push(`${indent}OUTLINE: ${relDir}/ (${countLabel})`);
|
|
76
|
+
sections.push('');
|
|
77
|
+
// Show subdirectories
|
|
57
78
|
if (subdirs.length > 0) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
79
|
+
if (depth < maxDepth) {
|
|
80
|
+
// Recursive: outline each subdir
|
|
81
|
+
for (const sub of subdirs) {
|
|
82
|
+
const subPath = resolve(absPath, sub);
|
|
83
|
+
await outlineDir(subPath, sections, depth + 1, maxDepth, projectRoot, astIndex);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Non-recursive: show file counts only
|
|
88
|
+
for (const sub of subdirs) {
|
|
89
|
+
const subPath = resolve(absPath, sub);
|
|
90
|
+
const fileCount = await countCodeFiles(subPath);
|
|
91
|
+
sections.push(`${indent} ${sub}/ (${fileCount} code files)`);
|
|
92
|
+
}
|
|
93
|
+
sections.push('');
|
|
62
94
|
}
|
|
63
|
-
sections.push('');
|
|
64
95
|
}
|
|
65
96
|
// Show code files at this level with AST outline
|
|
97
|
+
const fileIndent = ' '.repeat(depth + 1);
|
|
98
|
+
const symbolIndent = depth + 2;
|
|
66
99
|
for (const filePath of codeFiles) {
|
|
67
100
|
const name = basename(filePath);
|
|
68
101
|
try {
|
|
69
102
|
const structure = await astIndex.outline(filePath);
|
|
70
103
|
if (!structure) {
|
|
71
|
-
sections.push(`${name} (no AST)`);
|
|
104
|
+
sections.push(`${fileIndent}${name} (no AST)`);
|
|
72
105
|
sections.push('');
|
|
73
106
|
continue;
|
|
74
107
|
}
|
|
75
|
-
sections.push(`${name} (${structure.meta.lines} lines)`);
|
|
108
|
+
sections.push(`${fileIndent}${name} (${structure.meta.lines} lines)`);
|
|
76
109
|
for (const sym of structure.symbols) {
|
|
77
|
-
formatCompactSymbol(sym, sections,
|
|
110
|
+
formatCompactSymbol(sym, sections, symbolIndent);
|
|
78
111
|
}
|
|
79
112
|
sections.push('');
|
|
80
113
|
}
|
|
81
114
|
catch {
|
|
82
|
-
sections.push(`${name} (outline failed)`);
|
|
115
|
+
sections.push(`${fileIndent}${name} (outline failed)`);
|
|
83
116
|
sections.push('');
|
|
84
117
|
}
|
|
85
118
|
}
|
|
86
|
-
sections.push('HINT: Use outline(path) on subdirs, smart_read(path) for file structure, read_symbol(path, symbol) for source code.');
|
|
87
|
-
return { content: [{ type: 'text', text: sections.join('\n') }] };
|
|
88
119
|
}
|
|
89
120
|
/**
|
|
90
121
|
* Format a symbol in compact outline form.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
|
-
|
|
2
|
+
import type { ProjectOverviewArgs } from '../core/validation.js';
|
|
3
|
+
export declare function handleProjectOverview(args: ProjectOverviewArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
3
4
|
content: Array<{
|
|
4
5
|
type: 'text';
|
|
5
6
|
text: string;
|
|
@@ -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
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
|
+
import type { SmartDiffArgs } from '../core/validation.js';
|
|
3
|
+
import type { FileStructure } from '../types.js';
|
|
4
|
+
interface FileDiff {
|
|
5
|
+
path: string;
|
|
6
|
+
oldPath?: string;
|
|
7
|
+
addedLines: number;
|
|
8
|
+
removedLines: number;
|
|
9
|
+
hunks: DiffHunk[];
|
|
10
|
+
isBinary: boolean;
|
|
11
|
+
isNew: boolean;
|
|
12
|
+
isDeleted: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface DiffHunk {
|
|
15
|
+
newStart: number;
|
|
16
|
+
newCount: number;
|
|
17
|
+
lines: string[];
|
|
18
|
+
}
|
|
19
|
+
interface SymbolChange {
|
|
20
|
+
name: string;
|
|
21
|
+
kind: string;
|
|
22
|
+
changeType: 'MODIFIED' | 'ADDED' | 'REMOVED';
|
|
23
|
+
lineRange: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function handleSmartDiff(args: SmartDiffArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
26
|
+
content: Array<{
|
|
27
|
+
type: 'text';
|
|
28
|
+
text: string;
|
|
29
|
+
}>;
|
|
30
|
+
rawTokens: number;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function parseUnifiedDiff(raw: string): FileDiff[];
|
|
33
|
+
export declare function mapHunksToSymbols(hunks: DiffHunk[], structure: FileStructure): SymbolChange[];
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=smart-diff.d.ts.map
|