token-pilot 0.14.2 → 0.16.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/CHANGELOG.md +34 -0
- package/README.md +25 -8
- package/dist/ast-index/client.d.ts +2 -89
- package/dist/ast-index/client.js +49 -742
- package/dist/ast-index/enricher.d.ts +10 -0
- package/dist/ast-index/enricher.js +202 -0
- package/dist/ast-index/parser.d.ts +31 -0
- package/dist/ast-index/parser.js +340 -0
- package/dist/ast-index/regex-parser-python.d.ts +8 -0
- package/dist/ast-index/regex-parser-python.js +132 -0
- package/dist/ast-index/regex-parser.d.ts +8 -0
- package/dist/ast-index/regex-parser.js +118 -0
- package/dist/config/defaults.js +1 -0
- package/dist/core/session-analytics.d.ts +2 -2
- package/dist/core/session-analytics.js +78 -61
- package/dist/core/symbol-resolver.d.ts +0 -1
- package/dist/core/symbol-resolver.js +3 -12
- package/dist/core/validation.d.ts +12 -0
- package/dist/core/validation.js +62 -2
- package/dist/handlers/code-audit.js +2 -2
- package/dist/handlers/find-unused.js +1 -1
- package/dist/handlers/find-usages.d.ts +1 -1
- package/dist/handlers/find-usages.js +93 -25
- package/dist/handlers/read-for-edit.d.ts +1 -0
- package/dist/handlers/read-for-edit.js +65 -0
- package/dist/handlers/read-symbols.d.ts +18 -0
- package/dist/handlers/read-symbols.js +142 -0
- package/dist/handlers/smart-diff.js +23 -0
- package/dist/handlers/smart-read.js +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -5
- package/dist/server/token-estimates.d.ts +31 -0
- package/dist/server/token-estimates.js +204 -0
- package/dist/server/tool-definitions.d.ts +1070 -0
- package/dist/server/tool-definitions.js +316 -0
- package/dist/server.js +23 -480
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/skills/guide/SKILL.md +64 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File structure enrichment functions.
|
|
3
|
+
* Reads file content and adds Python/PHP method extraction, signatures, etc.
|
|
4
|
+
*/
|
|
5
|
+
import type { FileStructure } from '../types.js';
|
|
6
|
+
import type { AstIndexOutlineEntry } from './types.js';
|
|
7
|
+
export declare function buildFileStructure(filePath: string, entries: AstIndexOutlineEntry[]): Promise<FileStructure>;
|
|
8
|
+
/** Fix the last entry's end_line to use actual file line count */
|
|
9
|
+
export declare function fixLastEndLine(entries: AstIndexOutlineEntry[], totalLines: number): void;
|
|
10
|
+
//# sourceMappingURL=enricher.d.ts.map
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File structure enrichment functions.
|
|
3
|
+
* Reads file content and adds Python/PHP method extraction, signatures, etc.
|
|
4
|
+
*/
|
|
5
|
+
import { stat, readFile } from 'node:fs/promises';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { detectLanguage, mapOutlineEntry } from './parser.js';
|
|
8
|
+
export async function buildFileStructure(filePath, entries) {
|
|
9
|
+
const content = await readFile(filePath, 'utf-8');
|
|
10
|
+
const lines = content.split('\n');
|
|
11
|
+
const fileStat = await stat(filePath);
|
|
12
|
+
fixLastEndLine(entries, lines.length);
|
|
13
|
+
const lang = detectLanguage(filePath);
|
|
14
|
+
if (lang === 'Python') {
|
|
15
|
+
enrichPythonClassMethods(entries, lines);
|
|
16
|
+
}
|
|
17
|
+
else if (lang === 'PHP') {
|
|
18
|
+
enrichPHPClassMethods(entries, lines);
|
|
19
|
+
}
|
|
20
|
+
enrichSignatures(entries, lines);
|
|
21
|
+
return {
|
|
22
|
+
path: filePath,
|
|
23
|
+
language: lang,
|
|
24
|
+
meta: {
|
|
25
|
+
lines: lines.length,
|
|
26
|
+
bytes: fileStat.size,
|
|
27
|
+
lastModified: fileStat.mtimeMs,
|
|
28
|
+
contentHash: createHash('sha256').update(content).digest('hex'),
|
|
29
|
+
},
|
|
30
|
+
imports: [],
|
|
31
|
+
exports: [],
|
|
32
|
+
symbols: entries.map(e => mapOutlineEntry(e)),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Python: ast-index doesn't return methods inside classes.
|
|
37
|
+
* Parse file content to extract `def` methods for classes without children.
|
|
38
|
+
*/
|
|
39
|
+
function enrichPythonClassMethods(entries, lines) {
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
if (entry.kind.toLowerCase() !== 'class')
|
|
42
|
+
continue;
|
|
43
|
+
if (entry.children && entry.children.length > 0)
|
|
44
|
+
continue;
|
|
45
|
+
const classStartIdx = entry.start_line - 1; // 0-based
|
|
46
|
+
const classEndIdx = entry.end_line - 1;
|
|
47
|
+
// Detect class body indent: look for first `def ` inside class range
|
|
48
|
+
let bodyIndent = -1;
|
|
49
|
+
for (let i = classStartIdx + 1; i <= classEndIdx && i < lines.length; i++) {
|
|
50
|
+
const defMatch = lines[i].match(/^(\s+)def\s/);
|
|
51
|
+
if (defMatch) {
|
|
52
|
+
bodyIndent = defMatch[1].length;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (bodyIndent < 0)
|
|
57
|
+
continue; // no methods found
|
|
58
|
+
const methods = [];
|
|
59
|
+
for (let i = classStartIdx + 1; i <= classEndIdx && i < lines.length; i++) {
|
|
60
|
+
const line = lines[i];
|
|
61
|
+
// Match `def method_name(` at the detected indent level
|
|
62
|
+
const match = line.match(new RegExp(`^\\s{${bodyIndent}}def\\s+(\\w+)\\s*\\(`));
|
|
63
|
+
if (!match)
|
|
64
|
+
continue;
|
|
65
|
+
const methodName = match[1];
|
|
66
|
+
const methodLine = i + 1; // 1-based
|
|
67
|
+
const isAsync = line.includes('async def');
|
|
68
|
+
const isStatic = i > 0 && /^\s*@staticmethod/.test(lines[i - 1]);
|
|
69
|
+
const isClassMethod = i > 0 && /^\s*@classmethod/.test(lines[i - 1]);
|
|
70
|
+
// Collect decorators above
|
|
71
|
+
const decorators = [];
|
|
72
|
+
for (let d = i - 1; d >= classStartIdx; d--) {
|
|
73
|
+
const decMatch = lines[d].match(new RegExp(`^\\s{${bodyIndent}}@(\\w+)`));
|
|
74
|
+
if (decMatch) {
|
|
75
|
+
decorators.unshift(`@${decMatch[1]}`);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Determine visibility from name
|
|
82
|
+
const visibility = methodName.startsWith('__') && !methodName.endsWith('__')
|
|
83
|
+
? 'private'
|
|
84
|
+
: methodName.startsWith('_')
|
|
85
|
+
? 'protected'
|
|
86
|
+
: 'public';
|
|
87
|
+
methods.push({
|
|
88
|
+
name: methodName,
|
|
89
|
+
kind: isStatic || isClassMethod ? 'function' : 'method',
|
|
90
|
+
start_line: methodLine,
|
|
91
|
+
end_line: 0, // computed below
|
|
92
|
+
signature: line.trim(),
|
|
93
|
+
visibility,
|
|
94
|
+
is_async: isAsync,
|
|
95
|
+
is_static: isStatic,
|
|
96
|
+
decorators: decorators.length > 0 ? decorators : undefined,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Compute end_lines for methods
|
|
100
|
+
for (let m = 0; m < methods.length; m++) {
|
|
101
|
+
if (m < methods.length - 1) {
|
|
102
|
+
const nextStart = methods[m + 1].start_line;
|
|
103
|
+
let endLine = nextStart - 1;
|
|
104
|
+
for (let k = nextStart - 2; k >= methods[m].start_line; k--) {
|
|
105
|
+
const l = lines[k];
|
|
106
|
+
if (l.trim() === '' || new RegExp(`^\\s{${bodyIndent}}@`).test(l)) {
|
|
107
|
+
endLine = k;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
methods[m].end_line = endLine;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
methods[m].end_line = entry.end_line;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
entry.children = methods;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* PHP: ast-index doesn't return methods inside classes.
|
|
124
|
+
* Parse file content to extract `function` methods for classes without children.
|
|
125
|
+
*/
|
|
126
|
+
function enrichPHPClassMethods(entries, lines) {
|
|
127
|
+
for (const entry of entries) {
|
|
128
|
+
if (entry.kind.toLowerCase() !== 'class')
|
|
129
|
+
continue;
|
|
130
|
+
if (entry.children && entry.children.length > 0)
|
|
131
|
+
continue;
|
|
132
|
+
const classStartIdx = entry.start_line - 1;
|
|
133
|
+
const classEndIdx = entry.end_line - 1;
|
|
134
|
+
// Detect class body indent: look for first `function ` inside class range
|
|
135
|
+
let bodyIndent = -1;
|
|
136
|
+
for (let i = classStartIdx + 1; i <= classEndIdx && i < lines.length; i++) {
|
|
137
|
+
const fnMatch = lines[i].match(/^(\s+)(?:public|private|protected|static|\s)*function\s/);
|
|
138
|
+
if (fnMatch) {
|
|
139
|
+
bodyIndent = fnMatch[1].length;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (bodyIndent < 0)
|
|
144
|
+
continue;
|
|
145
|
+
const methods = [];
|
|
146
|
+
for (let i = classStartIdx + 1; i <= classEndIdx && i < lines.length; i++) {
|
|
147
|
+
const line = lines[i];
|
|
148
|
+
// Match PHP method: [visibility] [static] function name(
|
|
149
|
+
const match = line.match(new RegExp(`^\\s{${bodyIndent}}(?:(public|private|protected)\\s+)?(?:(static)\\s+)?function\\s+(\\w+)\\s*\\(`));
|
|
150
|
+
if (!match)
|
|
151
|
+
continue;
|
|
152
|
+
const visibility = match[1] ?? 'public';
|
|
153
|
+
const isStatic = !!match[2];
|
|
154
|
+
const methodName = match[3];
|
|
155
|
+
const methodLine = i + 1;
|
|
156
|
+
methods.push({
|
|
157
|
+
name: methodName,
|
|
158
|
+
kind: isStatic ? 'function' : 'method',
|
|
159
|
+
start_line: methodLine,
|
|
160
|
+
end_line: 0,
|
|
161
|
+
signature: line.trim(),
|
|
162
|
+
visibility,
|
|
163
|
+
is_static: isStatic,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Compute end_lines
|
|
167
|
+
for (let m = 0; m < methods.length; m++) {
|
|
168
|
+
if (m < methods.length - 1) {
|
|
169
|
+
methods[m].end_line = methods[m + 1].start_line - 1;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
methods[m].end_line = entry.end_line;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
entry.children = methods;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Fix the last entry's end_line to use actual file line count */
|
|
179
|
+
export function fixLastEndLine(entries, totalLines) {
|
|
180
|
+
if (entries.length === 0)
|
|
181
|
+
return;
|
|
182
|
+
const last = entries[entries.length - 1];
|
|
183
|
+
last.end_line = totalLines;
|
|
184
|
+
if (last.children?.length) {
|
|
185
|
+
fixLastEndLine(last.children, last.end_line - 1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/** Read actual signature lines from file content */
|
|
189
|
+
function enrichSignatures(entries, lines) {
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
if (!entry.signature) {
|
|
192
|
+
const lineIdx = entry.start_line - 1;
|
|
193
|
+
if (lineIdx >= 0 && lineIdx < lines.length) {
|
|
194
|
+
entry.signature = lines[lineIdx].trim();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (entry.children?.length) {
|
|
198
|
+
enrichSignatures(entry.children, lines);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=enricher.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsing functions for ast-index text/JSON output.
|
|
3
|
+
* No state, no side effects — safe to import anywhere.
|
|
4
|
+
*/
|
|
5
|
+
import type { SymbolInfo, SymbolKind, Visibility } from '../types.js';
|
|
6
|
+
import type { AstIndexOutlineEntry, AstIndexImplementation, AstIndexHierarchyNode, AstIndexImportEntry, AstIndexAgrepMatch, AstIndexTodoEntry, AstIndexDeprecatedEntry, AstIndexAnnotationEntry, AstIndexModuleEntry, AstIndexModuleDep, AstIndexUnusedDep, AstIndexModuleApi } from './types.js';
|
|
7
|
+
export declare function parseFileCount(statsText: string): number;
|
|
8
|
+
/**
|
|
9
|
+
* Parse text output from `ast-index outline`:
|
|
10
|
+
* Outline of src/file.ts:
|
|
11
|
+
* :10 ClassName [class]
|
|
12
|
+
* :11 propName [property]
|
|
13
|
+
* :14 methodName [function]
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseOutlineText(text: string): AstIndexOutlineEntry[];
|
|
16
|
+
export declare function parseImplementationsText(text: string): AstIndexImplementation[];
|
|
17
|
+
export declare function parseHierarchyText(text: string, rootName: string): AstIndexHierarchyNode | null;
|
|
18
|
+
export declare function parseImportsText(text: string): AstIndexImportEntry[];
|
|
19
|
+
export declare function parseAgrepText(text: string): AstIndexAgrepMatch[];
|
|
20
|
+
export declare function parseTodoText(text: string): AstIndexTodoEntry[];
|
|
21
|
+
export declare function parseDeprecatedText(text: string): AstIndexDeprecatedEntry[];
|
|
22
|
+
export declare function parseAnnotationsText(text: string, annotationName: string): AstIndexAnnotationEntry[];
|
|
23
|
+
export declare function parseModuleListText(text: string): AstIndexModuleEntry[];
|
|
24
|
+
export declare function parseModuleDepText(text: string): AstIndexModuleDep[];
|
|
25
|
+
export declare function parseUnusedDepsText(text: string): AstIndexUnusedDep[];
|
|
26
|
+
export declare function parseModuleApiText(text: string): AstIndexModuleApi[];
|
|
27
|
+
export declare function mapKind(kind: string): SymbolKind;
|
|
28
|
+
export declare function mapVisibility(vis?: string): Visibility;
|
|
29
|
+
export declare function detectLanguage(filePath: string): string;
|
|
30
|
+
export declare function mapOutlineEntry(entry: AstIndexOutlineEntry): SymbolInfo;
|
|
31
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsing functions for ast-index text/JSON output.
|
|
3
|
+
* No state, no side effects — safe to import anywhere.
|
|
4
|
+
*/
|
|
5
|
+
export function parseFileCount(statsText) {
|
|
6
|
+
try {
|
|
7
|
+
const json = JSON.parse(statsText);
|
|
8
|
+
if (json?.stats?.file_count !== undefined)
|
|
9
|
+
return json.stats.file_count;
|
|
10
|
+
}
|
|
11
|
+
catch { /* not JSON, fall through */ }
|
|
12
|
+
const match = statsText.match(/Files:\s*(\d+)/);
|
|
13
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse text output from `ast-index outline`:
|
|
17
|
+
* Outline of src/file.ts:
|
|
18
|
+
* :10 ClassName [class]
|
|
19
|
+
* :11 propName [property]
|
|
20
|
+
* :14 methodName [function]
|
|
21
|
+
*/
|
|
22
|
+
export function parseOutlineText(text) {
|
|
23
|
+
const lines = text.split('\n');
|
|
24
|
+
const entries = [];
|
|
25
|
+
const classStack = [];
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const match = line.match(/^(\s*):(\d+)\s+(\S+)\s+\[(\w+)\]/);
|
|
28
|
+
if (!match)
|
|
29
|
+
continue;
|
|
30
|
+
const indent = match[1].length;
|
|
31
|
+
const entry = {
|
|
32
|
+
name: match[3],
|
|
33
|
+
kind: match[4],
|
|
34
|
+
start_line: parseInt(match[2], 10),
|
|
35
|
+
end_line: 0,
|
|
36
|
+
};
|
|
37
|
+
while (classStack.length > 0 && classStack[classStack.length - 1].indent >= indent) {
|
|
38
|
+
classStack.pop();
|
|
39
|
+
}
|
|
40
|
+
if (classStack.length > 0) {
|
|
41
|
+
const parent = classStack[classStack.length - 1].entry;
|
|
42
|
+
if (!parent.children)
|
|
43
|
+
parent.children = [];
|
|
44
|
+
parent.children.push(entry);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
entries.push(entry);
|
|
48
|
+
}
|
|
49
|
+
if (['class', 'interface', 'struct', 'enum', 'impl', 'trait', 'namespace', 'module'].includes(entry.kind.toLowerCase())) {
|
|
50
|
+
classStack.push({ entry, indent });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
computeEndLines(entries);
|
|
54
|
+
return entries;
|
|
55
|
+
}
|
|
56
|
+
function computeEndLines(entries) {
|
|
57
|
+
for (let i = 0; i < entries.length; i++) {
|
|
58
|
+
if (entries[i].children?.length) {
|
|
59
|
+
computeEndLines(entries[i].children);
|
|
60
|
+
}
|
|
61
|
+
if (i < entries.length - 1) {
|
|
62
|
+
entries[i].end_line = entries[i + 1].start_line - 1;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
const children = entries[i].children;
|
|
66
|
+
if (children?.length) {
|
|
67
|
+
entries[i].end_line = children[children.length - 1].end_line + 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
entries[i].end_line = entries[i].start_line + 10; // estimated
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function parseImplementationsText(text) {
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const line of text.split('\n')) {
|
|
78
|
+
const m = line.match(/^\s*(class|interface|trait|struct|impl)\s+(\S+)\s+\((.+):(\d+)\)/);
|
|
79
|
+
if (m) {
|
|
80
|
+
results.push({ kind: m[1], name: m[2], file: m[3], line: parseInt(m[4], 10) });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
85
|
+
export function parseHierarchyText(text, rootName) {
|
|
86
|
+
if (!text.trim())
|
|
87
|
+
return null;
|
|
88
|
+
// Parse ast-index hierarchy text output:
|
|
89
|
+
// Hierarchy for 'ClassName':
|
|
90
|
+
// Parents:
|
|
91
|
+
// ParentClass (extends)
|
|
92
|
+
// Children:
|
|
93
|
+
// ChildClass (implements) (file.ts:42)
|
|
94
|
+
const lines = text.split('\n');
|
|
95
|
+
const parents = [];
|
|
96
|
+
const childNodes = [];
|
|
97
|
+
let section = 'none';
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
const trimmed = line.trim();
|
|
100
|
+
if (trimmed === 'Parents:') {
|
|
101
|
+
section = 'parents';
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (trimmed === 'Children:') {
|
|
105
|
+
section = 'children';
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (trimmed.startsWith('Hierarchy for') || !trimmed)
|
|
109
|
+
continue;
|
|
110
|
+
// Match: SymbolName (relationship) (file:line) — file:line is optional
|
|
111
|
+
const m = trimmed.match(/^(\S+)\s+\((\w+)\)(?:\s+\((.+):(\d+)\))?/);
|
|
112
|
+
if (m && section !== 'none') {
|
|
113
|
+
const node = {
|
|
114
|
+
name: m[1],
|
|
115
|
+
kind: m[2],
|
|
116
|
+
children: [],
|
|
117
|
+
file: m[3],
|
|
118
|
+
line: m[4] ? parseInt(m[4], 10) : undefined,
|
|
119
|
+
};
|
|
120
|
+
if (section === 'parents')
|
|
121
|
+
parents.push(node);
|
|
122
|
+
else
|
|
123
|
+
childNodes.push(node);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (parents.length === 0 && childNodes.length === 0)
|
|
127
|
+
return null;
|
|
128
|
+
return { name: rootName, kind: 'class', children: childNodes, parents };
|
|
129
|
+
}
|
|
130
|
+
export function parseImportsText(text) {
|
|
131
|
+
const entries = [];
|
|
132
|
+
for (const line of text.split('\n')) {
|
|
133
|
+
const trimmed = line.trim();
|
|
134
|
+
if (!trimmed || trimmed.startsWith('Imports in') || trimmed.startsWith('Total:'))
|
|
135
|
+
continue;
|
|
136
|
+
// Match: { X, Y } from 'source'
|
|
137
|
+
const braceMatch = trimmed.match(/^\{\s*(.+?)\s*\}\s+from\s+['"](.+?)['"]/);
|
|
138
|
+
if (braceMatch) {
|
|
139
|
+
entries.push({ specifiers: braceMatch[1].split(',').map(s => s.trim()), source: braceMatch[2] });
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
// Match: * as X from 'source'
|
|
143
|
+
const nsMatch = trimmed.match(/^\*\s+as\s+(\S+)\s+from\s+['"](.+?)['"]/);
|
|
144
|
+
if (nsMatch) {
|
|
145
|
+
entries.push({ specifiers: [nsMatch[1]], source: nsMatch[2], isNamespace: true });
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
// Match: X from 'source' (default import)
|
|
149
|
+
const defaultMatch = trimmed.match(/^(\w+)\s+from\s+['"](.+?)['"]/);
|
|
150
|
+
if (defaultMatch) {
|
|
151
|
+
entries.push({ specifiers: [defaultMatch[1]], source: defaultMatch[2], isDefault: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return entries;
|
|
155
|
+
}
|
|
156
|
+
export function parseAgrepText(text) {
|
|
157
|
+
const results = [];
|
|
158
|
+
for (const line of text.split('\n')) {
|
|
159
|
+
if (!line.trim())
|
|
160
|
+
continue;
|
|
161
|
+
// Format: file:line:matched_text OR file:line: matched_text
|
|
162
|
+
const match = line.match(/^(.+?):(\d+):(.*)$/);
|
|
163
|
+
if (match) {
|
|
164
|
+
results.push({ file: match[1], line: parseInt(match[2], 10), text: match[3].trim() });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return results;
|
|
168
|
+
}
|
|
169
|
+
export function parseTodoText(text) {
|
|
170
|
+
const results = [];
|
|
171
|
+
for (const line of text.split('\n')) {
|
|
172
|
+
if (!line.trim())
|
|
173
|
+
continue;
|
|
174
|
+
const match = line.match(/^(.+?):(\d+):\s*(TODO|FIXME|HACK|XXX|NOTE|WARN(?:ING)?)[:\s]+(.*)$/i);
|
|
175
|
+
if (match) {
|
|
176
|
+
results.push({ file: match[1], line: parseInt(match[2], 10), kind: match[3].toUpperCase(), text: match[4].trim() });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return results;
|
|
180
|
+
}
|
|
181
|
+
export function parseDeprecatedText(text) {
|
|
182
|
+
const results = [];
|
|
183
|
+
for (const line of text.split('\n')) {
|
|
184
|
+
if (!line.trim())
|
|
185
|
+
continue;
|
|
186
|
+
// Try format: kind name (file:line) - message OR kind name (file:line)
|
|
187
|
+
const match = line.match(/^(\w+)\s+(\S+)\s+\((.+?):(\d+)\)(?:\s*-\s*(.+))?$/);
|
|
188
|
+
if (match) {
|
|
189
|
+
results.push({ kind: match[1], name: match[2], file: match[3], line: parseInt(match[4], 10), message: match[5]?.trim() });
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return results;
|
|
193
|
+
}
|
|
194
|
+
export function parseAnnotationsText(text, annotationName) {
|
|
195
|
+
const results = [];
|
|
196
|
+
for (const line of text.split('\n')) {
|
|
197
|
+
if (!line.trim())
|
|
198
|
+
continue;
|
|
199
|
+
// Try format: kind name (file:line) OR @Annotation kind name (file:line)
|
|
200
|
+
const match = line.match(/^(?:@\S+\s+)?(\w+)\s+(\S+)\s+\((.+?):(\d+)\)$/);
|
|
201
|
+
if (match) {
|
|
202
|
+
results.push({ kind: match[1], name: match[2], file: match[3], line: parseInt(match[4], 10), annotation: annotationName });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return results;
|
|
206
|
+
}
|
|
207
|
+
export function parseModuleListText(text) {
|
|
208
|
+
const results = [];
|
|
209
|
+
for (const line of text.split('\n')) {
|
|
210
|
+
if (!line.trim())
|
|
211
|
+
continue;
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(line);
|
|
214
|
+
if (Array.isArray(parsed))
|
|
215
|
+
return parsed;
|
|
216
|
+
}
|
|
217
|
+
catch { /* not JSON, parse as text */ }
|
|
218
|
+
// Format: name (path) — N files OR name (path) OR path
|
|
219
|
+
const match = line.match(/^(\S+)\s+\((.+?)\)(?:\s*—\s*(\d+)\s+files?)?$/);
|
|
220
|
+
if (match) {
|
|
221
|
+
results.push({ name: match[1], path: match[2], file_count: match[3] ? parseInt(match[3], 10) : undefined });
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const trimmed = line.trim();
|
|
225
|
+
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('─')) {
|
|
226
|
+
const name = trimmed.split('/').pop() ?? trimmed;
|
|
227
|
+
results.push({ name, path: trimmed });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
export function parseModuleDepText(text) {
|
|
234
|
+
const results = [];
|
|
235
|
+
for (const line of text.split('\n')) {
|
|
236
|
+
if (!line.trim())
|
|
237
|
+
continue;
|
|
238
|
+
try {
|
|
239
|
+
const parsed = JSON.parse(line);
|
|
240
|
+
if (Array.isArray(parsed))
|
|
241
|
+
return parsed;
|
|
242
|
+
}
|
|
243
|
+
catch { /* not JSON, parse as text */ }
|
|
244
|
+
// Format: → name (path) OR ← name (path) OR name (path) OR name
|
|
245
|
+
const match = line.match(/^[→←\-\s]*(\S+)(?:\s+\((.+?)\))?(?:\s+\[(direct|transitive)\])?$/);
|
|
246
|
+
if (match) {
|
|
247
|
+
results.push({ name: match[1], path: match[2] ?? match[1], type: match[3] });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return results;
|
|
251
|
+
}
|
|
252
|
+
export function parseUnusedDepsText(text) {
|
|
253
|
+
const results = [];
|
|
254
|
+
for (const line of text.split('\n')) {
|
|
255
|
+
if (!line.trim())
|
|
256
|
+
continue;
|
|
257
|
+
try {
|
|
258
|
+
const parsed = JSON.parse(line);
|
|
259
|
+
if (Array.isArray(parsed))
|
|
260
|
+
return parsed;
|
|
261
|
+
}
|
|
262
|
+
catch { /* not JSON, parse as text */ }
|
|
263
|
+
// Format: ⚠ name (path) — reason OR name (path) OR name — reason
|
|
264
|
+
const match = line.match(/^[⚠!\s]*(\S+)(?:\s+\((.+?)\))?(?:\s*[—\-]+\s*(.+))?$/);
|
|
265
|
+
if (match) {
|
|
266
|
+
results.push({ name: match[1], path: match[2] ?? match[1], reason: match[3]?.trim() });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return results;
|
|
270
|
+
}
|
|
271
|
+
export function parseModuleApiText(text) {
|
|
272
|
+
const results = [];
|
|
273
|
+
for (const line of text.split('\n')) {
|
|
274
|
+
if (!line.trim())
|
|
275
|
+
continue;
|
|
276
|
+
try {
|
|
277
|
+
const parsed = JSON.parse(line);
|
|
278
|
+
if (Array.isArray(parsed))
|
|
279
|
+
return parsed;
|
|
280
|
+
}
|
|
281
|
+
catch { /* not JSON, parse as text */ }
|
|
282
|
+
// Format: kind name (file:line) OR kind name signature (file:line)
|
|
283
|
+
const match = line.match(/^(\w+)\s+(\S+)(?:\s+(.*?))?\s+\((.+?):(\d+)\)$/);
|
|
284
|
+
if (match) {
|
|
285
|
+
results.push({ kind: match[1], name: match[2], signature: match[3]?.trim() || undefined, file: match[4], line: parseInt(match[5], 10) });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
export function mapKind(kind) {
|
|
291
|
+
const map = {
|
|
292
|
+
function: 'function', class: 'class', method: 'method', property: 'property',
|
|
293
|
+
variable: 'variable', type: 'type', interface: 'interface', enum: 'enum',
|
|
294
|
+
constant: 'constant', namespace: 'namespace', struct: 'class', trait: 'interface',
|
|
295
|
+
impl: 'class', module: 'namespace',
|
|
296
|
+
};
|
|
297
|
+
return map[kind.toLowerCase()] ?? 'function';
|
|
298
|
+
}
|
|
299
|
+
export function mapVisibility(vis) {
|
|
300
|
+
if (!vis)
|
|
301
|
+
return 'default';
|
|
302
|
+
const map = {
|
|
303
|
+
public: 'public', private: 'private', protected: 'protected', pub: 'public', export: 'public',
|
|
304
|
+
};
|
|
305
|
+
return map[vis.toLowerCase()] ?? 'default';
|
|
306
|
+
}
|
|
307
|
+
export function detectLanguage(filePath) {
|
|
308
|
+
const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
|
|
309
|
+
const map = {
|
|
310
|
+
ts: 'TypeScript', tsx: 'TypeScript', js: 'JavaScript', jsx: 'JavaScript', mjs: 'JavaScript',
|
|
311
|
+
py: 'Python', go: 'Go', rs: 'Rust', java: 'Java', kt: 'Kotlin', kts: 'Kotlin',
|
|
312
|
+
swift: 'Swift', cs: 'C#', cpp: 'C++', cc: 'C++', cxx: 'C++', hpp: 'C++', c: 'C', h: 'C',
|
|
313
|
+
php: 'PHP', rb: 'Ruby', scala: 'Scala', dart: 'Dart', lua: 'Lua',
|
|
314
|
+
sh: 'Bash', bash: 'Bash', sql: 'SQL', r: 'R', vue: 'Vue', svelte: 'Svelte',
|
|
315
|
+
pl: 'Perl', pm: 'Perl', ex: 'Elixir', exs: 'Elixir', groovy: 'Groovy',
|
|
316
|
+
m: 'Objective-C', proto: 'Protocol Buffers', bsl: 'BSL',
|
|
317
|
+
};
|
|
318
|
+
return map[ext] ?? 'Unknown';
|
|
319
|
+
}
|
|
320
|
+
export function mapOutlineEntry(entry) {
|
|
321
|
+
return {
|
|
322
|
+
name: entry.name,
|
|
323
|
+
qualifiedName: entry.name,
|
|
324
|
+
kind: mapKind(entry.kind),
|
|
325
|
+
signature: entry.signature ?? entry.name,
|
|
326
|
+
location: {
|
|
327
|
+
startLine: entry.start_line,
|
|
328
|
+
endLine: entry.end_line,
|
|
329
|
+
lineCount: entry.end_line - entry.start_line + 1,
|
|
330
|
+
},
|
|
331
|
+
visibility: mapVisibility(entry.visibility),
|
|
332
|
+
async: entry.is_async ?? false,
|
|
333
|
+
static: entry.is_static ?? false,
|
|
334
|
+
decorators: entry.decorators ?? [],
|
|
335
|
+
children: (entry.children ?? []).map(c => mapOutlineEntry(c)),
|
|
336
|
+
doc: entry.doc ?? null,
|
|
337
|
+
references: [],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regex-based fallback parser for Python.
|
|
3
|
+
* Used when the ast-index binary is unavailable.
|
|
4
|
+
* Extracts top-level symbols (classes, functions, variables) and class methods.
|
|
5
|
+
*/
|
|
6
|
+
import type { AstIndexOutlineEntry } from './types.js';
|
|
7
|
+
export declare function parsePythonRegex(content: string): AstIndexOutlineEntry[];
|
|
8
|
+
//# sourceMappingURL=regex-parser-python.d.ts.map
|