@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.
Files changed (39) hide show
  1. package/README.md +163 -156
  2. package/dist/{cli.js → cli/cli.js} +59 -49
  3. package/dist/{prompt.d.ts → cli/prompt.d.ts} +0 -1
  4. package/dist/{prompt.js → cli/prompt.js} +0 -9
  5. package/dist/{analyzers → core/analyzers}/ast-analyzer.js +7 -6
  6. package/dist/{analyzers → core/analyzers}/framework-detector.d.ts +0 -3
  7. package/dist/{analyzers → core/analyzers}/framework-detector.js +0 -4
  8. package/dist/core/analyzers/git-analyzer.d.ts +10 -0
  9. package/dist/{analyzers → core/analyzers}/git-analyzer.js +12 -16
  10. package/dist/{analyzers → core/analyzers}/pattern-detector.d.ts +0 -3
  11. package/dist/{analyzers → core/analyzers}/pattern-detector.js +52 -47
  12. package/dist/{analyzers → core/analyzers}/project-analyzer.d.ts +2 -0
  13. package/dist/{analyzers → core/analyzers}/project-analyzer.js +19 -5
  14. package/dist/core/config.d.ts +20 -0
  15. package/dist/{config.js → core/config.js} +80 -67
  16. package/dist/{builders → core}/context-builder.d.ts +5 -9
  17. package/dist/{builders → core}/context-builder.js +103 -126
  18. package/dist/{generator.d.ts → core/generator.d.ts} +0 -3
  19. package/dist/{generator.js → core/generator.js} +12 -15
  20. package/dist/core/types/analyzer-types.d.ts +39 -0
  21. package/dist/core/types/analyzer-types.js +2 -0
  22. package/dist/{config.d.ts → core/types/config-types.d.ts} +8 -15
  23. package/dist/core/types/config-types.js +2 -0
  24. package/dist/core/types/generator-types.d.ts +8 -0
  25. package/dist/core/types/generator-types.js +2 -0
  26. package/dist/core/types/index.d.ts +4 -0
  27. package/dist/core/types/index.js +5 -0
  28. package/dist/core/types/project-types.d.ts +30 -0
  29. package/dist/core/types/project-types.js +2 -0
  30. package/dist/core/types.d.ts +1 -0
  31. package/dist/core/types.js +2 -0
  32. package/dist/index.d.ts +3 -4
  33. package/dist/index.js +2 -2
  34. package/package.json +3 -4
  35. package/dist/analyzers/git-analyzer.d.ts +0 -13
  36. package/dist/types.d.ts +0 -82
  37. package/dist/types.js +0 -2
  38. /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
  39. /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, try to import directly first
36
- // This works if the user has transpiled their TS config to JS or is using tsx/ts-node
37
- try {
38
- const module = await import(fullPath);
39
- const config = module.default || module;
40
- return new ParsemeConfig(config);
41
- }
42
- catch {
43
- // If direct import fails, suggest using .js instead
44
- console.warn(`Could not load TypeScript config file: ${path}`);
45
- console.warn(`Consider using a .js config file or ensure tsx/ts-node is available`);
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 content = await readFile(path, 'utf-8');
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
- includePatterns: config.includePatterns || [
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.config, null, 2));
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
- export default ${JSON.stringify(this.config, null, 2)};
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.config, null, 2)};
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
- const defaultPatterns = [
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
- if (gitignorePatterns.length > 0) {
171
- // Merge gitignore patterns with critical defaults
172
- const criticalDefaults = ['node_modules/**', '.git/**'];
173
- return [...new Set([...criticalDefaults, ...gitignorePatterns])];
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 defaultPatterns;
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 '../config.js';
2
- import type { ContextOutput, ProjectInfo, FileAnalysis, GitInfo, GeneratorOptions } from '../types.js';
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
- gitInfo?: GitInfo;
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
- const mainContent = this.buildHeader(projectInfo) +
86
- '\n\n' +
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
- // Detailed structure
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, 'routes');
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
- const gitResult = this.buildDetailedGit(gitInfo);
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(projectInfo) {
136
- return `# ${projectInfo.name}
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
- *AI-Generated Project Context*
139
-
140
- ${projectInfo.description || 'No description available.'}`;
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
- return `## Project Overview
158
+ let content = `## Basic Project Information
144
159
 
145
- - **Type**: ${projectInfo.type} project
146
- - **Package Manager**: ${projectInfo.packageManager}
147
- - **Framework**: ${projectInfo.framework?.name || 'None detected'}
148
- - **Version**: ${projectInfo.version || 'Not specified'}`;
149
- }
150
- buildFrameworkSection(framework) {
151
- if (!framework) {
152
- return '';
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
- const features = framework.features.length > 0 ? `\n- **Features**: ${framework.features.join(', ')}` : '';
155
- return `## Framework: ${framework.name}
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 += `**Production**: ${deps.join(', ')}\n\n`;
173
+ content += '\n\n### Dependencies\n';
174
+ deps.forEach((dep) => {
175
+ content += `- ${dep}\n`;
176
+ });
206
177
  }
207
- if (devDeps.length > 0) {
208
- content += `**Development**: ${devDeps.join(', ')}`;
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
- - **Branch**: ${gitInfo.branch}
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
- *Generated by PARSEME v1.0.0 on ${new Date().toISOString().split('T')[0]}*`;
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, contextDir, outputPath } = context;
227
- const totalFiles = fileAnalyses.length;
204
+ buildSummarySection(context, linkPath) {
205
+ const { fileAnalyses } = context;
228
206
  const routes = fileAnalyses.flatMap((f) => f.routes || []).length;
229
- // Calculate the relative path for the link in markdown
230
- let linkPath = 'parseme-context';
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
- For detailed information, see the files in the \`${linkPath}/\` directory.`;
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
- const jsonContent = JSON.stringify(gitInfo, null, 2);
321
- const result = this.truncateContent(jsonContent);
322
- if (Array.isArray(result)) {
323
- const splitFiles = {};
324
- result.forEach((part, index) => {
325
- const suffix = index === 0 ? '' : `_part${index + 1}`;
326
- splitFiles[`git${suffix}`] = part;
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
- return result;
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(this.config);
21
- this.gitAnalyzer = new GitAnalyzer(this.config);
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 git information if enabled
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
- : undefined;
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 5: Build the context output
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
- await writeFile(join(parsemeDir, `${filename}.json`), content);
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
  }