@parseme/cli 0.0.3 → 0.0.5

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.
Files changed (47) hide show
  1. package/README.md +182 -187
  2. package/dist/cli/cli.js +144 -0
  3. package/dist/{analyzers → core/analyzers}/ast-analyzer.d.ts +1 -1
  4. package/dist/{analyzers → core/analyzers}/ast-analyzer.js +14 -27
  5. package/dist/core/analyzers/framework-detector.d.ts +7 -0
  6. package/dist/core/analyzers/framework-detector.js +165 -0
  7. package/dist/{analyzers → core/analyzers}/pattern-detector.d.ts +2 -5
  8. package/dist/{analyzers → core/analyzers}/pattern-detector.js +134 -49
  9. package/dist/{analyzers → core/analyzers}/project-analyzer.d.ts +2 -0
  10. package/dist/{analyzers → core/analyzers}/project-analyzer.js +12 -9
  11. package/dist/core/config.d.ts +19 -0
  12. package/dist/{config.js → core/config.js} +79 -91
  13. package/dist/{builders → core}/context-builder.d.ts +4 -11
  14. package/dist/core/context-builder.js +225 -0
  15. package/dist/{generator.d.ts → core/generator.d.ts} +0 -3
  16. package/dist/{generator.js → core/generator.js} +12 -17
  17. package/dist/core/types/analyzer-types.d.ts +38 -0
  18. package/dist/core/types/analyzer-types.js +2 -0
  19. package/dist/core/types/config-types.d.ts +36 -0
  20. package/dist/core/types/config-types.js +2 -0
  21. package/dist/core/types/generator-types.d.ts +6 -0
  22. package/dist/core/types/generator-types.js +2 -0
  23. package/dist/core/types/index.d.ts +4 -0
  24. package/dist/core/types/index.js +5 -0
  25. package/dist/core/types/project-types.d.ts +30 -0
  26. package/dist/core/types/project-types.js +2 -0
  27. package/dist/core/types.d.ts +1 -0
  28. package/dist/core/types.js +2 -0
  29. package/dist/index.d.ts +3 -4
  30. package/dist/index.js +2 -2
  31. package/dist/utils/file-collector.d.ts +23 -0
  32. package/dist/utils/file-collector.js +61 -0
  33. package/dist/utils/file-filter.d.ts +30 -0
  34. package/dist/utils/file-filter.js +99 -0
  35. package/dist/{analyzers/git-analyzer.d.ts → utils/git.d.ts} +1 -4
  36. package/dist/{analyzers/git-analyzer.js → utils/git.js} +0 -4
  37. package/dist/{prompt.d.ts → utils/prompt.d.ts} +0 -1
  38. package/dist/{prompt.js → utils/prompt.js} +0 -9
  39. package/package.json +12 -8
  40. package/dist/analyzers/framework-detector.d.ts +0 -12
  41. package/dist/analyzers/framework-detector.js +0 -180
  42. package/dist/builders/context-builder.js +0 -386
  43. package/dist/cli.js +0 -145
  44. package/dist/config.d.ts +0 -44
  45. package/dist/types.d.ts +0 -84
  46. package/dist/types.js +0 -2
  47. /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
@@ -1,180 +0,0 @@
1
- export class FrameworkDetector {
2
- config;
3
- constructor(config) {
4
- this.config = config;
5
- }
6
- async detect(projectInfo) {
7
- const deps = { ...projectInfo.dependencies, ...projectInfo.devDependencies };
8
- // Check for frameworks in dependencies
9
- if (deps['@nestjs/core'] || deps['@nestjs/common']) {
10
- return this.detectNestJS(deps);
11
- }
12
- if (deps['fastify']) {
13
- return this.detectFastify(deps);
14
- }
15
- if (deps['express']) {
16
- return this.detectExpress(deps);
17
- }
18
- if (deps['koa']) {
19
- return this.detectKoa(deps);
20
- }
21
- if (deps['@hapi/hapi']) {
22
- return this.detectHapi(deps);
23
- }
24
- return {
25
- name: 'unknown',
26
- features: [],
27
- };
28
- }
29
- detectExpress(deps) {
30
- const features = [];
31
- // Detect common Express features
32
- if (deps['express-session']) {
33
- features.push('sessions');
34
- }
35
- if (deps['passport']) {
36
- features.push('authentication');
37
- }
38
- if (deps['express-rate-limit']) {
39
- features.push('rate-limiting');
40
- }
41
- if (deps['helmet']) {
42
- features.push('security');
43
- }
44
- if (deps['cors']) {
45
- features.push('cors');
46
- }
47
- if (deps['body-parser']) {
48
- features.push('body-parsing');
49
- }
50
- if (deps['express-validator']) {
51
- features.push('validation');
52
- }
53
- if (deps['multer']) {
54
- features.push('file-upload');
55
- }
56
- if (deps['express-static']) {
57
- features.push('static-files');
58
- }
59
- return {
60
- name: 'express',
61
- version: deps['express'],
62
- features,
63
- };
64
- }
65
- detectFastify(deps) {
66
- const features = [];
67
- if (deps['@fastify/cors']) {
68
- features.push('cors');
69
- }
70
- if (deps['@fastify/helmet']) {
71
- features.push('security');
72
- }
73
- if (deps['@fastify/rate-limit']) {
74
- features.push('rate-limiting');
75
- }
76
- if (deps['@fastify/multipart']) {
77
- features.push('file-upload');
78
- }
79
- if (deps['@fastify/static']) {
80
- features.push('static-files');
81
- }
82
- if (deps['@fastify/jwt']) {
83
- features.push('jwt');
84
- }
85
- if (deps['@fastify/session']) {
86
- features.push('sessions');
87
- }
88
- return {
89
- name: 'fastify',
90
- version: deps['fastify'],
91
- features,
92
- };
93
- }
94
- detectNestJS(deps) {
95
- const features = [];
96
- if (deps['@nestjs/typeorm'] || deps['@nestjs/mongoose']) {
97
- features.push('orm');
98
- }
99
- if (deps['@nestjs/passport']) {
100
- features.push('authentication');
101
- }
102
- if (deps['@nestjs/jwt']) {
103
- features.push('jwt');
104
- }
105
- if (deps['@nestjs/swagger']) {
106
- features.push('swagger');
107
- }
108
- if (deps['@nestjs/graphql']) {
109
- features.push('graphql');
110
- }
111
- if (deps['@nestjs/websockets']) {
112
- features.push('websockets');
113
- }
114
- if (deps['@nestjs/microservices']) {
115
- features.push('microservices');
116
- }
117
- if (deps['@nestjs/testing']) {
118
- features.push('testing');
119
- }
120
- // NestJS uses decorators by default
121
- features.push('decorators', 'dependency-injection', 'modules');
122
- return {
123
- name: 'nestjs',
124
- version: deps['@nestjs/core'],
125
- features,
126
- };
127
- }
128
- detectKoa(deps) {
129
- const features = [];
130
- if (deps['@koa/cors']) {
131
- features.push('cors');
132
- }
133
- if (deps['koa-helmet']) {
134
- features.push('security');
135
- }
136
- if (deps['koa-ratelimit']) {
137
- features.push('rate-limiting');
138
- }
139
- if (deps['koa-multer']) {
140
- features.push('file-upload');
141
- }
142
- if (deps['koa-static']) {
143
- features.push('static-files');
144
- }
145
- if (deps['koa-session']) {
146
- features.push('sessions');
147
- }
148
- if (deps['koa-bodyparser']) {
149
- features.push('body-parsing');
150
- }
151
- return {
152
- name: 'koa',
153
- version: deps['koa'],
154
- features,
155
- };
156
- }
157
- detectHapi(deps) {
158
- const features = [];
159
- if (deps['@hapi/cookie']) {
160
- features.push('sessions');
161
- }
162
- if (deps['@hapi/jwt']) {
163
- features.push('jwt');
164
- }
165
- if (deps['@hapi/inert']) {
166
- features.push('static-files');
167
- }
168
- if (deps['@hapi/vision']) {
169
- features.push('templates');
170
- }
171
- if (deps['@hapi/boom']) {
172
- features.push('error-handling');
173
- }
174
- return {
175
- name: 'hapi',
176
- version: deps['@hapi/hapi'],
177
- features,
178
- };
179
- }
180
- }
@@ -1,386 +0,0 @@
1
- import { relative, dirname } from 'path';
2
- export class ContextBuilder {
3
- config;
4
- constructor(config) {
5
- this.config = config;
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
- build(context) {
76
- return this.buildMultiFile(context);
77
- }
78
- buildMultiFile(context) {
79
- const { projectInfo, fileAnalyses, gitInfo, contextDir, outputPath } = context;
80
- const limits = this.config.get().limits;
81
- // Limit number of files analyzed if specified
82
- const limitedFileAnalyses = limits?.maxFilesPerContext
83
- ? fileAnalyses.slice(0, limits.maxFilesPerContext)
84
- : fileAnalyses;
85
- // Calculate the relative path for the link in markdown
86
- let linkPath = 'parseme-context';
87
- if (contextDir && outputPath) {
88
- const outputDir = dirname(outputPath);
89
- if (contextDir.startsWith('/')) {
90
- // Absolute path: calculate relative path from output file to context dir
91
- linkPath = relative(outputDir, contextDir);
92
- }
93
- else {
94
- // Relative path: use as-is since it's relative to the output file's directory
95
- linkPath = contextDir;
96
- }
97
- }
98
- else if (contextDir) {
99
- // Fallback: use contextDir as-is if no outputPath provided
100
- linkPath = contextDir;
101
- }
102
- const mainContent = this.buildHeader(linkPath) +
103
- '\n\n\n' +
104
- this.buildProjectOverview(projectInfo) +
105
- '\n\n\n' +
106
- this.buildSummarySection(context, linkPath) +
107
- '\n\n\n' +
108
- (gitInfo ? this.buildGitSection(gitInfo) : '');
109
- const contextFiles = {
110
- structure: '',
111
- routes: '',
112
- dependencies: '',
113
- };
114
- // Helper function to merge split files into contextFiles
115
- const mergeSplitFiles = (result, baseName) => {
116
- if (typeof result === 'string') {
117
- contextFiles[baseName] = result;
118
- }
119
- else {
120
- // Merge split files into contextFiles
121
- Object.entries(result).forEach(([key, value]) => {
122
- contextFiles[key] = value;
123
- });
124
- }
125
- };
126
- // Files list (markdown)
127
- contextFiles.files = this.buildFilesList(limitedFileAnalyses);
128
- // Detailed structure (JSON with AST)
129
- const structureResult = this.buildDetailedStructure(limitedFileAnalyses);
130
- mergeSplitFiles(structureResult, 'structure');
131
- // Routes documentation
132
- const routes = limitedFileAnalyses.flatMap((f) => f.routes || []);
133
- if (routes.length > 0) {
134
- const routesResult = this.buildDetailedRoutes(routes, limitedFileAnalyses);
135
- mergeSplitFiles(routesResult, 'api-endpoints');
136
- }
137
- // Dependencies
138
- const dependenciesResult = this.buildDetailedDependencies(projectInfo);
139
- mergeSplitFiles(dependenciesResult, 'dependencies');
140
- // Framework details
141
- if (projectInfo.framework &&
142
- projectInfo.framework.name &&
143
- projectInfo.framework.name !== 'unknown') {
144
- contextFiles.framework = this.buildDetailedFramework(projectInfo.framework);
145
- }
146
- // Git information
147
- if (gitInfo) {
148
- contextFiles.gitDiff = this.buildDetailedGit(gitInfo);
149
- }
150
- return {
151
- parseme: mainContent,
152
- context: contextFiles,
153
- };
154
- }
155
- buildHeader(linkPath) {
156
- return `## PARSEME - AI Agent Context
157
- Auto-generated project summary optimized for AI coding agents. This file provides complete project context without requiring full codebase traversal, designed for token efficiency.
158
-
159
- **Usage Instructions for AI Agents:**
160
- 1. Read this PARSEME.md file completely first before accessing individual project files
161
- 2. Basic project information, script availability and dependency information provides basic understanding of code base and tech stack without checking package.json
162
- 3. Use the provided file list (${linkPath}/files.md) to see all tracked files in the project
163
- 4. Utilize the structure and AST data (${linkPath}/structure.json) for code analysis without manual parsing
164
- 5. Follow the instruction in the "Git Information" section of this file to validate the actuality of the provided information.
165
- 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.`;
166
- }
167
- buildProjectOverview(projectInfo) {
168
- let content = `## Basic Project Information
169
-
170
- **Project:** ${projectInfo.name}${projectInfo.version ? ` v${projectInfo.version}` : ''}
171
- **Description:** ${projectInfo.description || 'No description available.'}
172
- **Type:** ${projectInfo.type} project
173
- **Package Manager:** ${projectInfo.packageManager}
174
- **Framework:** ${projectInfo.framework?.name || 'None detected'}`;
175
- // Add main entry point if available
176
- if (projectInfo.entryPoints && projectInfo.entryPoints.length > 0) {
177
- content += `\n**Main Entry Point:** ${projectInfo.entryPoints[0]}`;
178
- }
179
- content += '\n';
180
- // Add dependencies
181
- const deps = Object.keys(projectInfo.dependencies);
182
- if (deps.length > 0) {
183
- content += '\n\n### Dependencies\n';
184
- deps.forEach((dep) => {
185
- content += `- ${dep}\n`;
186
- });
187
- }
188
- // Add available scripts
189
- if (projectInfo.scripts && Object.keys(projectInfo.scripts).length > 0) {
190
- content += '\n### Available Scripts\n';
191
- Object.entries(projectInfo.scripts).forEach(([name, script]) => {
192
- content += `- **${name}**: \`${script}\`\n`;
193
- });
194
- }
195
- return content;
196
- }
197
- buildFrameworkSection(framework) {
198
- if (!framework) {
199
- return '';
200
- }
201
- const features = framework.features.length > 0 ? `\n- **Features**: ${framework.features.join(', ')}` : '';
202
- return `## Framework: ${framework.name}
203
-
204
- - **Version**: ${framework.version || 'Unknown'}${features}`;
205
- }
206
- buildArchitectureSection(fileAnalyses) {
207
- const typeGroups = fileAnalyses.reduce((acc, file) => {
208
- if (!acc[file.type]) {
209
- acc[file.type] = [];
210
- }
211
- acc[file.type].push(file);
212
- return acc;
213
- }, {});
214
- const archLines = Object.entries(typeGroups)
215
- .map(([type, files]) => `- **${type}**: ${files.length} files`)
216
- .join('\n');
217
- return `## Architecture Overview
218
-
219
- ${archLines}`;
220
- }
221
- buildRoutesSection(routes, _fileAnalyses) {
222
- const routesByMethod = routes.reduce((acc, route) => {
223
- if (!acc[route.method]) {
224
- acc[route.method] = [];
225
- }
226
- acc[route.method].push(route);
227
- return acc;
228
- }, {});
229
- const routeLines = Object.entries(routesByMethod)
230
- .map(([method, methodRoutes]) => {
231
- const routeList = methodRoutes
232
- .map((route) => ` - \`${route.path}\` → ${route.handler}`)
233
- .join('\n');
234
- return `- **${method}**:\n${routeList}`;
235
- })
236
- .join('\n');
237
- return `## API Endpoints
238
-
239
- ${routeLines}`;
240
- }
241
- buildFileStructureSection(fileAnalyses) {
242
- const structure = fileAnalyses.map((file) => `- \`${file.path}\` (${file.type})`).join('\n');
243
- return `## File Structure
244
-
245
- ${structure}`;
246
- }
247
- buildDependenciesSection(projectInfo) {
248
- const deps = Object.keys(projectInfo.dependencies);
249
- const devDeps = Object.keys(projectInfo.devDependencies);
250
- let content = '## Dependencies\n\n';
251
- if (deps.length > 0) {
252
- content += `**Production**: ${deps.join(', ')}\n\n`;
253
- }
254
- if (devDeps.length > 0) {
255
- content += `**Development**: ${devDeps.join(', ')}`;
256
- }
257
- return content;
258
- }
259
- buildGitSection(gitInfo) {
260
- return `## Git Information
261
-
262
- **State when PARSEME.md and all linked files were automatically generated:**
263
-
264
- - **Branch:** ${gitInfo.branch}
265
- - **Commit:** ${gitInfo.lastCommit}${gitInfo.origin ? `\n- **Origin:** ${gitInfo.origin}` : ''}
266
-
267
- ### Git Diff Statistics
268
- Git diff statistics from the time of generation are available at \`parseme-context/gitDiff.md\`.
269
-
270
- **AI Agent Command:** To check for changes since generation, run:
271
- \`\`\`bash
272
- git diff --stat
273
- \`\`\`
274
- Compare the output with the baseline in \`parseme-context/gitDiff.json\` to detect any modifications.`;
275
- }
276
- buildFooter() {
277
- return `---
278
-
279
- *Generated by PARSEME v1.0.0 on ${new Date().toISOString().split('T')[0]}*`;
280
- }
281
- buildSummarySection(context, linkPath) {
282
- const { fileAnalyses } = context;
283
- const routes = fileAnalyses.flatMap((f) => f.routes || []).length;
284
- let content = `## Project Files
285
- A complete list of all tracked files in the project is available at \`${linkPath}/files.md\`. This list excludes files ignored by git and provides a quick overview of the project structure.
286
-
287
-
288
- ## Project Structure & AST
289
- Detailed structure and Abstract Syntax Tree data for all tracked files is available at \`${linkPath}/structure.json\`. This includes file paths, types, imports, exports, functions, classes, interfaces, and routes for comprehensive code analysis without manual parsing.`;
290
- if (routes > 0) {
291
- content += `\n\n\n## API Endpoints
292
- A comprehensive list of all discovered API endpoints is available at \`${linkPath}/api-endpoints.json\`. This includes HTTP methods, paths, handler names, and source file locations for backend routes (Express, NestJS, and decorator-based routing).`;
293
- }
294
- return content;
295
- }
296
- buildFilesList(fileAnalyses) {
297
- let content = `# Project Files\n\n`;
298
- content += `This is a complete list of all analyzed files in the project.\n\n`;
299
- fileAnalyses.forEach((file) => {
300
- content += `- ${file.path}\n`;
301
- });
302
- return content;
303
- }
304
- buildDetailedStructure(fileAnalyses) {
305
- const structureData = fileAnalyses.map((file) => ({
306
- path: file.path,
307
- type: file.type,
308
- exports: file.exports,
309
- imports: file.imports,
310
- functions: file.functions,
311
- classes: file.classes,
312
- routes: file.routes || [],
313
- }));
314
- const jsonContent = JSON.stringify(structureData, null, 2);
315
- const result = this.truncateContent(jsonContent);
316
- if (Array.isArray(result)) {
317
- // Return split files as a record with numbered keys
318
- const splitFiles = {};
319
- result.forEach((part, index) => {
320
- const suffix = index === 0 ? '' : `_part${index + 1}`;
321
- splitFiles[`structure${suffix}`] = part;
322
- });
323
- return splitFiles;
324
- }
325
- return result;
326
- }
327
- buildDetailedRoutes(routes, _fileAnalyses) {
328
- const jsonContent = JSON.stringify(routes, null, 2);
329
- const result = this.truncateContent(jsonContent);
330
- if (Array.isArray(result)) {
331
- const splitFiles = {};
332
- result.forEach((part, index) => {
333
- const suffix = index === 0 ? '' : `_part${index + 1}`;
334
- splitFiles[`routes${suffix}`] = part;
335
- });
336
- return splitFiles;
337
- }
338
- return result;
339
- }
340
- buildDetailedDependencies(projectInfo) {
341
- const jsonContent = JSON.stringify({
342
- dependencies: projectInfo.dependencies,
343
- packageManager: projectInfo.packageManager,
344
- version: projectInfo.version,
345
- }, null, 2);
346
- const result = this.truncateContent(jsonContent);
347
- if (Array.isArray(result)) {
348
- const splitFiles = {};
349
- result.forEach((part, index) => {
350
- const suffix = index === 0 ? '' : `_part${index + 1}`;
351
- splitFiles[`dependencies${suffix}`] = part;
352
- });
353
- return splitFiles;
354
- }
355
- return result;
356
- }
357
- buildDetailedFramework(framework) {
358
- if (!framework) {
359
- return '';
360
- }
361
- let content = `# Framework: ${framework.name}\n\n`;
362
- content += `**Version**: ${framework.version || 'Unknown'}\n\n`;
363
- if (framework.features.length > 0) {
364
- content += '## Features Detected\n\n';
365
- framework.features.forEach((feature) => {
366
- content += `- ${feature}\n`;
367
- });
368
- }
369
- return content;
370
- }
371
- buildDetailedGit(gitInfo) {
372
- let content = `# Git Diff Statistics
373
- # Generated at time of PARSEME.md creation
374
- # To check for changes since then, run: git diff --stat
375
- # Compare the output with the content below
376
-
377
- `;
378
- if (gitInfo.diffStat && gitInfo.diffStat.length > 0) {
379
- content += gitInfo.diffStat;
380
- }
381
- else {
382
- content += 'No changes detected at time of generation.';
383
- }
384
- return content;
385
- }
386
- }