@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.
- package/README.md +182 -187
- package/dist/cli/cli.js +144 -0
- package/dist/{analyzers → core/analyzers}/ast-analyzer.d.ts +1 -1
- package/dist/{analyzers → core/analyzers}/ast-analyzer.js +14 -27
- package/dist/core/analyzers/framework-detector.d.ts +7 -0
- package/dist/core/analyzers/framework-detector.js +165 -0
- package/dist/{analyzers → core/analyzers}/pattern-detector.d.ts +2 -5
- package/dist/{analyzers → core/analyzers}/pattern-detector.js +134 -49
- package/dist/{analyzers → core/analyzers}/project-analyzer.d.ts +2 -0
- package/dist/{analyzers → core/analyzers}/project-analyzer.js +12 -9
- package/dist/core/config.d.ts +19 -0
- package/dist/{config.js → core/config.js} +79 -91
- package/dist/{builders → core}/context-builder.d.ts +4 -11
- package/dist/core/context-builder.js +225 -0
- package/dist/{generator.d.ts → core/generator.d.ts} +0 -3
- package/dist/{generator.js → core/generator.js} +12 -17
- package/dist/core/types/analyzer-types.d.ts +38 -0
- package/dist/core/types/analyzer-types.js +2 -0
- package/dist/core/types/config-types.d.ts +36 -0
- package/dist/core/types/config-types.js +2 -0
- package/dist/core/types/generator-types.d.ts +6 -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/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/{analyzers/git-analyzer.d.ts → utils/git.d.ts} +1 -4
- package/dist/{analyzers/git-analyzer.js → utils/git.js} +0 -4
- package/dist/{prompt.d.ts → utils/prompt.d.ts} +0 -1
- package/dist/{prompt.js → utils/prompt.js} +0 -9
- package/package.json +12 -8
- package/dist/analyzers/framework-detector.d.ts +0 -12
- package/dist/analyzers/framework-detector.js +0 -180
- package/dist/builders/context-builder.js +0 -386
- package/dist/cli.js +0 -145
- package/dist/config.d.ts +0 -44
- package/dist/types.d.ts +0 -84
- package/dist/types.js +0 -2
- /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { readFile, access, readdir, stat } from 'fs/promises';
|
|
2
2
|
import { join, basename } from 'path';
|
|
3
|
+
import { FileCollector } from '../../utils/file-collector.js';
|
|
3
4
|
export class ProjectAnalyzer {
|
|
4
5
|
config;
|
|
6
|
+
fileCollector;
|
|
5
7
|
constructor(config) {
|
|
6
8
|
this.config = config;
|
|
9
|
+
this.fileCollector = new FileCollector(config);
|
|
7
10
|
}
|
|
8
11
|
async analyze(rootDir) {
|
|
9
12
|
const packageJsonPath = join(rootDir, 'package.json');
|
|
@@ -30,7 +33,7 @@ export class ProjectAnalyzer {
|
|
|
30
33
|
name: basename(rootDir),
|
|
31
34
|
type: await this.detectProjectType(rootDir),
|
|
32
35
|
category: 'unknown',
|
|
33
|
-
packageManager: '
|
|
36
|
+
packageManager: 'unknown',
|
|
34
37
|
dependencies: {},
|
|
35
38
|
devDependencies: {},
|
|
36
39
|
scripts: {},
|
|
@@ -41,7 +44,7 @@ export class ProjectAnalyzer {
|
|
|
41
44
|
}
|
|
42
45
|
async detectProjectType(rootDir) {
|
|
43
46
|
try {
|
|
44
|
-
const files = await this.getFilesRecursive(rootDir, 2); // Only check 2 levels deep
|
|
47
|
+
const files = await this.getFilesRecursive(rootDir, rootDir, 2); // Only check 2 levels deep
|
|
45
48
|
const tsFiles = files.filter((f) => f.endsWith('.ts') && !f.endsWith('.d.ts'));
|
|
46
49
|
const jsFiles = files.filter((f) => f.endsWith('.js'));
|
|
47
50
|
if (tsFiles.length > 0 && jsFiles.length > 0) {
|
|
@@ -74,7 +77,7 @@ export class ProjectAnalyzer {
|
|
|
74
77
|
}
|
|
75
78
|
return 'npm';
|
|
76
79
|
}
|
|
77
|
-
async getFilesRecursive(dir, maxDepth) {
|
|
80
|
+
async getFilesRecursive(dir, rootDir, maxDepth) {
|
|
78
81
|
if (maxDepth <= 0) {
|
|
79
82
|
return [];
|
|
80
83
|
}
|
|
@@ -88,7 +91,7 @@ export class ProjectAnalyzer {
|
|
|
88
91
|
files.push(fullPath);
|
|
89
92
|
}
|
|
90
93
|
else if (stats.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
91
|
-
files.push(...(await this.getFilesRecursive(fullPath, maxDepth - 1)));
|
|
94
|
+
files.push(...(await this.getFilesRecursive(fullPath, rootDir, maxDepth - 1)));
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
return files;
|
|
@@ -132,11 +135,7 @@ export class ProjectAnalyzer {
|
|
|
132
135
|
return 'frontend-web';
|
|
133
136
|
}
|
|
134
137
|
// Check for backend frameworks
|
|
135
|
-
if (deps['express'] ||
|
|
136
|
-
deps['fastify'] ||
|
|
137
|
-
deps['@nestjs/core'] ||
|
|
138
|
-
deps['koa'] ||
|
|
139
|
-
deps['@hapi/hapi']) {
|
|
138
|
+
if (deps['express'] || deps['fastify'] || deps['@nestjs/core']) {
|
|
140
139
|
return 'backend-api';
|
|
141
140
|
}
|
|
142
141
|
// Check if it's a library (has main/module/exports but no app dependencies)
|
|
@@ -202,4 +201,8 @@ export class ProjectAnalyzer {
|
|
|
202
201
|
];
|
|
203
202
|
return appIndicators.some((indicator) => deps[indicator]);
|
|
204
203
|
}
|
|
204
|
+
async getAllProjectFiles(rootDir) {
|
|
205
|
+
const result = await this.fileCollector.getAllProjectFiles(rootDir);
|
|
206
|
+
return result.files;
|
|
207
|
+
}
|
|
205
208
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParsemeConfigFile } from './types.js';
|
|
2
|
+
export declare class ParsemeConfig {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly userConfig;
|
|
5
|
+
constructor(config?: Partial<ParsemeConfigFile>);
|
|
6
|
+
static fromFileWithOptions(configPath?: string, cliOptions?: Partial<ParsemeConfigFile>, options?: {
|
|
7
|
+
showWarnings?: boolean;
|
|
8
|
+
}): Promise<ParsemeConfig>;
|
|
9
|
+
static fromFile(configPath?: string, options?: {
|
|
10
|
+
showWarnings?: boolean;
|
|
11
|
+
throwOnNotFound?: boolean;
|
|
12
|
+
}): Promise<ParsemeConfig>;
|
|
13
|
+
private mergeWithDefaults;
|
|
14
|
+
get(): ParsemeConfigFile;
|
|
15
|
+
save(path?: string): Promise<void>;
|
|
16
|
+
private generateJSConfig;
|
|
17
|
+
private generateTSConfig;
|
|
18
|
+
private mergeExcludePatterns;
|
|
19
|
+
}
|
|
@@ -1,30 +1,37 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
2
|
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,19 +120,10 @@ 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,
|
|
126
|
+
useGitForFiles: config.useGitForFiles ?? true,
|
|
92
127
|
// Sections
|
|
93
128
|
sections: {
|
|
94
129
|
overview: true,
|
|
@@ -109,10 +144,7 @@ export class ParsemeConfig {
|
|
|
109
144
|
},
|
|
110
145
|
// Size limits
|
|
111
146
|
limits: {
|
|
112
|
-
|
|
113
|
-
maxCharsPerFile: config.limits?.maxCharsPerFile ?? 50000, // ~15k tokens
|
|
114
|
-
maxFilesPerContext: config.limits?.maxFilesPerContext ?? 20,
|
|
115
|
-
truncateStrategy: config.limits?.truncateStrategy ?? 'truncate',
|
|
147
|
+
maxFilesPerContext: config.limits?.maxFilesPerContext ?? 5000,
|
|
116
148
|
},
|
|
117
149
|
};
|
|
118
150
|
}
|
|
@@ -132,73 +164,29 @@ export class ParsemeConfig {
|
|
|
132
164
|
await writeFile(path, configContent);
|
|
133
165
|
}
|
|
134
166
|
else {
|
|
135
|
-
// Generate JSON config file
|
|
136
|
-
await writeFile(path, JSON.stringify(this.
|
|
167
|
+
// Generate JSON config file - only save user config, not defaults
|
|
168
|
+
await writeFile(path, JSON.stringify(this.userConfig, null, 2));
|
|
137
169
|
}
|
|
138
170
|
}
|
|
139
171
|
generateJSConfig() {
|
|
172
|
+
// Only export user-specified config, not defaults
|
|
140
173
|
return `/** @type {import('parseme').ParsemeConfigFile} */
|
|
141
|
-
|
|
174
|
+
const config = ${JSON.stringify(this.userConfig, null, 2)};
|
|
175
|
+
|
|
176
|
+
export default config;
|
|
142
177
|
`;
|
|
143
178
|
}
|
|
144
179
|
generateTSConfig() {
|
|
180
|
+
// Only export user-specified config, not defaults
|
|
145
181
|
return `import type { ParsemeConfigFile } from 'parseme';
|
|
146
182
|
|
|
147
|
-
const config: ParsemeConfigFile = ${JSON.stringify(this.
|
|
183
|
+
const config: ParsemeConfigFile = ${JSON.stringify(this.userConfig, null, 2)};
|
|
148
184
|
|
|
149
185
|
export default config;
|
|
150
186
|
`;
|
|
151
187
|
}
|
|
152
|
-
mergeExcludePatterns(configPatterns,
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
169
|
-
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])];
|
|
174
|
-
}
|
|
175
|
-
return defaultPatterns;
|
|
176
|
-
}
|
|
177
|
-
readGitignorePatterns(rootDir) {
|
|
178
|
-
try {
|
|
179
|
-
const gitignorePath = join(rootDir, '.gitignore');
|
|
180
|
-
if (!existsSync(gitignorePath)) {
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
|
|
184
|
-
return gitignoreContent
|
|
185
|
-
.split('\n')
|
|
186
|
-
.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
|
-
// Convert simple names to match directory patterns
|
|
195
|
-
return pattern + '/**';
|
|
196
|
-
}
|
|
197
|
-
return pattern;
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
188
|
+
mergeExcludePatterns(configPatterns, _rootDir) {
|
|
189
|
+
// Only use config patterns - git ignore is now handled by FileFilterService
|
|
190
|
+
return configPatterns || [];
|
|
203
191
|
}
|
|
204
192
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
allFiles: string[];
|
|
6
7
|
gitInfo?: GitInfo | null;
|
|
7
8
|
options: GeneratorOptions;
|
|
8
9
|
contextDir?: string;
|
|
@@ -11,20 +12,12 @@ interface BuildContext {
|
|
|
11
12
|
export declare class ContextBuilder {
|
|
12
13
|
private readonly config;
|
|
13
14
|
constructor(config: ParsemeConfig);
|
|
14
|
-
private truncateContent;
|
|
15
|
-
private splitContentByLines;
|
|
16
|
-
private splitContentByChars;
|
|
17
15
|
build(context: BuildContext): ContextOutput;
|
|
18
16
|
private buildMultiFile;
|
|
19
17
|
private buildHeader;
|
|
20
18
|
private buildProjectOverview;
|
|
21
|
-
private
|
|
22
|
-
private buildArchitectureSection;
|
|
23
|
-
private buildRoutesSection;
|
|
24
|
-
private buildFileStructureSection;
|
|
25
|
-
private buildDependenciesSection;
|
|
19
|
+
private formatFrameworksList;
|
|
26
20
|
private buildGitSection;
|
|
27
|
-
private buildFooter;
|
|
28
21
|
private buildSummarySection;
|
|
29
22
|
private buildFilesList;
|
|
30
23
|
private buildDetailedStructure;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { relative, dirname } from 'path';
|
|
2
|
+
export class ContextBuilder {
|
|
3
|
+
config;
|
|
4
|
+
constructor(config) {
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
build(context) {
|
|
8
|
+
return this.buildMultiFile(context);
|
|
9
|
+
}
|
|
10
|
+
buildMultiFile(context) {
|
|
11
|
+
const { projectInfo, fileAnalyses, gitInfo, contextDir, outputPath } = context;
|
|
12
|
+
// Files are now pre-limited in their respective analyzers, so no need to limit here
|
|
13
|
+
// Calculate the relative path for the link in markdown
|
|
14
|
+
let linkPath = 'parseme-context';
|
|
15
|
+
if (contextDir && outputPath) {
|
|
16
|
+
const outputDir = dirname(outputPath);
|
|
17
|
+
if (contextDir.startsWith('/')) {
|
|
18
|
+
// Absolute path: calculate relative path from output file to context dir
|
|
19
|
+
linkPath = relative(outputDir, contextDir);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Relative path: use as-is since it's relative to the output file's directory
|
|
23
|
+
linkPath = contextDir;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
else if (contextDir) {
|
|
27
|
+
// Fallback: use contextDir as-is if no outputPath provided
|
|
28
|
+
linkPath = contextDir;
|
|
29
|
+
}
|
|
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) +
|
|
41
|
+
'\n\n\n' +
|
|
42
|
+
this.buildProjectOverview(projectInfo) +
|
|
43
|
+
'\n\n\n' +
|
|
44
|
+
this.buildSummarySection(linkPath, hasRoutes) +
|
|
45
|
+
'\n\n\n' +
|
|
46
|
+
(gitInfo ? this.buildGitSection(gitInfo) : '');
|
|
47
|
+
const contextFiles = {
|
|
48
|
+
structure: '',
|
|
49
|
+
};
|
|
50
|
+
// Files list (markdown) - all files in project, not just analyzed ones
|
|
51
|
+
contextFiles.files = this.buildFilesList(context.allFiles);
|
|
52
|
+
// Detailed structure (JSON with AST)
|
|
53
|
+
contextFiles.structure = this.buildDetailedStructure(fileAnalyses, hasRoutes);
|
|
54
|
+
// Routes documentation (only if routes exist)
|
|
55
|
+
if (hasRoutes) {
|
|
56
|
+
contextFiles.routes = this.buildDetailedRoutes(routes);
|
|
57
|
+
}
|
|
58
|
+
// Git information
|
|
59
|
+
if (gitInfo?.diffStat?.length && gitInfo.diffStat.length > 0) {
|
|
60
|
+
contextFiles.gitDiff = this.buildDetailedGit(gitInfo);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
parseme: mainContent,
|
|
64
|
+
context: contextFiles,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
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
|
|
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.
|
|
77
|
+
|
|
78
|
+
**Usage Instructions for AI Agents:**
|
|
79
|
+
1. Read this PARSEME.md file completely first before accessing individual project files
|
|
80
|
+
2. Basic project information, script availability and dependency information provides basic understanding of code base and tech stack without checking package.json
|
|
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}`;
|
|
84
|
+
}
|
|
85
|
+
buildProjectOverview(projectInfo) {
|
|
86
|
+
let content = `## Basic Project Information
|
|
87
|
+
|
|
88
|
+
**Project:** ${projectInfo.name}${projectInfo.version ? ` v${projectInfo.version}` : ''}
|
|
89
|
+
**Description:** ${projectInfo.description || 'No description available.'}
|
|
90
|
+
**Type:** ${projectInfo.type} project
|
|
91
|
+
**Package Manager:** ${projectInfo.packageManager}
|
|
92
|
+
**Framework:** ${this.formatFrameworksList(projectInfo.frameworks)}`;
|
|
93
|
+
// Add main entry point if available
|
|
94
|
+
if (projectInfo.entryPoints && projectInfo.entryPoints.length > 0) {
|
|
95
|
+
content += `\n**Main Entry Point:** ${projectInfo.entryPoints[0]}`;
|
|
96
|
+
}
|
|
97
|
+
content += '\n';
|
|
98
|
+
// Add dependencies
|
|
99
|
+
const deps = Object.keys(projectInfo.dependencies);
|
|
100
|
+
if (deps.length > 0) {
|
|
101
|
+
content += '\n\n### Dependencies\n';
|
|
102
|
+
deps.forEach((dep) => {
|
|
103
|
+
content += `- ${dep}\n`;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
// Add available scripts
|
|
107
|
+
if (projectInfo.scripts && Object.keys(projectInfo.scripts).length > 0) {
|
|
108
|
+
content += '\n### Available Scripts\n';
|
|
109
|
+
Object.entries(projectInfo.scripts).forEach(([name, script]) => {
|
|
110
|
+
content += `- **${name}**: \`${script}\`\n`;
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return content;
|
|
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
|
+
}
|
|
125
|
+
buildGitSection(gitInfo) {
|
|
126
|
+
const base = `## Git Information
|
|
127
|
+
|
|
128
|
+
**State when PARSEME.md and all linked files were automatically generated:**
|
|
129
|
+
|
|
130
|
+
- **Branch:** ${gitInfo.branch}
|
|
131
|
+
- **Commit:** ${gitInfo.lastCommit}${gitInfo.origin ? `\n- **Origin:** ${gitInfo.origin}` : ''}
|
|
132
|
+
|
|
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).
|
|
136
|
+
|
|
137
|
+
**AI Agent Command:** To check for changes since generation, run:
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
git diff --stat
|
|
140
|
+
\`\`\`
|
|
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;
|
|
144
|
+
}
|
|
145
|
+
buildSummarySection(linkPath, hasRoutes) {
|
|
146
|
+
let content = `## Project Files
|
|
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.
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Project Structure & AST
|
|
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).`;
|
|
155
|
+
}
|
|
156
|
+
return content;
|
|
157
|
+
}
|
|
158
|
+
buildFilesList(allFiles) {
|
|
159
|
+
let content = `# Project Files\n`;
|
|
160
|
+
allFiles.forEach((file) => {
|
|
161
|
+
content += `- ${file}\n`;
|
|
162
|
+
});
|
|
163
|
+
return content;
|
|
164
|
+
}
|
|
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);
|
|
187
|
+
}
|
|
188
|
+
buildDetailedRoutes(routes) {
|
|
189
|
+
return JSON.stringify(routes, null, 2);
|
|
190
|
+
}
|
|
191
|
+
buildDetailedDependencies(projectInfo) {
|
|
192
|
+
const jsonContent = JSON.stringify({
|
|
193
|
+
dependencies: projectInfo.dependencies,
|
|
194
|
+
packageManager: projectInfo.packageManager,
|
|
195
|
+
version: projectInfo.version,
|
|
196
|
+
}, null, 2);
|
|
197
|
+
return jsonContent;
|
|
198
|
+
}
|
|
199
|
+
buildDetailedFramework(frameworks) {
|
|
200
|
+
if (!frameworks || frameworks.length === 0) {
|
|
201
|
+
return '';
|
|
202
|
+
}
|
|
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
|
+
});
|
|
217
|
+
return content;
|
|
218
|
+
}
|
|
219
|
+
buildDetailedGit(gitInfo) {
|
|
220
|
+
let content = `# Git Diff Statistics
|
|
221
|
+
`;
|
|
222
|
+
content += gitInfo.diffStat;
|
|
223
|
+
return content;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -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
|
}
|