@parseme/cli 0.0.2 → 0.0.4
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 +163 -156
- package/dist/{cli.js → cli/cli.js} +59 -49
- package/dist/{prompt.d.ts → cli/prompt.d.ts} +0 -1
- package/dist/{prompt.js → cli/prompt.js} +0 -9
- package/dist/{analyzers → core/analyzers}/ast-analyzer.js +7 -6
- package/dist/{analyzers → core/analyzers}/framework-detector.d.ts +0 -3
- package/dist/{analyzers → core/analyzers}/framework-detector.js +0 -4
- package/dist/core/analyzers/git-analyzer.d.ts +10 -0
- package/dist/{analyzers → core/analyzers}/git-analyzer.js +12 -16
- package/dist/{analyzers → core/analyzers}/pattern-detector.d.ts +0 -3
- package/dist/{analyzers → core/analyzers}/pattern-detector.js +52 -47
- package/dist/{analyzers → core/analyzers}/project-analyzer.d.ts +2 -0
- package/dist/{analyzers → core/analyzers}/project-analyzer.js +19 -5
- package/dist/core/config.d.ts +20 -0
- package/dist/{config.js → core/config.js} +80 -67
- package/dist/{builders → core}/context-builder.d.ts +5 -9
- package/dist/{builders → core}/context-builder.js +103 -126
- package/dist/{generator.d.ts → core/generator.d.ts} +0 -3
- package/dist/{generator.js → core/generator.js} +12 -15
- package/dist/core/types/analyzer-types.d.ts +39 -0
- package/dist/core/types/analyzer-types.js +2 -0
- package/dist/{config.d.ts → core/types/config-types.d.ts} +8 -15
- package/dist/core/types/config-types.js +2 -0
- package/dist/core/types/generator-types.d.ts +8 -0
- package/dist/core/types/generator-types.js +2 -0
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/project-types.d.ts +30 -0
- package/dist/core/types/project-types.js +2 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +2 -2
- package/package.json +3 -4
- package/dist/analyzers/git-analyzer.d.ts +0 -13
- package/dist/types.d.ts +0 -82
- package/dist/types.js +0 -2
- /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
- /package/dist/{analyzers → core/analyzers}/ast-analyzer.d.ts +0 -0
|
@@ -3,28 +3,35 @@ import { readFile, writeFile } from 'fs/promises';
|
|
|
3
3
|
import { join, extname } from 'path';
|
|
4
4
|
export class ParsemeConfig {
|
|
5
5
|
config;
|
|
6
|
+
userConfig;
|
|
6
7
|
constructor(config = {}) {
|
|
8
|
+
this.userConfig = { ...config }; // Store original user config
|
|
7
9
|
this.config = this.mergeWithDefaults(config);
|
|
8
10
|
}
|
|
9
|
-
static async fromFileWithOptions(configPath, cliOptions = {}) {
|
|
10
|
-
const configFromFile = await ParsemeConfig.fromFile(configPath);
|
|
11
|
+
static async fromFileWithOptions(configPath, cliOptions = {}, options = { showWarnings: true }) {
|
|
12
|
+
const configFromFile = await ParsemeConfig.fromFile(configPath, options);
|
|
11
13
|
const mergedConfig = {
|
|
12
14
|
...configFromFile.get(),
|
|
13
15
|
...cliOptions, // CLI options take priority
|
|
14
16
|
};
|
|
15
17
|
return new ParsemeConfig(mergedConfig);
|
|
16
18
|
}
|
|
17
|
-
static async fromFile(configPath
|
|
19
|
+
static async fromFile(configPath, options = {
|
|
20
|
+
showWarnings: true,
|
|
21
|
+
throwOnNotFound: false,
|
|
22
|
+
}) {
|
|
18
23
|
const defaultPaths = [
|
|
19
|
-
'parseme.config.ts',
|
|
20
|
-
'parseme.config.js',
|
|
21
24
|
'parseme.config.json',
|
|
22
|
-
'.parsemerc.ts',
|
|
23
|
-
'.parsemerc.js',
|
|
24
25
|
'.parsemerc.json',
|
|
25
26
|
'.parsemerc',
|
|
27
|
+
'parseme.config.ts',
|
|
28
|
+
'.parsemerc.ts',
|
|
29
|
+
'parseme.config.js',
|
|
30
|
+
'.parsemerc.js',
|
|
26
31
|
];
|
|
27
32
|
const paths = configPath ? [configPath] : defaultPaths;
|
|
33
|
+
let tsWarning = null;
|
|
34
|
+
let configLoadError = null;
|
|
28
35
|
for (const path of paths) {
|
|
29
36
|
try {
|
|
30
37
|
const ext = extname(path);
|
|
@@ -32,17 +39,18 @@ export class ParsemeConfig {
|
|
|
32
39
|
// Dynamic import for JS/TS config files
|
|
33
40
|
const fullPath = path.startsWith('/') ? path : join(process.cwd(), path);
|
|
34
41
|
if (ext === '.ts') {
|
|
35
|
-
// For TypeScript files,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
// For TypeScript files, check if file exists first
|
|
43
|
+
if (existsSync(fullPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const module = await import(fullPath);
|
|
46
|
+
const config = module.default || module;
|
|
47
|
+
return new ParsemeConfig(config);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// File exists but can't be loaded - save warning
|
|
51
|
+
tsWarning = path;
|
|
52
|
+
configLoadError = { path, error: error };
|
|
53
|
+
}
|
|
46
54
|
}
|
|
47
55
|
}
|
|
48
56
|
else {
|
|
@@ -54,20 +62,56 @@ export class ParsemeConfig {
|
|
|
54
62
|
}
|
|
55
63
|
else {
|
|
56
64
|
// JSON config files
|
|
57
|
-
const
|
|
65
|
+
const fullPath = path.startsWith('/') ? path : join(process.cwd(), path);
|
|
66
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
58
67
|
const config = JSON.parse(content);
|
|
59
68
|
return new ParsemeConfig(config);
|
|
60
69
|
}
|
|
61
70
|
}
|
|
62
|
-
catch {
|
|
71
|
+
catch (error) {
|
|
72
|
+
// If file exists, save the error
|
|
73
|
+
const fullPath = path.startsWith('/') ? path : join(process.cwd(), path);
|
|
74
|
+
if (existsSync(fullPath)) {
|
|
75
|
+
configLoadError = { path, error: error };
|
|
76
|
+
}
|
|
63
77
|
// Continue to next path
|
|
64
78
|
}
|
|
65
79
|
}
|
|
80
|
+
// Handle case when config file was found but couldn't be loaded
|
|
81
|
+
if (configLoadError) {
|
|
82
|
+
const { path, error } = configLoadError;
|
|
83
|
+
if (options.throwOnNotFound) {
|
|
84
|
+
throw new Error(`Configuration file "${path}" found but failed to load: ${error.message}`);
|
|
85
|
+
}
|
|
86
|
+
if (options.showWarnings) {
|
|
87
|
+
console.warn(`Configuration file "${path}" found but failed to load: ${error.message}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Handle case when no config found at all
|
|
92
|
+
if (options.throwOnNotFound) {
|
|
93
|
+
throw new Error('No configuration file found. Run "parseme init" to create one.');
|
|
94
|
+
}
|
|
95
|
+
if (options.showWarnings) {
|
|
96
|
+
console.warn('No configuration file found. Run "parseme init" to create one.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (tsWarning && !configLoadError && options.showWarnings) {
|
|
100
|
+
console.warn(`Could not load TypeScript config file: ${tsWarning}`);
|
|
101
|
+
console.warn(`Consider using a .js config file or ensure tsx/ts-node is available`);
|
|
102
|
+
}
|
|
66
103
|
// Return default config if no file found
|
|
67
104
|
return new ParsemeConfig();
|
|
68
105
|
}
|
|
69
106
|
mergeWithDefaults(config) {
|
|
70
107
|
const rootDir = config.rootDir || process.cwd();
|
|
108
|
+
const supportedFileTypes = ['ts', 'tsx', 'js', 'jsx'];
|
|
109
|
+
// Validate analyzeFileTypes
|
|
110
|
+
const fileTypes = config.analyzeFileTypes || ['ts', 'tsx', 'js', 'jsx'];
|
|
111
|
+
const invalidTypes = fileTypes.filter((type) => !supportedFileTypes.includes(type));
|
|
112
|
+
if (invalidTypes.length > 0) {
|
|
113
|
+
throw new Error(`Invalid file types: ${invalidTypes.join(', ')}. Supported types are: ${supportedFileTypes.join(', ')}`);
|
|
114
|
+
}
|
|
71
115
|
return {
|
|
72
116
|
// Output
|
|
73
117
|
outputPath: config.outputPath || 'PARSEME.md',
|
|
@@ -76,17 +120,7 @@ export class ParsemeConfig {
|
|
|
76
120
|
rootDir,
|
|
77
121
|
maxDepth: config.maxDepth || 10,
|
|
78
122
|
excludePatterns: this.mergeExcludePatterns(config.excludePatterns, rootDir),
|
|
79
|
-
|
|
80
|
-
'src/**/*.ts',
|
|
81
|
-
'src/**/*.js',
|
|
82
|
-
'src/**/*.tsx',
|
|
83
|
-
'src/**/*.jsx',
|
|
84
|
-
'lib/**/*.ts',
|
|
85
|
-
'lib/**/*.js',
|
|
86
|
-
'package.json',
|
|
87
|
-
'tsconfig.json',
|
|
88
|
-
'README.md',
|
|
89
|
-
],
|
|
123
|
+
analyzeFileTypes: fileTypes,
|
|
90
124
|
// Git
|
|
91
125
|
includeGitInfo: config.includeGitInfo ?? true,
|
|
92
126
|
// Sections
|
|
@@ -132,47 +166,36 @@ export class ParsemeConfig {
|
|
|
132
166
|
await writeFile(path, configContent);
|
|
133
167
|
}
|
|
134
168
|
else {
|
|
135
|
-
// Generate JSON config file
|
|
136
|
-
await writeFile(path, JSON.stringify(this.
|
|
169
|
+
// Generate JSON config file - only save user config, not defaults
|
|
170
|
+
await writeFile(path, JSON.stringify(this.userConfig, null, 2));
|
|
137
171
|
}
|
|
138
172
|
}
|
|
139
173
|
generateJSConfig() {
|
|
174
|
+
// Only export user-specified config, not defaults
|
|
140
175
|
return `/** @type {import('parseme').ParsemeConfigFile} */
|
|
141
|
-
|
|
176
|
+
const config = ${JSON.stringify(this.userConfig, null, 2)};
|
|
177
|
+
|
|
178
|
+
export default config;
|
|
142
179
|
`;
|
|
143
180
|
}
|
|
144
181
|
generateTSConfig() {
|
|
182
|
+
// Only export user-specified config, not defaults
|
|
145
183
|
return `import type { ParsemeConfigFile } from 'parseme';
|
|
146
184
|
|
|
147
|
-
const config: ParsemeConfigFile = ${JSON.stringify(this.
|
|
185
|
+
const config: ParsemeConfigFile = ${JSON.stringify(this.userConfig, null, 2)};
|
|
148
186
|
|
|
149
187
|
export default config;
|
|
150
188
|
`;
|
|
151
189
|
}
|
|
152
190
|
mergeExcludePatterns(configPatterns, rootDir) {
|
|
153
|
-
|
|
154
|
-
'node_modules/**',
|
|
155
|
-
'dist/**',
|
|
156
|
-
'build/**',
|
|
157
|
-
'coverage/**',
|
|
158
|
-
'.git/**',
|
|
159
|
-
'**/*.log',
|
|
160
|
-
'**/*.tmp',
|
|
161
|
-
'**/.DS_Store',
|
|
162
|
-
'**/.*',
|
|
163
|
-
];
|
|
164
|
-
// Priority: Config patterns > .gitignore patterns > Default patterns
|
|
165
|
-
if (configPatterns) {
|
|
166
|
-
return configPatterns;
|
|
167
|
-
}
|
|
168
|
-
// Try to read .gitignore patterns
|
|
191
|
+
// Always read .gitignore patterns
|
|
169
192
|
const gitignorePatterns = this.readGitignorePatterns(rootDir);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
193
|
+
// Merge gitignore patterns with config patterns, they are added to gitignore patterns, not replacing them
|
|
194
|
+
const excludePatterns = [...gitignorePatterns];
|
|
195
|
+
if (configPatterns) {
|
|
196
|
+
excludePatterns.push(...configPatterns);
|
|
174
197
|
}
|
|
175
|
-
return
|
|
198
|
+
return excludePatterns;
|
|
176
199
|
}
|
|
177
200
|
readGitignorePatterns(rootDir) {
|
|
178
201
|
try {
|
|
@@ -184,17 +207,7 @@ export default config;
|
|
|
184
207
|
return gitignoreContent
|
|
185
208
|
.split('\n')
|
|
186
209
|
.map((line) => line.trim())
|
|
187
|
-
.filter((line) => line && !line.startsWith('#'))
|
|
188
|
-
.map((pattern) => {
|
|
189
|
-
// Convert gitignore patterns to glob patterns
|
|
190
|
-
if (pattern.endsWith('/')) {
|
|
191
|
-
return pattern + '**';
|
|
192
|
-
}
|
|
193
|
-
if (!pattern.includes('/') && !pattern.includes('*')) {
|
|
194
|
-
return '**/' + pattern;
|
|
195
|
-
}
|
|
196
|
-
return pattern;
|
|
197
|
-
});
|
|
210
|
+
.filter((line) => line && !line.startsWith('#'));
|
|
198
211
|
}
|
|
199
212
|
catch {
|
|
200
213
|
return [];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { ParsemeConfig } from '
|
|
2
|
-
import type { ContextOutput, ProjectInfo, FileAnalysis, GitInfo, GeneratorOptions } from '
|
|
1
|
+
import type { ParsemeConfig } from './config.js';
|
|
2
|
+
import type { ContextOutput, ProjectInfo, FileAnalysis, GitInfo, GeneratorOptions } from './types.js';
|
|
3
3
|
interface BuildContext {
|
|
4
4
|
projectInfo: ProjectInfo;
|
|
5
5
|
fileAnalyses: FileAnalysis[];
|
|
6
|
-
|
|
6
|
+
allFiles: string[];
|
|
7
|
+
gitInfo?: GitInfo | null;
|
|
7
8
|
options: GeneratorOptions;
|
|
8
9
|
contextDir?: string;
|
|
9
10
|
outputPath?: string;
|
|
@@ -18,14 +19,9 @@ export declare class ContextBuilder {
|
|
|
18
19
|
private buildMultiFile;
|
|
19
20
|
private buildHeader;
|
|
20
21
|
private buildProjectOverview;
|
|
21
|
-
private buildFrameworkSection;
|
|
22
|
-
private buildArchitectureSection;
|
|
23
|
-
private buildRoutesSection;
|
|
24
|
-
private buildFileStructureSection;
|
|
25
|
-
private buildDependenciesSection;
|
|
26
22
|
private buildGitSection;
|
|
27
|
-
private buildFooter;
|
|
28
23
|
private buildSummarySection;
|
|
24
|
+
private buildFilesList;
|
|
29
25
|
private buildDetailedStructure;
|
|
30
26
|
private buildDetailedRoutes;
|
|
31
27
|
private buildDetailedDependencies;
|
|
@@ -76,21 +76,39 @@ export class ContextBuilder {
|
|
|
76
76
|
return this.buildMultiFile(context);
|
|
77
77
|
}
|
|
78
78
|
buildMultiFile(context) {
|
|
79
|
-
const { projectInfo, fileAnalyses, gitInfo } = context;
|
|
79
|
+
const { projectInfo, fileAnalyses, gitInfo, contextDir, outputPath } = context;
|
|
80
80
|
const limits = this.config.get().limits;
|
|
81
81
|
// Limit number of files analyzed if specified
|
|
82
82
|
const limitedFileAnalyses = limits?.maxFilesPerContext
|
|
83
83
|
? fileAnalyses.slice(0, limits.maxFilesPerContext)
|
|
84
84
|
: fileAnalyses;
|
|
85
|
-
|
|
86
|
-
|
|
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' +
|
|
87
104
|
this.buildProjectOverview(projectInfo) +
|
|
88
|
-
'\n\n' +
|
|
89
|
-
this.buildSummarySection(context)
|
|
105
|
+
'\n\n\n' +
|
|
106
|
+
this.buildSummarySection(context, linkPath) +
|
|
107
|
+
'\n\n\n' +
|
|
108
|
+
(gitInfo ? this.buildGitSection(gitInfo) : '');
|
|
90
109
|
const contextFiles = {
|
|
91
110
|
structure: '',
|
|
92
111
|
routes: '',
|
|
93
|
-
dependencies: '',
|
|
94
112
|
};
|
|
95
113
|
// Helper function to merge split files into contextFiles
|
|
96
114
|
const mergeSplitFiles = (result, baseName) => {
|
|
@@ -104,150 +122,107 @@ export class ContextBuilder {
|
|
|
104
122
|
});
|
|
105
123
|
}
|
|
106
124
|
};
|
|
107
|
-
//
|
|
125
|
+
// Files list (markdown) - all files in project, not just analyzed ones
|
|
126
|
+
contextFiles.files = this.buildFilesList(context.allFiles);
|
|
127
|
+
// Detailed structure (JSON with AST)
|
|
108
128
|
const structureResult = this.buildDetailedStructure(limitedFileAnalyses);
|
|
109
129
|
mergeSplitFiles(structureResult, 'structure');
|
|
110
130
|
// Routes documentation
|
|
111
131
|
const routes = limitedFileAnalyses.flatMap((f) => f.routes || []);
|
|
112
132
|
if (routes.length > 0) {
|
|
113
133
|
const routesResult = this.buildDetailedRoutes(routes, limitedFileAnalyses);
|
|
114
|
-
mergeSplitFiles(routesResult, '
|
|
115
|
-
}
|
|
116
|
-
// Dependencies
|
|
117
|
-
const dependenciesResult = this.buildDetailedDependencies(projectInfo);
|
|
118
|
-
mergeSplitFiles(dependenciesResult, 'dependencies');
|
|
119
|
-
// Framework details
|
|
120
|
-
if (projectInfo.framework &&
|
|
121
|
-
projectInfo.framework.name &&
|
|
122
|
-
projectInfo.framework.name !== 'unknown') {
|
|
123
|
-
contextFiles.framework = this.buildDetailedFramework(projectInfo.framework);
|
|
134
|
+
mergeSplitFiles(routesResult, 'api-endpoints');
|
|
124
135
|
}
|
|
125
136
|
// Git information
|
|
126
137
|
if (gitInfo) {
|
|
127
|
-
|
|
128
|
-
mergeSplitFiles(gitResult, 'git');
|
|
138
|
+
contextFiles.gitDiff = this.buildDetailedGit(gitInfo);
|
|
129
139
|
}
|
|
130
140
|
return {
|
|
131
141
|
parseme: mainContent,
|
|
132
142
|
context: contextFiles,
|
|
133
143
|
};
|
|
134
144
|
}
|
|
135
|
-
buildHeader(
|
|
136
|
-
return
|
|
145
|
+
buildHeader(linkPath) {
|
|
146
|
+
return `## PARSEME - AI Agent Context
|
|
147
|
+
Auto-generated project summary optimized for AI coding agents. This file provides complete project context without requiring full codebase traversal, designed for token efficiency.
|
|
137
148
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
149
|
+
**Usage Instructions for AI Agents:**
|
|
150
|
+
1. Read this PARSEME.md file completely first before accessing individual project files
|
|
151
|
+
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
|
+
5. Follow the instruction in the "Git Information" section of this file to validate the actuality of the provided information.
|
|
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.`;
|
|
141
156
|
}
|
|
142
157
|
buildProjectOverview(projectInfo) {
|
|
143
|
-
|
|
158
|
+
let content = `## Basic Project Information
|
|
144
159
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (
|
|
152
|
-
|
|
160
|
+
**Project:** ${projectInfo.name}${projectInfo.version ? ` v${projectInfo.version}` : ''}
|
|
161
|
+
**Description:** ${projectInfo.description || 'No description available.'}
|
|
162
|
+
**Type:** ${projectInfo.type} project
|
|
163
|
+
**Package Manager:** ${projectInfo.packageManager}
|
|
164
|
+
**Framework:** ${projectInfo.framework?.name || 'None detected'}`;
|
|
165
|
+
// Add main entry point if available
|
|
166
|
+
if (projectInfo.entryPoints && projectInfo.entryPoints.length > 0) {
|
|
167
|
+
content += `\n**Main Entry Point:** ${projectInfo.entryPoints[0]}`;
|
|
153
168
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
- **Version**: ${framework.version || 'Unknown'}${features}`;
|
|
158
|
-
}
|
|
159
|
-
buildArchitectureSection(fileAnalyses) {
|
|
160
|
-
const typeGroups = fileAnalyses.reduce((acc, file) => {
|
|
161
|
-
if (!acc[file.type]) {
|
|
162
|
-
acc[file.type] = [];
|
|
163
|
-
}
|
|
164
|
-
acc[file.type].push(file);
|
|
165
|
-
return acc;
|
|
166
|
-
}, {});
|
|
167
|
-
const archLines = Object.entries(typeGroups)
|
|
168
|
-
.map(([type, files]) => `- **${type}**: ${files.length} files`)
|
|
169
|
-
.join('\n');
|
|
170
|
-
return `## Architecture Overview
|
|
171
|
-
|
|
172
|
-
${archLines}`;
|
|
173
|
-
}
|
|
174
|
-
buildRoutesSection(routes, _fileAnalyses) {
|
|
175
|
-
const routesByMethod = routes.reduce((acc, route) => {
|
|
176
|
-
if (!acc[route.method]) {
|
|
177
|
-
acc[route.method] = [];
|
|
178
|
-
}
|
|
179
|
-
acc[route.method].push(route);
|
|
180
|
-
return acc;
|
|
181
|
-
}, {});
|
|
182
|
-
const routeLines = Object.entries(routesByMethod)
|
|
183
|
-
.map(([method, methodRoutes]) => {
|
|
184
|
-
const routeList = methodRoutes
|
|
185
|
-
.map((route) => ` - \`${route.path}\` → ${route.handler}`)
|
|
186
|
-
.join('\n');
|
|
187
|
-
return `- **${method}**:\n${routeList}`;
|
|
188
|
-
})
|
|
189
|
-
.join('\n');
|
|
190
|
-
return `## API Endpoints
|
|
191
|
-
|
|
192
|
-
${routeLines}`;
|
|
193
|
-
}
|
|
194
|
-
buildFileStructureSection(fileAnalyses) {
|
|
195
|
-
const structure = fileAnalyses.map((file) => `- \`${file.path}\` (${file.type})`).join('\n');
|
|
196
|
-
return `## File Structure
|
|
197
|
-
|
|
198
|
-
${structure}`;
|
|
199
|
-
}
|
|
200
|
-
buildDependenciesSection(projectInfo) {
|
|
169
|
+
content += '\n';
|
|
170
|
+
// Add dependencies
|
|
201
171
|
const deps = Object.keys(projectInfo.dependencies);
|
|
202
|
-
const devDeps = Object.keys(projectInfo.devDependencies);
|
|
203
|
-
let content = '## Dependencies\n\n';
|
|
204
172
|
if (deps.length > 0) {
|
|
205
|
-
content +=
|
|
173
|
+
content += '\n\n### Dependencies\n';
|
|
174
|
+
deps.forEach((dep) => {
|
|
175
|
+
content += `- ${dep}\n`;
|
|
176
|
+
});
|
|
206
177
|
}
|
|
207
|
-
|
|
208
|
-
|
|
178
|
+
// Add available scripts
|
|
179
|
+
if (projectInfo.scripts && Object.keys(projectInfo.scripts).length > 0) {
|
|
180
|
+
content += '\n### Available Scripts\n';
|
|
181
|
+
Object.entries(projectInfo.scripts).forEach(([name, script]) => {
|
|
182
|
+
content += `- **${name}**: \`${script}\`\n`;
|
|
183
|
+
});
|
|
209
184
|
}
|
|
210
185
|
return content;
|
|
211
186
|
}
|
|
212
187
|
buildGitSection(gitInfo) {
|
|
213
188
|
return `## Git Information
|
|
214
189
|
|
|
215
|
-
|
|
216
|
-
- **Status**: ${gitInfo.status}
|
|
217
|
-
- **Last Commit**: ${gitInfo.lastCommit}
|
|
218
|
-
${gitInfo.changedFiles.length > 0 ? `- **Changed Files**: ${gitInfo.changedFiles.join(', ')}` : ''}`;
|
|
219
|
-
}
|
|
220
|
-
buildFooter() {
|
|
221
|
-
return `---
|
|
190
|
+
**State when PARSEME.md and all linked files were automatically generated:**
|
|
222
191
|
|
|
223
|
-
|
|
192
|
+
- **Branch:** ${gitInfo.branch}
|
|
193
|
+
- **Commit:** ${gitInfo.lastCommit}${gitInfo.origin ? `\n- **Origin:** ${gitInfo.origin}` : ''}
|
|
194
|
+
|
|
195
|
+
### Git Diff Statistics
|
|
196
|
+
Git diff statistics from the time of generation are available at \`parseme-context/gitDiff.md\`.
|
|
197
|
+
|
|
198
|
+
**AI Agent Command:** To check for changes since generation, run:
|
|
199
|
+
\`\`\`bash
|
|
200
|
+
git diff --stat
|
|
201
|
+
\`\`\`
|
|
202
|
+
Compare the output with the baseline in \`parseme-context/gitDiff.md\` to detect any modifications.`;
|
|
224
203
|
}
|
|
225
|
-
buildSummarySection(context) {
|
|
226
|
-
const { fileAnalyses
|
|
227
|
-
const totalFiles = fileAnalyses.length;
|
|
204
|
+
buildSummarySection(context, linkPath) {
|
|
205
|
+
const { fileAnalyses } = context;
|
|
228
206
|
const routes = fileAnalyses.flatMap((f) => f.routes || []).length;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (contextDir && outputPath) {
|
|
232
|
-
const outputDir = dirname(outputPath);
|
|
233
|
-
if (contextDir.startsWith('/')) {
|
|
234
|
-
// Absolute path: calculate relative path from output file to context dir
|
|
235
|
-
linkPath = relative(outputDir, contextDir);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
// Relative path: use as-is since it's relative to the output file's directory
|
|
239
|
-
linkPath = contextDir;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
else if (contextDir) {
|
|
243
|
-
// Fallback: use contextDir as-is if no outputPath provided
|
|
244
|
-
linkPath = contextDir;
|
|
245
|
-
}
|
|
246
|
-
return `## Summary
|
|
207
|
+
let content = `## Project Files
|
|
208
|
+
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.
|
|
247
209
|
|
|
248
|
-
This project contains ${totalFiles} analyzed files${routes > 0 ? ` with ${routes} API endpoints` : ''}.
|
|
249
210
|
|
|
250
|
-
|
|
211
|
+
## Project Structure & AST
|
|
212
|
+
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.`;
|
|
213
|
+
if (routes > 0) {
|
|
214
|
+
content += `\n\n\n## API Endpoints
|
|
215
|
+
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).`;
|
|
216
|
+
}
|
|
217
|
+
return content;
|
|
218
|
+
}
|
|
219
|
+
buildFilesList(allFiles) {
|
|
220
|
+
let content = `# Project Files\n\n`;
|
|
221
|
+
content += `This is a complete list of all files in the project (excluding files matching exclude patterns).\n\n`;
|
|
222
|
+
allFiles.forEach((file) => {
|
|
223
|
+
content += `- ${file}\n`;
|
|
224
|
+
});
|
|
225
|
+
return content;
|
|
251
226
|
}
|
|
252
227
|
buildDetailedStructure(fileAnalyses) {
|
|
253
228
|
const structureData = fileAnalyses.map((file) => ({
|
|
@@ -317,16 +292,18 @@ For detailed information, see the files in the \`${linkPath}/\` directory.`;
|
|
|
317
292
|
return content;
|
|
318
293
|
}
|
|
319
294
|
buildDetailedGit(gitInfo) {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
return splitFiles;
|
|
295
|
+
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
|
+
`;
|
|
301
|
+
if (gitInfo.diffStat && gitInfo.diffStat.length > 0) {
|
|
302
|
+
content += gitInfo.diffStat;
|
|
329
303
|
}
|
|
330
|
-
|
|
304
|
+
else {
|
|
305
|
+
content += 'No changes detected at time of generation.';
|
|
306
|
+
}
|
|
307
|
+
return content;
|
|
331
308
|
}
|
|
332
309
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ParsemeConfigFile } from './config.js';
|
|
2
1
|
import type { ContextOutput, GeneratorOptions } from './types.js';
|
|
3
2
|
export declare class ParsemeGenerator {
|
|
4
3
|
private readonly config;
|
|
@@ -8,8 +7,6 @@ export declare class ParsemeGenerator {
|
|
|
8
7
|
private readonly gitAnalyzer;
|
|
9
8
|
private readonly contextBuilder;
|
|
10
9
|
constructor(options?: GeneratorOptions);
|
|
11
|
-
static fromConfig(configPath?: string): Promise<ParsemeGenerator>;
|
|
12
|
-
static fromConfigWithOptions(configPath?: string, cliOptions?: Partial<ParsemeConfigFile>): Promise<ParsemeGenerator>;
|
|
13
10
|
generate(outputPath?: string): Promise<ContextOutput>;
|
|
14
11
|
generateToFile(outputPath?: string, contextDir?: string): Promise<void>;
|
|
15
12
|
}
|
|
@@ -4,8 +4,8 @@ import { ASTAnalyzer } from './analyzers/ast-analyzer.js';
|
|
|
4
4
|
import { FrameworkDetector } from './analyzers/framework-detector.js';
|
|
5
5
|
import { GitAnalyzer } from './analyzers/git-analyzer.js';
|
|
6
6
|
import { ProjectAnalyzer } from './analyzers/project-analyzer.js';
|
|
7
|
-
import { ContextBuilder } from './builders/context-builder.js';
|
|
8
7
|
import { ParsemeConfig } from './config.js';
|
|
8
|
+
import { ContextBuilder } from './context-builder.js';
|
|
9
9
|
export class ParsemeGenerator {
|
|
10
10
|
config;
|
|
11
11
|
projectAnalyzer;
|
|
@@ -17,18 +17,10 @@ export class ParsemeGenerator {
|
|
|
17
17
|
this.config = new ParsemeConfig(options);
|
|
18
18
|
this.projectAnalyzer = new ProjectAnalyzer(this.config);
|
|
19
19
|
this.astAnalyzer = new ASTAnalyzer(this.config);
|
|
20
|
-
this.frameworkDetector = new FrameworkDetector(
|
|
21
|
-
this.gitAnalyzer = new GitAnalyzer(
|
|
20
|
+
this.frameworkDetector = new FrameworkDetector();
|
|
21
|
+
this.gitAnalyzer = new GitAnalyzer();
|
|
22
22
|
this.contextBuilder = new ContextBuilder(this.config);
|
|
23
23
|
}
|
|
24
|
-
static async fromConfig(configPath) {
|
|
25
|
-
const config = await ParsemeConfig.fromFile(configPath);
|
|
26
|
-
return new ParsemeGenerator(config.get());
|
|
27
|
-
}
|
|
28
|
-
static async fromConfigWithOptions(configPath, cliOptions = {}) {
|
|
29
|
-
const config = await ParsemeConfig.fromFileWithOptions(configPath, cliOptions);
|
|
30
|
-
return new ParsemeGenerator(config.get());
|
|
31
|
-
}
|
|
32
24
|
async generate(outputPath) {
|
|
33
25
|
const configData = this.config.get();
|
|
34
26
|
// Step 1: Analyze the project structure and metadata
|
|
@@ -37,16 +29,19 @@ export class ParsemeGenerator {
|
|
|
37
29
|
projectInfo.framework = await this.frameworkDetector.detect(projectInfo);
|
|
38
30
|
// Step 3: Analyze all relevant files with AST
|
|
39
31
|
const fileAnalyses = await this.astAnalyzer.analyzeProject(configData.rootDir);
|
|
40
|
-
// Step 4: Get
|
|
32
|
+
// Step 4: Get all project files (for file list output)
|
|
33
|
+
const allFiles = await this.projectAnalyzer.getAllProjectFiles(configData.rootDir);
|
|
34
|
+
// Step 5: Get git information if enabled
|
|
41
35
|
const gitInfo = configData.includeGitInfo
|
|
42
36
|
? await this.gitAnalyzer.analyze(configData.rootDir)
|
|
43
|
-
:
|
|
37
|
+
: null;
|
|
44
38
|
// Calculate final output path for link generation
|
|
45
39
|
const finalOutputPath = outputPath || configData.outputPath || join(configData.rootDir, 'PARSEME.md');
|
|
46
|
-
// Step
|
|
40
|
+
// Step 6: Build the context output
|
|
47
41
|
return this.contextBuilder.build({
|
|
48
42
|
projectInfo,
|
|
49
43
|
fileAnalyses,
|
|
44
|
+
allFiles,
|
|
50
45
|
gitInfo,
|
|
51
46
|
options: configData,
|
|
52
47
|
contextDir: configData.contextDir,
|
|
@@ -81,7 +76,9 @@ export class ParsemeGenerator {
|
|
|
81
76
|
await writeFile(finalOutputPath, context.parseme);
|
|
82
77
|
if (context.context) {
|
|
83
78
|
for (const [filename, content] of Object.entries(context.context)) {
|
|
84
|
-
|
|
79
|
+
// Use .md extension for markdown files, .json for others
|
|
80
|
+
const extension = filename === 'gitDiff' || filename === 'files' ? '.md' : '.json';
|
|
81
|
+
await writeFile(join(parsemeDir, `${filename}${extension}`), content);
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
84
|
}
|