@parseme/cli 0.0.2
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/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/analyzers/ast-analyzer.d.ts +12 -0
- package/dist/analyzers/ast-analyzer.js +176 -0
- package/dist/analyzers/framework-detector.d.ts +12 -0
- package/dist/analyzers/framework-detector.js +180 -0
- package/dist/analyzers/git-analyzer.d.ts +13 -0
- package/dist/analyzers/git-analyzer.js +92 -0
- package/dist/analyzers/pattern-detector.d.ts +58 -0
- package/dist/analyzers/pattern-detector.js +174 -0
- package/dist/analyzers/project-analyzer.d.ts +14 -0
- package/dist/analyzers/project-analyzer.js +205 -0
- package/dist/builders/context-builder.d.ts +35 -0
- package/dist/builders/context-builder.js +332 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +145 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +203 -0
- package/dist/generator.d.ts +15 -0
- package/dist/generator.js +88 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.js +42 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +2 -0
- package/package.json +81 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { ParsemeConfig } from './config.js';
|
|
5
|
+
import { ParsemeGenerator } from './generator.js';
|
|
6
|
+
import { confirmPrompt } from './prompt.js';
|
|
7
|
+
const program = new Command();
|
|
8
|
+
async function promptForMissingConfig(config, cliOptions) {
|
|
9
|
+
const finalConfig = { ...config };
|
|
10
|
+
// Only prompt if running interactively (stdin is a TTY) and not in CI
|
|
11
|
+
if (!process.stdin.isTTY || process.env.CI) {
|
|
12
|
+
return finalConfig;
|
|
13
|
+
}
|
|
14
|
+
// Prompt for README suggestion if not set via CLI or config
|
|
15
|
+
if (cliOptions.readmeSuggestion === undefined && config.readmeSuggestion === undefined) {
|
|
16
|
+
const wantReadmeSuggestion = await confirmPrompt('Show README.md section suggestion for AI agents?', true);
|
|
17
|
+
finalConfig.readmeSuggestion = wantReadmeSuggestion;
|
|
18
|
+
}
|
|
19
|
+
return finalConfig;
|
|
20
|
+
}
|
|
21
|
+
program.name('parseme').description('AI Project Context Generator').version('0.1.0');
|
|
22
|
+
// Main command - just run parseme
|
|
23
|
+
program
|
|
24
|
+
.description('Generate project context using config file')
|
|
25
|
+
.option('-c, --config <path>', 'Config file path')
|
|
26
|
+
.option('-o, --output <path>', 'Output file path')
|
|
27
|
+
.option('-r, --root <path>', 'Root directory to analyze')
|
|
28
|
+
.option('--context-dir <path>', 'Context directory path (default: parseme-context)')
|
|
29
|
+
.option('--include <patterns...>', 'Include patterns (glob)')
|
|
30
|
+
.option('--exclude <patterns...>', 'Exclude patterns (glob)')
|
|
31
|
+
.option('--no-git', 'Disable git information')
|
|
32
|
+
.option('--max-depth <number>', 'Maximum directory depth', parseInt)
|
|
33
|
+
.option('--no-readme-suggestion', 'Disable README.md section suggestion')
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
try {
|
|
36
|
+
// Convert CLI options to config format
|
|
37
|
+
const cliOptions = {
|
|
38
|
+
...(options.output && { outputPath: options.output }),
|
|
39
|
+
...(options.root && { rootDir: options.root }),
|
|
40
|
+
...(options.contextDir && { contextDir: options.contextDir }),
|
|
41
|
+
...(options.include && { includePatterns: options.include }),
|
|
42
|
+
...(options.exclude && { excludePatterns: options.exclude }),
|
|
43
|
+
...(options.git === false && { includeGitInfo: false }),
|
|
44
|
+
...(options.maxDepth && { maxDepth: options.maxDepth }),
|
|
45
|
+
...(options.readmeSuggestion === false && { readmeSuggestion: false }),
|
|
46
|
+
};
|
|
47
|
+
const configFromFile = await ParsemeConfig.fromFile(options.config);
|
|
48
|
+
const interactiveConfig = await promptForMissingConfig(configFromFile.get(), cliOptions);
|
|
49
|
+
// Merge: CLI options > interactive prompts > config file > defaults
|
|
50
|
+
const finalConfig = {
|
|
51
|
+
...interactiveConfig,
|
|
52
|
+
...cliOptions,
|
|
53
|
+
};
|
|
54
|
+
const config = new ParsemeConfig(finalConfig);
|
|
55
|
+
const generator = new ParsemeGenerator(config.get());
|
|
56
|
+
await generator.generateToFile();
|
|
57
|
+
console.log('Context generated successfully');
|
|
58
|
+
console.log('Tip: Add parseme as a git hook to keep context auto-updated! See README for setup instructions.');
|
|
59
|
+
const shouldShowReadmeSuggestion = finalConfig.readmeSuggestion !== false;
|
|
60
|
+
if (shouldShowReadmeSuggestion) {
|
|
61
|
+
console.log('Tip: Add this section to your README.md to help AI agents find the context:');
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('## For AI Assistants');
|
|
64
|
+
console.log('This project includes AI-optimized documentation:');
|
|
65
|
+
console.log('- `PARSEME.md` - Main project context and overview');
|
|
66
|
+
console.log('- `parseme-context/` - Detailed JSON files with code structure, dependencies, and git info');
|
|
67
|
+
console.log('');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Failed to generate context:', error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
program
|
|
76
|
+
.command('init')
|
|
77
|
+
.description('Initialize parseme configuration')
|
|
78
|
+
.option('-f, --force', 'Overwrite existing config')
|
|
79
|
+
.option('--format <format>', 'Config format: js, ts, or json', 'js')
|
|
80
|
+
.action(async (options) => {
|
|
81
|
+
try {
|
|
82
|
+
// Validate format
|
|
83
|
+
if (!['js', 'ts', 'json'].includes(options.format)) {
|
|
84
|
+
console.error('Invalid format. Use js, ts, or json');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
const configPath = join(process.cwd(), `parseme.config.${options.format}`);
|
|
88
|
+
const config = new ParsemeConfig();
|
|
89
|
+
// Check if config already exists
|
|
90
|
+
if (!options.force) {
|
|
91
|
+
try {
|
|
92
|
+
const fs = await import('fs/promises');
|
|
93
|
+
await fs.access(configPath);
|
|
94
|
+
console.log('Configuration file already exists. Use --force to overwrite.');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// File doesn't exist, continue
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
await config.save(configPath);
|
|
102
|
+
console.log(`Configuration file created at ${configPath}`);
|
|
103
|
+
if (options.format === 'ts') {
|
|
104
|
+
console.log('For TypeScript configs, ensure tsx or ts-node is available to load .ts files');
|
|
105
|
+
}
|
|
106
|
+
console.log('Add "parseme": "parseme" to your package.json scripts');
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error('Failed to create configuration:', error);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
// If no command and no args, run main action
|
|
114
|
+
if (process.argv.length <= 2) {
|
|
115
|
+
// Run the default action
|
|
116
|
+
(async () => {
|
|
117
|
+
try {
|
|
118
|
+
const configFromFile = await ParsemeConfig.fromFile();
|
|
119
|
+
const interactiveConfig = await promptForMissingConfig(configFromFile.get(), {});
|
|
120
|
+
const config = new ParsemeConfig(interactiveConfig);
|
|
121
|
+
const generator = new ParsemeGenerator(config.get());
|
|
122
|
+
await generator.generateToFile();
|
|
123
|
+
console.log('Context generated successfully');
|
|
124
|
+
console.log('Tip: Add parseme as a git hook to keep context auto-updated! See README for setup instructions.');
|
|
125
|
+
const shouldShowReadmeSuggestion = interactiveConfig.readmeSuggestion !== false;
|
|
126
|
+
if (shouldShowReadmeSuggestion) {
|
|
127
|
+
console.log('Tip: Add this section to your README.md to help AI agents find the context:');
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log('## For AI Assistants');
|
|
130
|
+
console.log('This project includes AI-optimized documentation:');
|
|
131
|
+
console.log('- `PARSEME.md` - Main project context and overview');
|
|
132
|
+
console.log('- `parseme-context/` - Detailed JSON files with code structure, dependencies, and git info');
|
|
133
|
+
console.log('');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error('Failed to generate context:', error);
|
|
138
|
+
console.log('Run "parseme init" to create a configuration file');
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
})();
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
program.parse();
|
|
145
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { GeneratorOptions } from './types.js';
|
|
2
|
+
export interface ParsemeConfigFile extends GeneratorOptions {
|
|
3
|
+
outputPath?: string;
|
|
4
|
+
contextDir?: string;
|
|
5
|
+
rootDir?: string;
|
|
6
|
+
includePatterns?: string[];
|
|
7
|
+
excludePatterns?: string[];
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
includeGitInfo?: boolean;
|
|
10
|
+
readmeSuggestion?: boolean;
|
|
11
|
+
sections?: {
|
|
12
|
+
overview?: boolean;
|
|
13
|
+
architecture?: boolean;
|
|
14
|
+
routes?: boolean;
|
|
15
|
+
dependencies?: boolean;
|
|
16
|
+
git?: boolean;
|
|
17
|
+
fileStructure?: boolean;
|
|
18
|
+
};
|
|
19
|
+
style?: {
|
|
20
|
+
includeLineNumbers?: boolean;
|
|
21
|
+
includeFileStats?: boolean;
|
|
22
|
+
groupByType?: boolean;
|
|
23
|
+
sortOrder?: 'alphabetical' | 'type' | 'size';
|
|
24
|
+
};
|
|
25
|
+
limits?: {
|
|
26
|
+
maxLinesPerFile?: number;
|
|
27
|
+
maxCharsPerFile?: number;
|
|
28
|
+
maxFilesPerContext?: number;
|
|
29
|
+
truncateStrategy?: 'truncate' | 'split' | 'summarize';
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export declare class ParsemeConfig {
|
|
33
|
+
private readonly config;
|
|
34
|
+
constructor(config?: Partial<ParsemeConfigFile>);
|
|
35
|
+
static fromFileWithOptions(configPath?: string, cliOptions?: Partial<ParsemeConfigFile>): Promise<ParsemeConfig>;
|
|
36
|
+
static fromFile(configPath?: string): Promise<ParsemeConfig>;
|
|
37
|
+
private mergeWithDefaults;
|
|
38
|
+
get(): ParsemeConfigFile;
|
|
39
|
+
save(path?: string): Promise<void>;
|
|
40
|
+
private generateJSConfig;
|
|
41
|
+
private generateTSConfig;
|
|
42
|
+
private mergeExcludePatterns;
|
|
43
|
+
private readGitignorePatterns;
|
|
44
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { join, extname } from 'path';
|
|
4
|
+
export class ParsemeConfig {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.config = this.mergeWithDefaults(config);
|
|
8
|
+
}
|
|
9
|
+
static async fromFileWithOptions(configPath, cliOptions = {}) {
|
|
10
|
+
const configFromFile = await ParsemeConfig.fromFile(configPath);
|
|
11
|
+
const mergedConfig = {
|
|
12
|
+
...configFromFile.get(),
|
|
13
|
+
...cliOptions, // CLI options take priority
|
|
14
|
+
};
|
|
15
|
+
return new ParsemeConfig(mergedConfig);
|
|
16
|
+
}
|
|
17
|
+
static async fromFile(configPath) {
|
|
18
|
+
const defaultPaths = [
|
|
19
|
+
'parseme.config.ts',
|
|
20
|
+
'parseme.config.js',
|
|
21
|
+
'parseme.config.json',
|
|
22
|
+
'.parsemerc.ts',
|
|
23
|
+
'.parsemerc.js',
|
|
24
|
+
'.parsemerc.json',
|
|
25
|
+
'.parsemerc',
|
|
26
|
+
];
|
|
27
|
+
const paths = configPath ? [configPath] : defaultPaths;
|
|
28
|
+
for (const path of paths) {
|
|
29
|
+
try {
|
|
30
|
+
const ext = extname(path);
|
|
31
|
+
if (ext === '.js' || ext === '.ts') {
|
|
32
|
+
// Dynamic import for JS/TS config files
|
|
33
|
+
const fullPath = path.startsWith('/') ? path : join(process.cwd(), path);
|
|
34
|
+
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`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// JavaScript files
|
|
50
|
+
const module = await import(fullPath);
|
|
51
|
+
const config = module.default || module;
|
|
52
|
+
return new ParsemeConfig(config);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// JSON config files
|
|
57
|
+
const content = await readFile(path, 'utf-8');
|
|
58
|
+
const config = JSON.parse(content);
|
|
59
|
+
return new ParsemeConfig(config);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Continue to next path
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Return default config if no file found
|
|
67
|
+
return new ParsemeConfig();
|
|
68
|
+
}
|
|
69
|
+
mergeWithDefaults(config) {
|
|
70
|
+
const rootDir = config.rootDir || process.cwd();
|
|
71
|
+
return {
|
|
72
|
+
// Output
|
|
73
|
+
outputPath: config.outputPath || 'PARSEME.md',
|
|
74
|
+
contextDir: config.contextDir || 'parseme-context',
|
|
75
|
+
// Analysis
|
|
76
|
+
rootDir,
|
|
77
|
+
maxDepth: config.maxDepth || 10,
|
|
78
|
+
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
|
+
],
|
|
90
|
+
// Git
|
|
91
|
+
includeGitInfo: config.includeGitInfo ?? true,
|
|
92
|
+
// Sections
|
|
93
|
+
sections: {
|
|
94
|
+
overview: true,
|
|
95
|
+
architecture: true,
|
|
96
|
+
routes: true,
|
|
97
|
+
dependencies: true,
|
|
98
|
+
git: true,
|
|
99
|
+
fileStructure: true,
|
|
100
|
+
...config.sections,
|
|
101
|
+
},
|
|
102
|
+
// Style
|
|
103
|
+
style: {
|
|
104
|
+
includeLineNumbers: false,
|
|
105
|
+
includeFileStats: true,
|
|
106
|
+
groupByType: true,
|
|
107
|
+
sortOrder: 'type',
|
|
108
|
+
...config.style,
|
|
109
|
+
},
|
|
110
|
+
// Size limits
|
|
111
|
+
limits: {
|
|
112
|
+
maxLinesPerFile: config.limits?.maxLinesPerFile ?? 1000,
|
|
113
|
+
maxCharsPerFile: config.limits?.maxCharsPerFile ?? 50000, // ~15k tokens
|
|
114
|
+
maxFilesPerContext: config.limits?.maxFilesPerContext ?? 20,
|
|
115
|
+
truncateStrategy: config.limits?.truncateStrategy ?? 'truncate',
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
get() {
|
|
120
|
+
return { ...this.config };
|
|
121
|
+
}
|
|
122
|
+
async save(path = 'parseme.config.js') {
|
|
123
|
+
const ext = extname(path);
|
|
124
|
+
if (ext === '.js') {
|
|
125
|
+
// Generate JavaScript config file
|
|
126
|
+
const configContent = this.generateJSConfig();
|
|
127
|
+
await writeFile(path, configContent);
|
|
128
|
+
}
|
|
129
|
+
else if (ext === '.ts') {
|
|
130
|
+
// Generate TypeScript config file
|
|
131
|
+
const configContent = this.generateTSConfig();
|
|
132
|
+
await writeFile(path, configContent);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// Generate JSON config file
|
|
136
|
+
await writeFile(path, JSON.stringify(this.config, null, 2));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
generateJSConfig() {
|
|
140
|
+
return `/** @type {import('parseme').ParsemeConfigFile} */
|
|
141
|
+
export default ${JSON.stringify(this.config, null, 2)};
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
generateTSConfig() {
|
|
145
|
+
return `import type { ParsemeConfigFile } from 'parseme';
|
|
146
|
+
|
|
147
|
+
const config: ParsemeConfigFile = ${JSON.stringify(this.config, null, 2)};
|
|
148
|
+
|
|
149
|
+
export default config;
|
|
150
|
+
`;
|
|
151
|
+
}
|
|
152
|
+
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
|
|
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
|
+
return '**/' + pattern;
|
|
195
|
+
}
|
|
196
|
+
return pattern;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ParsemeConfigFile } from './config.js';
|
|
2
|
+
import type { ContextOutput, GeneratorOptions } from './types.js';
|
|
3
|
+
export declare class ParsemeGenerator {
|
|
4
|
+
private readonly config;
|
|
5
|
+
private readonly projectAnalyzer;
|
|
6
|
+
private readonly astAnalyzer;
|
|
7
|
+
private readonly frameworkDetector;
|
|
8
|
+
private readonly gitAnalyzer;
|
|
9
|
+
private readonly contextBuilder;
|
|
10
|
+
constructor(options?: GeneratorOptions);
|
|
11
|
+
static fromConfig(configPath?: string): Promise<ParsemeGenerator>;
|
|
12
|
+
static fromConfigWithOptions(configPath?: string, cliOptions?: Partial<ParsemeConfigFile>): Promise<ParsemeGenerator>;
|
|
13
|
+
generate(outputPath?: string): Promise<ContextOutput>;
|
|
14
|
+
generateToFile(outputPath?: string, contextDir?: string): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { ASTAnalyzer } from './analyzers/ast-analyzer.js';
|
|
4
|
+
import { FrameworkDetector } from './analyzers/framework-detector.js';
|
|
5
|
+
import { GitAnalyzer } from './analyzers/git-analyzer.js';
|
|
6
|
+
import { ProjectAnalyzer } from './analyzers/project-analyzer.js';
|
|
7
|
+
import { ContextBuilder } from './builders/context-builder.js';
|
|
8
|
+
import { ParsemeConfig } from './config.js';
|
|
9
|
+
export class ParsemeGenerator {
|
|
10
|
+
config;
|
|
11
|
+
projectAnalyzer;
|
|
12
|
+
astAnalyzer;
|
|
13
|
+
frameworkDetector;
|
|
14
|
+
gitAnalyzer;
|
|
15
|
+
contextBuilder;
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.config = new ParsemeConfig(options);
|
|
18
|
+
this.projectAnalyzer = new ProjectAnalyzer(this.config);
|
|
19
|
+
this.astAnalyzer = new ASTAnalyzer(this.config);
|
|
20
|
+
this.frameworkDetector = new FrameworkDetector(this.config);
|
|
21
|
+
this.gitAnalyzer = new GitAnalyzer(this.config);
|
|
22
|
+
this.contextBuilder = new ContextBuilder(this.config);
|
|
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
|
+
async generate(outputPath) {
|
|
33
|
+
const configData = this.config.get();
|
|
34
|
+
// Step 1: Analyze the project structure and metadata
|
|
35
|
+
const projectInfo = await this.projectAnalyzer.analyze(configData.rootDir);
|
|
36
|
+
// Step 2: Detect framework and analyze specific patterns
|
|
37
|
+
projectInfo.framework = await this.frameworkDetector.detect(projectInfo);
|
|
38
|
+
// Step 3: Analyze all relevant files with AST
|
|
39
|
+
const fileAnalyses = await this.astAnalyzer.analyzeProject(configData.rootDir);
|
|
40
|
+
// Step 4: Get git information if enabled
|
|
41
|
+
const gitInfo = configData.includeGitInfo
|
|
42
|
+
? await this.gitAnalyzer.analyze(configData.rootDir)
|
|
43
|
+
: undefined;
|
|
44
|
+
// Calculate final output path for link generation
|
|
45
|
+
const finalOutputPath = outputPath || configData.outputPath || join(configData.rootDir, 'PARSEME.md');
|
|
46
|
+
// Step 5: Build the context output
|
|
47
|
+
return this.contextBuilder.build({
|
|
48
|
+
projectInfo,
|
|
49
|
+
fileAnalyses,
|
|
50
|
+
gitInfo,
|
|
51
|
+
options: configData,
|
|
52
|
+
contextDir: configData.contextDir,
|
|
53
|
+
outputPath: finalOutputPath,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async generateToFile(outputPath, contextDir) {
|
|
57
|
+
// Use outputPath from config if not specified
|
|
58
|
+
const configData = this.config.get();
|
|
59
|
+
let finalOutputPath;
|
|
60
|
+
if (outputPath) {
|
|
61
|
+
finalOutputPath = outputPath;
|
|
62
|
+
}
|
|
63
|
+
else if (configData.outputPath) {
|
|
64
|
+
// If outputPath is relative, resolve it relative to rootDir
|
|
65
|
+
finalOutputPath = configData.outputPath.startsWith('/')
|
|
66
|
+
? configData.outputPath
|
|
67
|
+
: join(configData.rootDir, configData.outputPath);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
finalOutputPath = join(configData.rootDir, 'PARSEME.md');
|
|
71
|
+
}
|
|
72
|
+
const context = await this.generate(finalOutputPath);
|
|
73
|
+
// Use contextDir from config if not specified
|
|
74
|
+
const finalContextDir = contextDir || configData.contextDir || 'parseme-context';
|
|
75
|
+
// If contextDir is relative, make it relative to the output file's directory
|
|
76
|
+
const baseDir = join(finalOutputPath, '..');
|
|
77
|
+
const parsemeDir = finalContextDir.startsWith('/')
|
|
78
|
+
? finalContextDir // Absolute path
|
|
79
|
+
: join(baseDir, finalContextDir); // Relative path
|
|
80
|
+
await mkdir(parsemeDir, { recursive: true });
|
|
81
|
+
await writeFile(finalOutputPath, context.parseme);
|
|
82
|
+
if (context.context) {
|
|
83
|
+
for (const [filename, content] of Object.entries(context.context)) {
|
|
84
|
+
await writeFile(join(parsemeDir, `${filename}.json`), content);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { ParsemeGenerator } from './generator.js';
|
|
2
|
+
export { ParsemeConfig } from './config.js';
|
|
3
|
+
export type { GeneratorOptions, ContextOutput, ProjectInfo, FrameworkInfo, RouteInfo, FileAnalysis, GitInfo, } from './types.js';
|
|
4
|
+
export type { ParsemeConfigFile } from './config.js';
|
package/dist/index.js
ADDED
package/dist/prompt.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface PromptOptions {
|
|
2
|
+
message: string;
|
|
3
|
+
defaultValue?: string;
|
|
4
|
+
choices?: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function prompt(options: PromptOptions): Promise<string>;
|
|
7
|
+
export declare function confirmPrompt(message: string, defaultValue?: boolean): Promise<boolean>;
|
|
8
|
+
export {};
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
export async function prompt(options) {
|
|
3
|
+
const rl = createInterface({
|
|
4
|
+
input: process.stdin,
|
|
5
|
+
output: process.stdout,
|
|
6
|
+
});
|
|
7
|
+
return new Promise((resolve) => {
|
|
8
|
+
const { message, defaultValue, choices } = options;
|
|
9
|
+
let promptMessage = message;
|
|
10
|
+
if (choices) {
|
|
11
|
+
promptMessage += ` (${choices.join('/')})`;
|
|
12
|
+
}
|
|
13
|
+
if (defaultValue) {
|
|
14
|
+
promptMessage += ` [${defaultValue}]`;
|
|
15
|
+
}
|
|
16
|
+
promptMessage += ': ';
|
|
17
|
+
rl.question(promptMessage, (answer) => {
|
|
18
|
+
rl.close();
|
|
19
|
+
const trimmed = answer.trim();
|
|
20
|
+
if (!trimmed && defaultValue) {
|
|
21
|
+
resolve(defaultValue);
|
|
22
|
+
}
|
|
23
|
+
else if (choices && trimmed) {
|
|
24
|
+
const normalized = trimmed.toLowerCase();
|
|
25
|
+
const match = choices.find((choice) => choice.toLowerCase().startsWith(normalized));
|
|
26
|
+
resolve(match || trimmed);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
resolve(trimmed);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export async function confirmPrompt(message, defaultValue = true) {
|
|
35
|
+
const answer = await prompt({
|
|
36
|
+
message,
|
|
37
|
+
defaultValue: defaultValue ? 'y' : 'n',
|
|
38
|
+
choices: ['y', 'n', 'yes', 'no'],
|
|
39
|
+
});
|
|
40
|
+
const normalized = answer.toLowerCase();
|
|
41
|
+
return normalized === 'y' || normalized === 'yes';
|
|
42
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo } from './analyzers/pattern-detector.js';
|
|
2
|
+
export type { ServiceInfo, ModelInfo, ConfigInfo, MiddlewareInfo, UtilityInfo };
|
|
3
|
+
export interface GeneratorOptions {
|
|
4
|
+
rootDir?: string;
|
|
5
|
+
includeGitInfo?: boolean;
|
|
6
|
+
maxDepth?: number;
|
|
7
|
+
excludePatterns?: string[];
|
|
8
|
+
includePatterns?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ProjectInfo {
|
|
11
|
+
name: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
type: 'typescript' | 'javascript' | 'mixed';
|
|
15
|
+
category: ProjectCategory;
|
|
16
|
+
packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
17
|
+
framework?: FrameworkInfo;
|
|
18
|
+
buildTool?: BuildToolInfo;
|
|
19
|
+
dependencies: Record<string, string>;
|
|
20
|
+
devDependencies: Record<string, string>;
|
|
21
|
+
scripts?: Record<string, string>;
|
|
22
|
+
entryPoints?: string[];
|
|
23
|
+
outputTargets?: string[];
|
|
24
|
+
}
|
|
25
|
+
export type ProjectCategory = 'backend-api' | 'frontend-web' | 'frontend-mobile' | 'npm-package' | 'monorepo' | 'cli-tool' | 'desktop-app' | 'fullstack' | 'unknown';
|
|
26
|
+
export interface BuildToolInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
version?: string;
|
|
29
|
+
configFiles: string[];
|
|
30
|
+
features: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface FrameworkInfo {
|
|
33
|
+
name: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
features: string[];
|
|
36
|
+
routes?: RouteInfo[];
|
|
37
|
+
components?: ComponentInfo[];
|
|
38
|
+
}
|
|
39
|
+
export interface RouteInfo {
|
|
40
|
+
method: string;
|
|
41
|
+
path: string;
|
|
42
|
+
handler: string;
|
|
43
|
+
middleware?: string[];
|
|
44
|
+
file: string;
|
|
45
|
+
line: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ComponentInfo {
|
|
48
|
+
name: string;
|
|
49
|
+
file: string;
|
|
50
|
+
line: number;
|
|
51
|
+
}
|
|
52
|
+
export interface FileAnalysis {
|
|
53
|
+
path: string;
|
|
54
|
+
type: 'route' | 'middleware' | 'model' | 'service' | 'utility' | 'config' | 'test' | 'component';
|
|
55
|
+
framework?: string;
|
|
56
|
+
exports: string[];
|
|
57
|
+
imports: string[];
|
|
58
|
+
functions: string[];
|
|
59
|
+
classes: string[];
|
|
60
|
+
routes?: RouteInfo[];
|
|
61
|
+
components?: ComponentInfo[];
|
|
62
|
+
services?: ServiceInfo[];
|
|
63
|
+
models?: ModelInfo[];
|
|
64
|
+
configs?: ConfigInfo[];
|
|
65
|
+
middleware?: MiddlewareInfo[];
|
|
66
|
+
utilities?: UtilityInfo[];
|
|
67
|
+
}
|
|
68
|
+
export interface ContextOutput {
|
|
69
|
+
parseme: string;
|
|
70
|
+
context?: {
|
|
71
|
+
structure: string;
|
|
72
|
+
routes: string;
|
|
73
|
+
dependencies: string;
|
|
74
|
+
[key: string]: string;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export interface GitInfo {
|
|
78
|
+
branch: string;
|
|
79
|
+
lastCommit: string;
|
|
80
|
+
changedFiles: string[];
|
|
81
|
+
status: 'clean' | 'dirty';
|
|
82
|
+
}
|
package/dist/types.js
ADDED