agentic-compaction 0.0.1
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/.orchestration/orchestration.md +52 -0
- package/.orchestration/workflows/react/bugfix.md +32 -0
- package/.orchestration/workflows/react/docs.md +59 -0
- package/.orchestration/workflows/react/feature.md +37 -0
- package/.orchestration/workflows/react/performance.md +59 -0
- package/.orchestration/workflows/react/pr.md +19 -0
- package/.orchestration/workflows/react/refactor.md +32 -0
- package/.orchestration/workflows/react/review.md +46 -0
- package/ORCHESTRATION.md +5 -0
- package/README.md +118 -0
- package/compacted_2026-02-12_15-24-24.txt +34 -0
- package/compacted_standalonecompaction_2026-02-12_15-26-38.md +34 -0
- package/compacted_standalonecompaction_2026-02-12_15-28-25.md +34 -0
- package/package.json +25 -0
- package/src/cli.js +59 -0
- package/src/formatter.js +33 -0
- package/src/index.js +75 -0
- package/src/parsers/babel.js +469 -0
- package/src/parsers/python.js +181 -0
- package/src/walker.js +54 -0
- package/test/fixtures/sample.js +18 -0
- package/test/fixtures/sample.py +25 -0
- package/test/test.js +102 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { collectFiles } from './walker.js';
|
|
3
|
+
import { formatOutput, estimateTokens } from './formatter.js';
|
|
4
|
+
import { isBabelParseable, extractSkeleton as extractBabelSkeleton, formatSkeletonForPrompt as formatBabelSkeleton } from './parsers/babel.js';
|
|
5
|
+
import { isPythonParseable, extractSkeleton as extractPythonSkeleton, formatSkeletonForPrompt as formatPythonSkeleton } from './parsers/python.js';
|
|
6
|
+
|
|
7
|
+
export { isBabelParseable } from './parsers/babel.js';
|
|
8
|
+
export { isPythonParseable } from './parsers/python.js';
|
|
9
|
+
export { estimateTokens, formatTokenCount } from './formatter.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compact a single file's content into a skeleton
|
|
13
|
+
* @param {string} filePath - File path (used to determine parser)
|
|
14
|
+
* @param {string} content - File content
|
|
15
|
+
* @returns {{ skeleton: Object|null, formatted: string }}
|
|
16
|
+
*/
|
|
17
|
+
export function compactFile(filePath, content) {
|
|
18
|
+
let skeleton = null;
|
|
19
|
+
let formatted = '';
|
|
20
|
+
|
|
21
|
+
if (isPythonParseable(filePath)) {
|
|
22
|
+
skeleton = extractPythonSkeleton(content, filePath);
|
|
23
|
+
formatted = skeleton ? formatPythonSkeleton(skeleton) : '';
|
|
24
|
+
} else if (isBabelParseable(filePath)) {
|
|
25
|
+
skeleton = extractBabelSkeleton(content, filePath);
|
|
26
|
+
formatted = skeleton ? formatBabelSkeleton(skeleton) : '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return { skeleton, formatted };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compact an entire project directory
|
|
34
|
+
* @param {string} rootPath - Project root path
|
|
35
|
+
* @param {Object} [options] - Options
|
|
36
|
+
* @returns {{ output: string, stats: { files: number, rawTokens: number, compactedTokens: number } }}
|
|
37
|
+
*/
|
|
38
|
+
export function compactProject(rootPath, options = {}) {
|
|
39
|
+
const files = collectFiles(rootPath);
|
|
40
|
+
const results = [];
|
|
41
|
+
let rawTokens = 0;
|
|
42
|
+
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(file.path, 'utf-8');
|
|
46
|
+
rawTokens += estimateTokens(content);
|
|
47
|
+
|
|
48
|
+
let skeleton = null;
|
|
49
|
+
if (isPythonParseable(file.path)) {
|
|
50
|
+
skeleton = extractPythonSkeleton(content, file.path);
|
|
51
|
+
} else if (isBabelParseable(file.path)) {
|
|
52
|
+
skeleton = extractBabelSkeleton(content, file.path);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
results.push({
|
|
56
|
+
relativePath: file.relativePath,
|
|
57
|
+
skeleton,
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// Skip unreadable files
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const output = formatOutput(results);
|
|
65
|
+
const compactedTokens = estimateTokens(output);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
output,
|
|
69
|
+
stats: {
|
|
70
|
+
files: results.length,
|
|
71
|
+
rawTokens,
|
|
72
|
+
compactedTokens,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import _traverse from '@babel/traverse';
|
|
3
|
+
|
|
4
|
+
// Handle ESM/CJS interop for @babel/traverse
|
|
5
|
+
const traverse = _traverse.default || _traverse;
|
|
6
|
+
|
|
7
|
+
const BABEL_EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts'];
|
|
8
|
+
|
|
9
|
+
export const isBabelParseable = (path) => {
|
|
10
|
+
return BABEL_EXTENSIONS.some(ext => path.endsWith(ext));
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const isPascalCase = (name) => {
|
|
14
|
+
return /^[A-Z][a-zA-Z0-9]*$/.test(name);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getReactHOCInfo = (node) => {
|
|
18
|
+
if (node.type !== 'CallExpression') return null;
|
|
19
|
+
|
|
20
|
+
const callee = node.callee;
|
|
21
|
+
let hocType = null;
|
|
22
|
+
|
|
23
|
+
if (callee.type === 'MemberExpression' &&
|
|
24
|
+
callee.object?.name === 'React' &&
|
|
25
|
+
callee.property?.name) {
|
|
26
|
+
const method = callee.property.name;
|
|
27
|
+
if (['forwardRef', 'memo', 'lazy'].includes(method)) {
|
|
28
|
+
hocType = method;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (callee.type === 'Identifier') {
|
|
33
|
+
const name = callee.name;
|
|
34
|
+
if (['forwardRef', 'memo', 'lazy'].includes(name)) {
|
|
35
|
+
hocType = name;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!hocType) return null;
|
|
40
|
+
|
|
41
|
+
const firstArg = node.arguments[0];
|
|
42
|
+
let innerFn = null;
|
|
43
|
+
|
|
44
|
+
if (firstArg?.type === 'ArrowFunctionExpression' ||
|
|
45
|
+
firstArg?.type === 'FunctionExpression') {
|
|
46
|
+
innerFn = firstArg;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { type: hocType, innerFn };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isCreateContext = (node) => {
|
|
53
|
+
if (node.type !== 'CallExpression') return false;
|
|
54
|
+
const callee = node.callee;
|
|
55
|
+
|
|
56
|
+
if (callee.type === 'MemberExpression' &&
|
|
57
|
+
callee.object?.name === 'React' &&
|
|
58
|
+
callee.property?.name === 'createContext') {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (callee.type === 'Identifier' && callee.name === 'createContext') {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const extractDependencyArray = (node) => {
|
|
70
|
+
if (!node) return null;
|
|
71
|
+
|
|
72
|
+
if (node.type === 'ArrayExpression') {
|
|
73
|
+
return node.elements.map(el => {
|
|
74
|
+
if (!el) return '?';
|
|
75
|
+
if (el.type === 'Identifier') return el.name;
|
|
76
|
+
if (el.type === 'MemberExpression') {
|
|
77
|
+
const parts = [];
|
|
78
|
+
let current = el;
|
|
79
|
+
while (current.type === 'MemberExpression') {
|
|
80
|
+
if (current.property?.name) {
|
|
81
|
+
parts.unshift(current.property.name);
|
|
82
|
+
}
|
|
83
|
+
current = current.object;
|
|
84
|
+
}
|
|
85
|
+
if (current.type === 'Identifier') {
|
|
86
|
+
parts.unshift(current.name);
|
|
87
|
+
}
|
|
88
|
+
return parts.join('.') || '?';
|
|
89
|
+
}
|
|
90
|
+
return '?';
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return '?';
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const getParamsString = (params) => {
|
|
98
|
+
return params.map(param => {
|
|
99
|
+
if (param.type === 'Identifier') {
|
|
100
|
+
const typeAnnotation = param.typeAnnotation?.typeAnnotation;
|
|
101
|
+
const typeName = typeAnnotation ? `: ${getTypeName(typeAnnotation)}` : '';
|
|
102
|
+
return `${param.name}${typeName}`;
|
|
103
|
+
}
|
|
104
|
+
if (param.type === 'AssignmentPattern') {
|
|
105
|
+
const left = param.left;
|
|
106
|
+
if (left.type === 'Identifier') {
|
|
107
|
+
return `${left.name} = ...`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (param.type === 'RestElement') {
|
|
111
|
+
return `...${param.argument?.name || 'args'}`;
|
|
112
|
+
}
|
|
113
|
+
if (param.type === 'ObjectPattern') {
|
|
114
|
+
return '{ ... }';
|
|
115
|
+
}
|
|
116
|
+
if (param.type === 'ArrayPattern') {
|
|
117
|
+
return '[ ... ]';
|
|
118
|
+
}
|
|
119
|
+
return '?';
|
|
120
|
+
}).join(', ');
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const getTypeName = (typeAnnotation) => {
|
|
124
|
+
if (!typeAnnotation) return 'any';
|
|
125
|
+
switch (typeAnnotation.type) {
|
|
126
|
+
case 'TSStringKeyword': return 'string';
|
|
127
|
+
case 'TSNumberKeyword': return 'number';
|
|
128
|
+
case 'TSBooleanKeyword': return 'boolean';
|
|
129
|
+
case 'TSVoidKeyword': return 'void';
|
|
130
|
+
case 'TSAnyKeyword': return 'any';
|
|
131
|
+
case 'TSNullKeyword': return 'null';
|
|
132
|
+
case 'TSUndefinedKeyword': return 'undefined';
|
|
133
|
+
case 'TSArrayType': return `${getTypeName(typeAnnotation.elementType)}[]`;
|
|
134
|
+
case 'TSTypeReference': return typeAnnotation.typeName?.name || 'unknown';
|
|
135
|
+
case 'TSUnionType': return typeAnnotation.types.map(getTypeName).join(' | ');
|
|
136
|
+
case 'TSFunctionType': return '(...) => ...';
|
|
137
|
+
default: return 'unknown';
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const getReturnType = (node) => {
|
|
142
|
+
const returnType = node.returnType?.typeAnnotation;
|
|
143
|
+
return returnType ? `: ${getTypeName(returnType)}` : '';
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const extractSignatures = (code, filePath = '') => {
|
|
147
|
+
const signatures = [];
|
|
148
|
+
const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx') || filePath.endsWith('.mts') || filePath.endsWith('.cts');
|
|
149
|
+
|
|
150
|
+
const plugins = [
|
|
151
|
+
'jsx',
|
|
152
|
+
isTypeScript && 'typescript',
|
|
153
|
+
'classProperties',
|
|
154
|
+
'decorators-legacy',
|
|
155
|
+
'exportDefaultFrom',
|
|
156
|
+
'optionalChaining',
|
|
157
|
+
'nullishCoalescingOperator',
|
|
158
|
+
].filter(Boolean);
|
|
159
|
+
|
|
160
|
+
let ast;
|
|
161
|
+
try {
|
|
162
|
+
ast = parse(code, {
|
|
163
|
+
sourceType: 'module',
|
|
164
|
+
plugins,
|
|
165
|
+
errorRecovery: true,
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return signatures;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const seenNames = new Set();
|
|
172
|
+
|
|
173
|
+
traverse(ast, {
|
|
174
|
+
FunctionDeclaration(path) {
|
|
175
|
+
const node = path.node;
|
|
176
|
+
const name = node.id?.name;
|
|
177
|
+
if (!name || seenNames.has(name)) return;
|
|
178
|
+
seenNames.add(name);
|
|
179
|
+
|
|
180
|
+
const params = getParamsString(node.params);
|
|
181
|
+
const returnType = getReturnType(node);
|
|
182
|
+
const async = node.async ? 'async ' : '';
|
|
183
|
+
|
|
184
|
+
signatures.push({
|
|
185
|
+
name,
|
|
186
|
+
signature: `${async}function ${name}(${params})${returnType}`,
|
|
187
|
+
line: node.loc?.start?.line || 0,
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
VariableDeclarator(path) {
|
|
192
|
+
const name = path.node.id?.name;
|
|
193
|
+
if (!name || seenNames.has(name)) return;
|
|
194
|
+
|
|
195
|
+
const init = path.node.init;
|
|
196
|
+
if (!init) return;
|
|
197
|
+
|
|
198
|
+
if (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') {
|
|
199
|
+
seenNames.add(name);
|
|
200
|
+
const params = getParamsString(init.params);
|
|
201
|
+
const returnType = getReturnType(init);
|
|
202
|
+
const async = init.async ? 'async ' : '';
|
|
203
|
+
|
|
204
|
+
signatures.push({
|
|
205
|
+
name,
|
|
206
|
+
signature: `${async}const ${name} = (${params})${returnType} => ...`,
|
|
207
|
+
line: path.node.loc?.start?.line || 0,
|
|
208
|
+
});
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const hocInfo = getReactHOCInfo(init);
|
|
213
|
+
if (hocInfo) {
|
|
214
|
+
seenNames.add(name);
|
|
215
|
+
let params = '?';
|
|
216
|
+
if (hocInfo.innerFn) {
|
|
217
|
+
params = getParamsString(hocInfo.innerFn.params);
|
|
218
|
+
}
|
|
219
|
+
signatures.push({
|
|
220
|
+
name,
|
|
221
|
+
signature: `const ${name} = ${hocInfo.type}((${params}) => ...)`,
|
|
222
|
+
line: path.node.loc?.start?.line || 0,
|
|
223
|
+
});
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (isCreateContext(init)) {
|
|
228
|
+
seenNames.add(name);
|
|
229
|
+
signatures.push({
|
|
230
|
+
name,
|
|
231
|
+
signature: `const ${name} = createContext(...)`,
|
|
232
|
+
line: path.node.loc?.start?.line || 0,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
signatures.sort((a, b) => a.line - b.line);
|
|
239
|
+
return signatures;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export const extractSkeleton = (code, filePath = '') => {
|
|
243
|
+
const isTypeScript = filePath.endsWith('.ts') || filePath.endsWith('.tsx') || filePath.endsWith('.mts') || filePath.endsWith('.cts');
|
|
244
|
+
|
|
245
|
+
const plugins = [
|
|
246
|
+
'jsx',
|
|
247
|
+
isTypeScript && 'typescript',
|
|
248
|
+
'classProperties',
|
|
249
|
+
'decorators-legacy',
|
|
250
|
+
'exportDefaultFrom',
|
|
251
|
+
'optionalChaining',
|
|
252
|
+
'nullishCoalescingOperator',
|
|
253
|
+
].filter(Boolean);
|
|
254
|
+
|
|
255
|
+
let ast;
|
|
256
|
+
try {
|
|
257
|
+
ast = parse(code, {
|
|
258
|
+
sourceType: 'module',
|
|
259
|
+
plugins,
|
|
260
|
+
errorRecovery: true,
|
|
261
|
+
});
|
|
262
|
+
} catch (error) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const skeleton = {
|
|
267
|
+
imports: [],
|
|
268
|
+
exports: [],
|
|
269
|
+
components: [],
|
|
270
|
+
functions: [],
|
|
271
|
+
hooks: { useState: 0, useEffect: [], useCallback: 0, useMemo: 0, useRef: 0, custom: [] },
|
|
272
|
+
constants: 0,
|
|
273
|
+
classes: [],
|
|
274
|
+
interfaces: [],
|
|
275
|
+
types: [],
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
traverse(ast, {
|
|
279
|
+
ImportDeclaration(path) {
|
|
280
|
+
const source = path.node.source.value;
|
|
281
|
+
const specifiers = path.node.specifiers.map(s => {
|
|
282
|
+
if (s.type === 'ImportDefaultSpecifier') return s.local.name;
|
|
283
|
+
if (s.type === 'ImportNamespaceSpecifier') return `* as ${s.local.name}`;
|
|
284
|
+
return s.imported?.name || s.local.name;
|
|
285
|
+
});
|
|
286
|
+
skeleton.imports.push({ source, specifiers });
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
ExportDefaultDeclaration(path) {
|
|
290
|
+
const decl = path.node.declaration;
|
|
291
|
+
if (decl.type === 'Identifier') {
|
|
292
|
+
skeleton.exports.push({ name: decl.name, type: 'default' });
|
|
293
|
+
} else if (decl.type === 'FunctionDeclaration' && decl.id) {
|
|
294
|
+
skeleton.exports.push({ name: decl.id.name, type: 'default' });
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
ExportNamedDeclaration(path) {
|
|
299
|
+
if (path.node.declaration) {
|
|
300
|
+
const decl = path.node.declaration;
|
|
301
|
+
if (decl.type === 'FunctionDeclaration' && decl.id) {
|
|
302
|
+
skeleton.exports.push({ name: decl.id.name, type: 'named' });
|
|
303
|
+
} else if (decl.type === 'VariableDeclaration') {
|
|
304
|
+
decl.declarations.forEach(d => {
|
|
305
|
+
if (d.id?.name) {
|
|
306
|
+
skeleton.exports.push({ name: d.id.name, type: 'named' });
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (path.node.specifiers) {
|
|
312
|
+
path.node.specifiers.forEach(s => {
|
|
313
|
+
skeleton.exports.push({ name: s.exported?.name || s.local.name, type: 'named' });
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
|
|
318
|
+
FunctionDeclaration(path) {
|
|
319
|
+
const name = path.node.id?.name;
|
|
320
|
+
if (!name) return;
|
|
321
|
+
|
|
322
|
+
if (isPascalCase(name)) {
|
|
323
|
+
skeleton.components.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
324
|
+
} else {
|
|
325
|
+
skeleton.functions.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
VariableDeclarator(path) {
|
|
330
|
+
const name = path.node.id?.name;
|
|
331
|
+
const init = path.node.init;
|
|
332
|
+
if (!name || !init) return;
|
|
333
|
+
|
|
334
|
+
if (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') {
|
|
335
|
+
if (isPascalCase(name)) {
|
|
336
|
+
skeleton.components.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
337
|
+
} else {
|
|
338
|
+
skeleton.functions.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const hocInfo = getReactHOCInfo(init);
|
|
344
|
+
if (hocInfo) {
|
|
345
|
+
skeleton.components.push({ name, line: path.node.loc?.start?.line || 0, hoc: hocInfo.type });
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (isCreateContext(init)) {
|
|
350
|
+
skeleton.contexts = skeleton.contexts || [];
|
|
351
|
+
skeleton.contexts.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
skeleton.constants++;
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
CallExpression(path) {
|
|
359
|
+
const callee = path.node.callee;
|
|
360
|
+
if (callee.type === 'Identifier' && callee.name.startsWith('use')) {
|
|
361
|
+
const hookName = callee.name;
|
|
362
|
+
const line = path.node.loc?.start?.line || 0;
|
|
363
|
+
|
|
364
|
+
if (hookName === 'useEffect') {
|
|
365
|
+
const deps = extractDependencyArray(path.node.arguments[1]);
|
|
366
|
+
skeleton.hooks.useEffect.push({ line, deps });
|
|
367
|
+
} else if (skeleton.hooks[hookName] !== undefined) {
|
|
368
|
+
skeleton.hooks[hookName]++;
|
|
369
|
+
} else {
|
|
370
|
+
if (!skeleton.hooks.custom.includes(hookName)) {
|
|
371
|
+
skeleton.hooks.custom.push(hookName);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
ClassDeclaration(path) {
|
|
378
|
+
const name = path.node.id?.name;
|
|
379
|
+
if (name) {
|
|
380
|
+
skeleton.classes.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
|
|
384
|
+
TSInterfaceDeclaration(path) {
|
|
385
|
+
const name = path.node.id?.name;
|
|
386
|
+
if (name) {
|
|
387
|
+
skeleton.interfaces.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
TSTypeAliasDeclaration(path) {
|
|
392
|
+
const name = path.node.id?.name;
|
|
393
|
+
if (name) {
|
|
394
|
+
skeleton.types.push({ name, line: path.node.loc?.start?.line || 0 });
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
return skeleton;
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
export const formatSignaturesForPrompt = (signatures) => {
|
|
403
|
+
if (!signatures || signatures.length === 0) return '';
|
|
404
|
+
return signatures.map(s => ` ${s.signature} // line ${s.line}`).join('\n');
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export const formatSkeletonForPrompt = (skeleton) => {
|
|
408
|
+
if (!skeleton) return '';
|
|
409
|
+
|
|
410
|
+
const lines = [];
|
|
411
|
+
|
|
412
|
+
if (skeleton.imports.length > 0) {
|
|
413
|
+
const local = skeleton.imports.filter(i => i.source.startsWith('.'));
|
|
414
|
+
const extCount = skeleton.imports.length - local.length;
|
|
415
|
+
const parts = [];
|
|
416
|
+
if (extCount > 0) parts.push(`${extCount} ext`);
|
|
417
|
+
parts.push(...local.map(i => i.source));
|
|
418
|
+
lines.push(`imports: ${parts.join(', ')}`);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (skeleton.exports.length > 0) {
|
|
422
|
+
const exportNames = skeleton.exports.map(e => e.type === 'default' ? `${e.name}*` : e.name).join(', ');
|
|
423
|
+
lines.push(`exports: ${exportNames}`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (skeleton.components.length > 0) {
|
|
427
|
+
const componentList = skeleton.components.map(c => c.hoc ? `${c.name}(${c.hoc}):${c.line}` : `${c.name}:${c.line}`).join(', ');
|
|
428
|
+
lines.push(`components: ${componentList}`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (skeleton.contexts?.length > 0) {
|
|
432
|
+
lines.push(`contexts: ${skeleton.contexts.map(c => `${c.name}:${c.line}`).join(', ')}`);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (skeleton.functions.length > 0) {
|
|
436
|
+
lines.push(`fn: ${skeleton.functions.map(f => `${f.name}:${f.line}`).join(', ')}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const hookParts = [];
|
|
440
|
+
if (skeleton.hooks.useState > 0) hookParts.push(`useState(${skeleton.hooks.useState})`);
|
|
441
|
+
if (skeleton.hooks.useCallback > 0) hookParts.push(`useCallback(${skeleton.hooks.useCallback})`);
|
|
442
|
+
if (skeleton.hooks.useMemo > 0) hookParts.push(`useMemo(${skeleton.hooks.useMemo})`);
|
|
443
|
+
if (skeleton.hooks.useRef > 0) hookParts.push(`useRef(${skeleton.hooks.useRef})`);
|
|
444
|
+
if (skeleton.hooks.custom.length > 0) hookParts.push(...skeleton.hooks.custom);
|
|
445
|
+
|
|
446
|
+
if (skeleton.hooks.useEffect.length > 0) {
|
|
447
|
+
const effects = skeleton.hooks.useEffect.map(eff => {
|
|
448
|
+
if (eff.deps === null) return `useEffect(∞):${eff.line}`;
|
|
449
|
+
if (eff.deps === '?') return `useEffect(?):${eff.line}`;
|
|
450
|
+
return `useEffect([${eff.deps.join(',')}]):${eff.line}`;
|
|
451
|
+
});
|
|
452
|
+
hookParts.push(...effects);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (hookParts.length > 0) {
|
|
456
|
+
lines.push(`hooks: ${hookParts.join(', ')}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (skeleton.classes.length > 0) {
|
|
460
|
+
lines.push(`classes: ${skeleton.classes.map(c => `${c.name}:${c.line}`).join(', ')}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (skeleton.interfaces.length > 0 || skeleton.types.length > 0) {
|
|
464
|
+
const typeNames = [...skeleton.interfaces, ...skeleton.types].map(t => `${t.name}:${t.line}`).join(', ');
|
|
465
|
+
lines.push(`types: ${typeNames}`);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return lines.join('\n');
|
|
469
|
+
};
|