@parseme/cli 0.0.4 → 0.0.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 +102 -58
- package/dist/cli/cli.js +25 -34
- package/dist/core/analyzers/ast-analyzer.d.ts +1 -1
- package/dist/core/analyzers/ast-analyzer.js +9 -23
- package/dist/core/analyzers/framework-detector.d.ts +4 -6
- package/dist/core/analyzers/framework-detector.js +154 -165
- package/dist/core/analyzers/pattern-detector.d.ts +2 -2
- package/dist/core/analyzers/pattern-detector.js +93 -13
- package/dist/core/analyzers/project-analyzer.d.ts +1 -1
- package/dist/core/analyzers/project-analyzer.js +7 -18
- package/dist/core/config.d.ts +0 -1
- package/dist/core/config.js +6 -30
- package/dist/core/context-builder.d.ts +1 -3
- package/dist/core/context-builder.js +97 -181
- package/dist/core/generator.js +7 -5
- package/dist/core/types/analyzer-types.d.ts +3 -4
- package/dist/core/types/config-types.d.ts +2 -3
- package/dist/core/types/generator-types.d.ts +0 -2
- package/dist/core/types/project-types.d.ts +1 -1
- package/dist/utils/file-collector.d.ts +23 -0
- package/dist/utils/file-collector.js +61 -0
- package/dist/utils/file-filter.d.ts +30 -0
- package/dist/utils/file-filter.js +99 -0
- package/dist/{core/analyzers/git-analyzer.d.ts → utils/git.d.ts} +1 -1
- package/package.json +9 -6
- /package/dist/{core/analyzers/git-analyzer.js → utils/git.js} +0 -0
- /package/dist/{cli → utils}/prompt.d.ts +0 -0
- /package/dist/{cli → utils}/prompt.js +0 -0
|
@@ -4,84 +4,12 @@ export class ContextBuilder {
|
|
|
4
4
|
constructor(config) {
|
|
5
5
|
this.config = config;
|
|
6
6
|
}
|
|
7
|
-
truncateContent(content, type = 'chars') {
|
|
8
|
-
const limits = this.config.get().limits;
|
|
9
|
-
if (!limits) {
|
|
10
|
-
return content;
|
|
11
|
-
}
|
|
12
|
-
const strategy = limits.truncateStrategy || 'truncate';
|
|
13
|
-
if (type === 'lines') {
|
|
14
|
-
const lines = content.split('\n');
|
|
15
|
-
const maxLines = limits.maxLinesPerFile || 1000;
|
|
16
|
-
if (lines.length > maxLines) {
|
|
17
|
-
if (strategy === 'split') {
|
|
18
|
-
return this.splitContentByLines(lines, maxLines);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
const truncated = lines.slice(0, maxLines).join('\n');
|
|
22
|
-
return truncated + '\n\n[... truncated for AI compatibility ...]';
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
const maxChars = limits.maxCharsPerFile || 50000;
|
|
28
|
-
if (content.length > maxChars) {
|
|
29
|
-
if (strategy === 'split') {
|
|
30
|
-
return this.splitContentByChars(content, maxChars);
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
const truncated = content.substring(0, maxChars - 100);
|
|
34
|
-
return truncated + '\n\n[... truncated for AI compatibility ...]';
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return content;
|
|
39
|
-
}
|
|
40
|
-
splitContentByLines(lines, maxLines) {
|
|
41
|
-
const parts = [];
|
|
42
|
-
const safeMaxLines = maxLines - 5; // Reserve space for split indicators
|
|
43
|
-
for (let i = 0; i < lines.length; i += safeMaxLines) {
|
|
44
|
-
const chunk = lines.slice(i, i + safeMaxLines);
|
|
45
|
-
const partNumber = Math.floor(i / safeMaxLines) + 1;
|
|
46
|
-
const totalParts = Math.ceil(lines.length / safeMaxLines);
|
|
47
|
-
let partContent = chunk.join('\n');
|
|
48
|
-
partContent += `\n\n[... part ${partNumber} of ${totalParts} ...]`;
|
|
49
|
-
parts.push(partContent);
|
|
50
|
-
}
|
|
51
|
-
return parts;
|
|
52
|
-
}
|
|
53
|
-
splitContentByChars(content, maxChars) {
|
|
54
|
-
const parts = [];
|
|
55
|
-
const safeMaxChars = maxChars - 200; // Reserve space for split indicators
|
|
56
|
-
for (let i = 0; i < content.length; i += safeMaxChars) {
|
|
57
|
-
let chunk = content.substring(i, i + safeMaxChars);
|
|
58
|
-
const partNumber = Math.floor(i / safeMaxChars) + 1;
|
|
59
|
-
const totalParts = Math.ceil(content.length / safeMaxChars);
|
|
60
|
-
// Try to break at a reasonable place (newline or word boundary)
|
|
61
|
-
if (i + safeMaxChars < content.length) {
|
|
62
|
-
const lastNewline = chunk.lastIndexOf('\n');
|
|
63
|
-
const lastSpace = chunk.lastIndexOf(' ');
|
|
64
|
-
const breakPoint = lastNewline > -1 ? lastNewline : lastSpace > -1 ? lastSpace : chunk.length;
|
|
65
|
-
if (breakPoint > safeMaxChars * 0.8) {
|
|
66
|
-
// Only break if we're not losing too much content
|
|
67
|
-
chunk = chunk.substring(0, breakPoint);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
chunk += `\n\n[... part ${partNumber} of ${totalParts} ...]`;
|
|
71
|
-
parts.push(chunk);
|
|
72
|
-
}
|
|
73
|
-
return parts;
|
|
74
|
-
}
|
|
75
7
|
build(context) {
|
|
76
8
|
return this.buildMultiFile(context);
|
|
77
9
|
}
|
|
78
10
|
buildMultiFile(context) {
|
|
79
11
|
const { projectInfo, fileAnalyses, gitInfo, contextDir, outputPath } = context;
|
|
80
|
-
|
|
81
|
-
// Limit number of files analyzed if specified
|
|
82
|
-
const limitedFileAnalyses = limits?.maxFilesPerContext
|
|
83
|
-
? fileAnalyses.slice(0, limits.maxFilesPerContext)
|
|
84
|
-
: fileAnalyses;
|
|
12
|
+
// Files are now pre-limited in their respective analyzers, so no need to limit here
|
|
85
13
|
// Calculate the relative path for the link in markdown
|
|
86
14
|
let linkPath = 'parseme-context';
|
|
87
15
|
if (contextDir && outputPath) {
|
|
@@ -99,42 +27,36 @@ export class ContextBuilder {
|
|
|
99
27
|
// Fallback: use contextDir as-is if no outputPath provided
|
|
100
28
|
linkPath = contextDir;
|
|
101
29
|
}
|
|
102
|
-
|
|
30
|
+
// Check if routes exist before building main content
|
|
31
|
+
// Extract all actual route objects (filter out reference objects)
|
|
32
|
+
const routes = fileAnalyses.flatMap((f) => {
|
|
33
|
+
const fileRoutes = f.routes || [];
|
|
34
|
+
// Only include if it's an array of actual routes, not a reference object
|
|
35
|
+
return Array.isArray(fileRoutes) && fileRoutes.length > 0 && !('$ref' in fileRoutes[0])
|
|
36
|
+
? fileRoutes
|
|
37
|
+
: [];
|
|
38
|
+
});
|
|
39
|
+
const hasRoutes = routes.length > 0;
|
|
40
|
+
const mainContent = this.buildHeader(linkPath, hasRoutes) +
|
|
103
41
|
'\n\n\n' +
|
|
104
42
|
this.buildProjectOverview(projectInfo) +
|
|
105
43
|
'\n\n\n' +
|
|
106
|
-
this.buildSummarySection(
|
|
44
|
+
this.buildSummarySection(linkPath, hasRoutes) +
|
|
107
45
|
'\n\n\n' +
|
|
108
46
|
(gitInfo ? this.buildGitSection(gitInfo) : '');
|
|
109
47
|
const contextFiles = {
|
|
110
48
|
structure: '',
|
|
111
|
-
routes: '',
|
|
112
|
-
};
|
|
113
|
-
// Helper function to merge split files into contextFiles
|
|
114
|
-
const mergeSplitFiles = (result, baseName) => {
|
|
115
|
-
if (typeof result === 'string') {
|
|
116
|
-
contextFiles[baseName] = result;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
// Merge split files into contextFiles
|
|
120
|
-
Object.entries(result).forEach(([key, value]) => {
|
|
121
|
-
contextFiles[key] = value;
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
49
|
};
|
|
125
50
|
// Files list (markdown) - all files in project, not just analyzed ones
|
|
126
51
|
contextFiles.files = this.buildFilesList(context.allFiles);
|
|
127
52
|
// Detailed structure (JSON with AST)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if (routes.length > 0) {
|
|
133
|
-
const routesResult = this.buildDetailedRoutes(routes, limitedFileAnalyses);
|
|
134
|
-
mergeSplitFiles(routesResult, 'api-endpoints');
|
|
53
|
+
contextFiles.structure = this.buildDetailedStructure(fileAnalyses, hasRoutes);
|
|
54
|
+
// Routes documentation (only if routes exist)
|
|
55
|
+
if (hasRoutes) {
|
|
56
|
+
contextFiles.routes = this.buildDetailedRoutes(routes);
|
|
135
57
|
}
|
|
136
58
|
// Git information
|
|
137
|
-
if (gitInfo) {
|
|
59
|
+
if (gitInfo?.diffStat?.length && gitInfo.diffStat.length > 0) {
|
|
138
60
|
contextFiles.gitDiff = this.buildDetailedGit(gitInfo);
|
|
139
61
|
}
|
|
140
62
|
return {
|
|
@@ -142,17 +64,23 @@ export class ContextBuilder {
|
|
|
142
64
|
context: contextFiles,
|
|
143
65
|
};
|
|
144
66
|
}
|
|
145
|
-
buildHeader(linkPath) {
|
|
146
|
-
|
|
67
|
+
buildHeader(linkPath, hasRoutes) {
|
|
68
|
+
const routesInstructions = hasRoutes
|
|
69
|
+
? ` - Files with routes will reference [${linkPath}/routes.json](${linkPath}/routes.json) using a $ref pattern for token efficiency
|
|
70
|
+
5. For API route details, see [${linkPath}/routes.json](${linkPath}/routes.json) which contains all discovered endpoints
|
|
71
|
+
6. For git tracked projects follow the instructions in the "Git Information" section of this file to validate the actuality of the provided information.
|
|
72
|
+
7. Only dive deeper into specific files after reviewing this summary, that replaces the need for initial project exploration and significantly reduces token usage for project comprehension.`
|
|
73
|
+
: `5. For git tracked projects, follow the instructions in the "Git Information" section of this file to validate the actuality of the provided information.
|
|
74
|
+
6. Only dive deeper into specific files after reviewing this summary, that replaces the need for initial project exploration and significantly reduces token usage for project comprehension.`;
|
|
75
|
+
return `## PARSEME - AI Agent Context
|
|
147
76
|
Auto-generated project summary optimized for AI coding agents. This file provides complete project context without requiring full codebase traversal, designed for token efficiency.
|
|
148
77
|
|
|
149
78
|
**Usage Instructions for AI Agents:**
|
|
150
79
|
1. Read this PARSEME.md file completely first before accessing individual project files
|
|
151
80
|
2. Basic project information, script availability and dependency information provides basic understanding of code base and tech stack without checking package.json
|
|
152
|
-
3. Use the provided file list (${linkPath}/files.md) to see all tracked files in the project
|
|
153
|
-
4. Utilize the structure and AST data (${linkPath}/structure.json) for code analysis without manual parsing
|
|
154
|
-
|
|
155
|
-
6. Only dive deeper into specific files after reviewing this summary, that replaces the need for initial project exploration and significantly reduces token usage for project comprehension.`;
|
|
81
|
+
3. Use the provided file list [${linkPath}/files.md](${linkPath}/files.md) to see all tracked files in the project
|
|
82
|
+
4. Utilize the structure and AST data [${linkPath}/structure.json](${linkPath}/structure.json) for code analysis without manual parsing
|
|
83
|
+
${routesInstructions}`;
|
|
156
84
|
}
|
|
157
85
|
buildProjectOverview(projectInfo) {
|
|
158
86
|
let content = `## Basic Project Information
|
|
@@ -161,7 +89,7 @@ Auto-generated project summary optimized for AI coding agents. This file provide
|
|
|
161
89
|
**Description:** ${projectInfo.description || 'No description available.'}
|
|
162
90
|
**Type:** ${projectInfo.type} project
|
|
163
91
|
**Package Manager:** ${projectInfo.packageManager}
|
|
164
|
-
**Framework:** ${projectInfo.
|
|
92
|
+
**Framework:** ${this.formatFrameworksList(projectInfo.frameworks)}`;
|
|
165
93
|
// Add main entry point if available
|
|
166
94
|
if (projectInfo.entryPoints && projectInfo.entryPoints.length > 0) {
|
|
167
95
|
content += `\n**Main Entry Point:** ${projectInfo.entryPoints[0]}`;
|
|
@@ -184,81 +112,81 @@ Auto-generated project summary optimized for AI coding agents. This file provide
|
|
|
184
112
|
}
|
|
185
113
|
return content;
|
|
186
114
|
}
|
|
115
|
+
formatFrameworksList(frameworks) {
|
|
116
|
+
if (!frameworks || frameworks.length === 0) {
|
|
117
|
+
return 'unknown';
|
|
118
|
+
}
|
|
119
|
+
if (frameworks.length === 1) {
|
|
120
|
+
return frameworks[0].name;
|
|
121
|
+
}
|
|
122
|
+
// Multiple frameworks - return comma-separated list
|
|
123
|
+
return frameworks.map((f) => f.name).join(', ');
|
|
124
|
+
}
|
|
187
125
|
buildGitSection(gitInfo) {
|
|
188
|
-
|
|
126
|
+
const base = `## Git Information
|
|
189
127
|
|
|
190
128
|
**State when PARSEME.md and all linked files were automatically generated:**
|
|
191
129
|
|
|
192
130
|
- **Branch:** ${gitInfo.branch}
|
|
193
131
|
- **Commit:** ${gitInfo.lastCommit}${gitInfo.origin ? `\n- **Origin:** ${gitInfo.origin}` : ''}
|
|
194
132
|
|
|
195
|
-
### Git Diff Statistics
|
|
196
|
-
|
|
133
|
+
### Git Diff Statistics`;
|
|
134
|
+
const info = gitInfo.diffStat && gitInfo.diffStat.length > 0
|
|
135
|
+
? `Git diff statistics from the time of generation are available at [parseme-context/gitDiff.md](parseme-context/gitDiff.md) (relative to the commit mentioned above).
|
|
197
136
|
|
|
198
137
|
**AI Agent Command:** To check for changes since generation, run:
|
|
199
138
|
\`\`\`bash
|
|
200
139
|
git diff --stat
|
|
201
140
|
\`\`\`
|
|
202
|
-
Compare the output with the baseline in
|
|
141
|
+
Compare the output with the baseline in [parseme-context/gitDiff.md](parseme-context/gitDiff.md) to detect any modifications.`
|
|
142
|
+
: `Git diff statistics showed no changes at the time of generation relative to the commit mentioned above.`;
|
|
143
|
+
return base + '\n\n' + info;
|
|
203
144
|
}
|
|
204
|
-
buildSummarySection(
|
|
205
|
-
const { fileAnalyses } = context;
|
|
206
|
-
const routes = fileAnalyses.flatMap((f) => f.routes || []).length;
|
|
145
|
+
buildSummarySection(linkPath, hasRoutes) {
|
|
207
146
|
let content = `## Project Files
|
|
208
|
-
A complete list of all tracked files in the project is available at
|
|
147
|
+
A complete list of all git-tracked files in the project (excluding files matching additional exclude patterns) is available at [${linkPath}/files.md](${linkPath}/files.md). This provides a quick overview of the project structure.
|
|
209
148
|
|
|
210
149
|
|
|
211
150
|
## Project Structure & AST
|
|
212
|
-
Detailed structure and Abstract Syntax Tree data for all tracked files is available at
|
|
213
|
-
if (
|
|
214
|
-
content += `\n\n\n## API
|
|
215
|
-
A comprehensive list of all discovered API
|
|
151
|
+
Detailed structure and Abstract Syntax Tree data for all tracked files is available at [${linkPath}/structure.json](${linkPath}/structure.json). This includes file paths, types, imports, exports, functions, classes, interfaces, and routes for comprehensive code analysis without manual parsing.`;
|
|
152
|
+
if (hasRoutes) {
|
|
153
|
+
content += `\n\n\n## API Routes
|
|
154
|
+
A comprehensive list of all discovered API routes is available at [${linkPath}/routes.json](${linkPath}/routes.json). This includes HTTP methods, paths, handler names, and source file locations for backend routes (Express, NestJS, and decorator-based routing).`;
|
|
216
155
|
}
|
|
217
156
|
return content;
|
|
218
157
|
}
|
|
219
158
|
buildFilesList(allFiles) {
|
|
220
|
-
let content = `# Project Files\n
|
|
221
|
-
content += `This is a complete list of all files in the project (excluding files matching exclude patterns).\n\n`;
|
|
159
|
+
let content = `# Project Files\n`;
|
|
222
160
|
allFiles.forEach((file) => {
|
|
223
161
|
content += `- ${file}\n`;
|
|
224
162
|
});
|
|
225
163
|
return content;
|
|
226
164
|
}
|
|
227
|
-
buildDetailedStructure(fileAnalyses) {
|
|
228
|
-
const structureData = fileAnalyses.map((file) =>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
return
|
|
165
|
+
buildDetailedStructure(fileAnalyses, hasRoutes) {
|
|
166
|
+
const structureData = fileAnalyses.map((file) => {
|
|
167
|
+
const routes = file.routes || [];
|
|
168
|
+
// If file has routes and routes exist in the project, replace with reference instead of full route objects
|
|
169
|
+
const routesData = routes.length > 0 && hasRoutes
|
|
170
|
+
? {
|
|
171
|
+
$ref: './routes.json',
|
|
172
|
+
filter: { file: file.path },
|
|
173
|
+
count: routes.length,
|
|
174
|
+
}
|
|
175
|
+
: [];
|
|
176
|
+
return {
|
|
177
|
+
path: file.path,
|
|
178
|
+
type: file.type,
|
|
179
|
+
exports: file.exports,
|
|
180
|
+
imports: file.imports,
|
|
181
|
+
functions: file.functions,
|
|
182
|
+
classes: file.classes,
|
|
183
|
+
routes: routesData,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
return JSON.stringify(structureData, null, 2);
|
|
249
187
|
}
|
|
250
|
-
buildDetailedRoutes(routes
|
|
251
|
-
|
|
252
|
-
const result = this.truncateContent(jsonContent);
|
|
253
|
-
if (Array.isArray(result)) {
|
|
254
|
-
const splitFiles = {};
|
|
255
|
-
result.forEach((part, index) => {
|
|
256
|
-
const suffix = index === 0 ? '' : `_part${index + 1}`;
|
|
257
|
-
splitFiles[`routes${suffix}`] = part;
|
|
258
|
-
});
|
|
259
|
-
return splitFiles;
|
|
260
|
-
}
|
|
261
|
-
return result;
|
|
188
|
+
buildDetailedRoutes(routes) {
|
|
189
|
+
return JSON.stringify(routes, null, 2);
|
|
262
190
|
}
|
|
263
191
|
buildDetailedDependencies(projectInfo) {
|
|
264
192
|
const jsonContent = JSON.stringify({
|
|
@@ -266,44 +194,32 @@ A comprehensive list of all discovered API endpoints is available at \`${linkPat
|
|
|
266
194
|
packageManager: projectInfo.packageManager,
|
|
267
195
|
version: projectInfo.version,
|
|
268
196
|
}, null, 2);
|
|
269
|
-
|
|
270
|
-
if (Array.isArray(result)) {
|
|
271
|
-
const splitFiles = {};
|
|
272
|
-
result.forEach((part, index) => {
|
|
273
|
-
const suffix = index === 0 ? '' : `_part${index + 1}`;
|
|
274
|
-
splitFiles[`dependencies${suffix}`] = part;
|
|
275
|
-
});
|
|
276
|
-
return splitFiles;
|
|
277
|
-
}
|
|
278
|
-
return result;
|
|
197
|
+
return jsonContent;
|
|
279
198
|
}
|
|
280
|
-
buildDetailedFramework(
|
|
281
|
-
if (!
|
|
199
|
+
buildDetailedFramework(frameworks) {
|
|
200
|
+
if (!frameworks || frameworks.length === 0) {
|
|
282
201
|
return '';
|
|
283
202
|
}
|
|
284
|
-
let content =
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
291
|
-
|
|
203
|
+
let content = '';
|
|
204
|
+
frameworks.forEach((framework, index) => {
|
|
205
|
+
if (index > 0) {
|
|
206
|
+
content += '\n\n';
|
|
207
|
+
}
|
|
208
|
+
content += `# Framework: ${framework.name}\n\n`;
|
|
209
|
+
content += `**Version**: ${framework.version || 'Unknown'}\n\n`;
|
|
210
|
+
if (framework.features.length > 0) {
|
|
211
|
+
content += '## Features Detected\n\n';
|
|
212
|
+
framework.features.forEach((feature) => {
|
|
213
|
+
content += `- ${feature}\n`;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
292
217
|
return content;
|
|
293
218
|
}
|
|
294
219
|
buildDetailedGit(gitInfo) {
|
|
295
220
|
let content = `# Git Diff Statistics
|
|
296
|
-
# Generated at time of PARSEME.md creation
|
|
297
|
-
# To check for changes since then, run: git diff --stat
|
|
298
|
-
# Compare the output with the content below
|
|
299
|
-
|
|
300
221
|
`;
|
|
301
|
-
|
|
302
|
-
content += gitInfo.diffStat;
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
content += 'No changes detected at time of generation.';
|
|
306
|
-
}
|
|
222
|
+
content += gitInfo.diffStat;
|
|
307
223
|
return content;
|
|
308
224
|
}
|
|
309
225
|
}
|
package/dist/core/generator.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
1
|
+
import { mkdir, writeFile, rm } from 'fs/promises';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { ASTAnalyzer } from './analyzers/ast-analyzer.js';
|
|
4
4
|
import { FrameworkDetector } from './analyzers/framework-detector.js';
|
|
5
|
-
import { GitAnalyzer } from './analyzers/git-analyzer.js';
|
|
6
5
|
import { ProjectAnalyzer } from './analyzers/project-analyzer.js';
|
|
7
6
|
import { ParsemeConfig } from './config.js';
|
|
8
7
|
import { ContextBuilder } from './context-builder.js';
|
|
8
|
+
import { GitAnalyzer } from '../utils/git.js';
|
|
9
9
|
export class ParsemeGenerator {
|
|
10
10
|
config;
|
|
11
11
|
projectAnalyzer;
|
|
@@ -25,10 +25,10 @@ export class ParsemeGenerator {
|
|
|
25
25
|
const configData = this.config.get();
|
|
26
26
|
// Step 1: Analyze the project structure and metadata
|
|
27
27
|
const projectInfo = await this.projectAnalyzer.analyze(configData.rootDir);
|
|
28
|
-
// Step 2:
|
|
29
|
-
projectInfo.framework = await this.frameworkDetector.detect(projectInfo);
|
|
30
|
-
// Step 3: Analyze all relevant files with AST
|
|
28
|
+
// Step 2: Analyze all relevant files with AST
|
|
31
29
|
const fileAnalyses = await this.astAnalyzer.analyzeProject(configData.rootDir);
|
|
30
|
+
// Step 3: Detect frameworks from dependencies
|
|
31
|
+
projectInfo.frameworks = await this.frameworkDetector.detect(projectInfo);
|
|
32
32
|
// Step 4: Get all project files (for file list output)
|
|
33
33
|
const allFiles = await this.projectAnalyzer.getAllProjectFiles(configData.rootDir);
|
|
34
34
|
// Step 5: Get git information if enabled
|
|
@@ -72,6 +72,8 @@ export class ParsemeGenerator {
|
|
|
72
72
|
const parsemeDir = finalContextDir.startsWith('/')
|
|
73
73
|
? finalContextDir // Absolute path
|
|
74
74
|
: join(baseDir, finalContextDir); // Relative path
|
|
75
|
+
// Clear the directory if it exists, then recreate it
|
|
76
|
+
await rm(parsemeDir, { recursive: true, force: true });
|
|
75
77
|
await mkdir(parsemeDir, { recursive: true });
|
|
76
78
|
await writeFile(finalOutputPath, context.parseme);
|
|
77
79
|
if (context.context) {
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo } from '../analyzers/pattern-detector.js';
|
|
2
|
-
export type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo };
|
|
1
|
+
import type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo, EndpointInfo } from '../analyzers/pattern-detector.js';
|
|
2
|
+
export type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo, EndpointInfo };
|
|
3
3
|
export interface FileAnalysis {
|
|
4
4
|
path: string;
|
|
5
5
|
type: 'route' | 'middleware' | 'model' | 'service' | 'utility' | 'config' | 'test' | 'component';
|
|
6
|
-
framework?: string;
|
|
7
6
|
exports: string[];
|
|
8
7
|
imports: string[];
|
|
9
8
|
functions: string[];
|
|
10
9
|
classes: string[];
|
|
11
|
-
routes?:
|
|
10
|
+
routes?: EndpointInfo[];
|
|
12
11
|
components?: ComponentInfo[];
|
|
13
12
|
services?: ServiceInfo[];
|
|
14
13
|
models?: ModelInfo[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface GeneratorOptions {
|
|
2
2
|
rootDir?: string;
|
|
3
3
|
includeGitInfo?: boolean;
|
|
4
|
+
useGitForFiles?: boolean;
|
|
4
5
|
maxDepth?: number;
|
|
5
6
|
excludePatterns?: string[];
|
|
6
7
|
analyzeFileTypes?: string[];
|
|
@@ -13,6 +14,7 @@ export interface ParsemeConfigFile extends GeneratorOptions {
|
|
|
13
14
|
excludePatterns?: string[];
|
|
14
15
|
maxDepth?: number;
|
|
15
16
|
includeGitInfo?: boolean;
|
|
17
|
+
useGitForFiles?: boolean;
|
|
16
18
|
readmeSuggestion?: boolean;
|
|
17
19
|
sections?: {
|
|
18
20
|
overview?: boolean;
|
|
@@ -29,9 +31,6 @@ export interface ParsemeConfigFile extends GeneratorOptions {
|
|
|
29
31
|
sortOrder?: 'alphabetical' | 'type' | 'size';
|
|
30
32
|
};
|
|
31
33
|
limits?: {
|
|
32
|
-
maxLinesPerFile?: number;
|
|
33
|
-
maxCharsPerFile?: number;
|
|
34
34
|
maxFilesPerContext?: number;
|
|
35
|
-
truncateStrategy?: 'truncate' | 'split' | 'summarize';
|
|
36
35
|
};
|
|
37
36
|
}
|
|
@@ -6,7 +6,7 @@ export interface ProjectInfo {
|
|
|
6
6
|
type: 'typescript' | 'javascript' | 'mixed';
|
|
7
7
|
category: ProjectCategory;
|
|
8
8
|
packageManager: 'unknown' | 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
9
|
-
|
|
9
|
+
frameworks?: FrameworkInfo[];
|
|
10
10
|
buildTool?: BuildToolInfo;
|
|
11
11
|
dependencies: Record<string, string>;
|
|
12
12
|
devDependencies: Record<string, string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ParsemeConfig } from '../core/config.js';
|
|
2
|
+
export interface FileCollectionOptions {
|
|
3
|
+
fileTypes?: string[];
|
|
4
|
+
}
|
|
5
|
+
export interface FileCollectionResult {
|
|
6
|
+
files: string[];
|
|
7
|
+
totalFound: number;
|
|
8
|
+
excluded: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class FileCollector {
|
|
11
|
+
private readonly fileFilter;
|
|
12
|
+
private readonly config;
|
|
13
|
+
constructor(config: ParsemeConfig);
|
|
14
|
+
getFiles(rootDir: string, options?: FileCollectionOptions): Promise<FileCollectionResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Get all project files (no file type filtering, for files.md)
|
|
17
|
+
*/
|
|
18
|
+
getAllProjectFiles(rootDir: string): Promise<FileCollectionResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Get code files for AST analysis (filtered by file types, for structure.json)
|
|
21
|
+
*/
|
|
22
|
+
getCodeFiles(rootDir: string, fileTypes?: string[]): Promise<FileCollectionResult>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { FileFilterService } from './file-filter.js';
|
|
2
|
+
export class FileCollector {
|
|
3
|
+
fileFilter;
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
const configData = this.config.get();
|
|
8
|
+
this.fileFilter = new FileFilterService(configData.excludePatterns, configData.useGitForFiles ?? true);
|
|
9
|
+
}
|
|
10
|
+
async getFiles(rootDir, options = {}) {
|
|
11
|
+
const configData = this.config.get();
|
|
12
|
+
const { fileTypes } = options;
|
|
13
|
+
// Get all filtered files (respects git ignore + custom excludePatterns)
|
|
14
|
+
const allFiles = await this.fileFilter.getFilteredFiles(rootDir);
|
|
15
|
+
// Filter by file types if specified
|
|
16
|
+
let filteredFiles = allFiles;
|
|
17
|
+
if (fileTypes && fileTypes.length > 0) {
|
|
18
|
+
const extensionSet = new Set(fileTypes.map((ext) => (ext.startsWith('.') ? ext : `.${ext}`)));
|
|
19
|
+
filteredFiles = allFiles.filter((file) => {
|
|
20
|
+
const ext = file.substring(file.lastIndexOf('.'));
|
|
21
|
+
return extensionSet.has(ext);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Apply limits
|
|
25
|
+
let finalFiles = filteredFiles;
|
|
26
|
+
let excluded = 0;
|
|
27
|
+
if (configData.limits?.maxFilesPerContext) {
|
|
28
|
+
const limit = configData.limits.maxFilesPerContext;
|
|
29
|
+
if (filteredFiles.length > limit) {
|
|
30
|
+
finalFiles = filteredFiles.slice(0, limit);
|
|
31
|
+
excluded = filteredFiles.length - limit;
|
|
32
|
+
// Show warning when files are excluded
|
|
33
|
+
const fileTypeDesc = fileTypes ? `${fileTypes.join(', ')} files` : 'files';
|
|
34
|
+
console.warn(`⚠️ File limit reached: ${excluded} ${fileTypeDesc} excluded.`);
|
|
35
|
+
console.warn(` Processed: ${limit}/${filteredFiles.length} files`);
|
|
36
|
+
console.warn(` To process more files, increase 'limits.maxFilesPerContext' in your config file.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
files: finalFiles,
|
|
41
|
+
totalFound: filteredFiles.length,
|
|
42
|
+
excluded,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get all project files (no file type filtering, for files.md)
|
|
47
|
+
*/
|
|
48
|
+
async getAllProjectFiles(rootDir) {
|
|
49
|
+
return this.getFiles(rootDir);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get code files for AST analysis (filtered by file types, for structure.json)
|
|
53
|
+
*/
|
|
54
|
+
async getCodeFiles(rootDir, fileTypes) {
|
|
55
|
+
const configData = this.config.get();
|
|
56
|
+
const defaultFileTypes = configData.analyzeFileTypes || ['ts', 'tsx', 'js', 'jsx'];
|
|
57
|
+
return this.getFiles(rootDir, {
|
|
58
|
+
fileTypes: fileTypes || defaultFileTypes,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare class FileFilterService {
|
|
2
|
+
private readonly excludePatterns;
|
|
3
|
+
private readonly useGitForFiles;
|
|
4
|
+
private readonly ig;
|
|
5
|
+
constructor(excludePatterns?: string[], useGitForFiles?: boolean);
|
|
6
|
+
/**
|
|
7
|
+
* Get all files that should be analyzed, respecting:
|
|
8
|
+
* 1. Git-tracked files (respects all .gitignore files automatically)
|
|
9
|
+
* 2. All files (if non-git repo) - respects only custom excludePatterns
|
|
10
|
+
* 3. Custom excludePatterns from config (always applied)
|
|
11
|
+
*/
|
|
12
|
+
getFilteredFiles(rootDir: string, fileExtensions?: string[]): Promise<string[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Check if the directory is a git repository
|
|
15
|
+
*/
|
|
16
|
+
private isGitRepository;
|
|
17
|
+
/**
|
|
18
|
+
* Get all tracked files from git
|
|
19
|
+
* This respects all .gitignore files in the repository (parent, current, and child dirs)
|
|
20
|
+
*/
|
|
21
|
+
private getGitTrackedFiles;
|
|
22
|
+
/**
|
|
23
|
+
* Get all files recursively (fallback for non-git repos)
|
|
24
|
+
*/
|
|
25
|
+
private getAllFilesRecursive;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a specific file should be filtered out
|
|
28
|
+
*/
|
|
29
|
+
shouldIgnore(filePath: string): boolean;
|
|
30
|
+
}
|