@parseme/cli 0.0.2 → 0.0.3

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.
@@ -3,11 +3,11 @@ import type { GitInfo } from '../types.js';
3
3
  export declare class GitAnalyzer {
4
4
  private readonly config;
5
5
  constructor(config: ParsemeConfig);
6
- analyze(rootDir: string): Promise<GitInfo | undefined>;
6
+ analyze(rootDir: string): Promise<GitInfo | null>;
7
7
  private getCurrentBranch;
8
8
  private getLastCommit;
9
9
  private getStatus;
10
10
  private getChangedFiles;
11
- getFileLastModified(filePath: string, rootDir: string): Promise<string | undefined>;
12
- getFileHistory(filePath: string, rootDir: string, limit?: number): Promise<string[]>;
11
+ private getOrigin;
12
+ private getDiffStat;
13
13
  }
@@ -10,22 +10,26 @@ export class GitAnalyzer {
10
10
  try {
11
11
  // Check if this is a git repository
12
12
  await execAsync('git rev-parse --git-dir', { cwd: rootDir });
13
- const [branch, lastCommit, status, changedFiles] = await Promise.all([
13
+ const [branch, lastCommit, status, changedFiles, origin, diffStat] = await Promise.all([
14
14
  this.getCurrentBranch(rootDir),
15
15
  this.getLastCommit(rootDir),
16
16
  this.getStatus(rootDir),
17
17
  this.getChangedFiles(rootDir),
18
+ this.getOrigin(rootDir),
19
+ this.getDiffStat(rootDir),
18
20
  ]);
19
21
  return {
20
22
  branch,
21
23
  lastCommit,
22
24
  status,
23
25
  changedFiles,
26
+ origin,
27
+ diffStat,
24
28
  };
25
29
  }
26
30
  catch {
27
31
  // Not a git repository or git not available
28
- return undefined;
32
+ return null;
29
33
  }
30
34
  }
31
35
  async getCurrentBranch(rootDir) {
@@ -67,26 +71,22 @@ export class GitAnalyzer {
67
71
  return [];
68
72
  }
69
73
  }
70
- async getFileLastModified(filePath, rootDir) {
74
+ async getOrigin(rootDir) {
71
75
  try {
72
- const { stdout } = await execAsync(`git log -1 --format="%H %ai" -- "${filePath}"`, {
73
- cwd: rootDir,
74
- });
76
+ const { stdout } = await execAsync('git remote get-url origin', { cwd: rootDir });
75
77
  return stdout.trim();
76
78
  }
77
79
  catch {
78
80
  return undefined;
79
81
  }
80
82
  }
81
- async getFileHistory(filePath, rootDir, limit = 5) {
83
+ async getDiffStat(rootDir) {
82
84
  try {
83
- const { stdout } = await execAsync(`git log --oneline -${limit} -- "${filePath}"`, {
84
- cwd: rootDir,
85
- });
86
- return stdout.split('\n').filter((line) => line.trim());
85
+ const { stdout } = await execAsync('git diff --stat', { cwd: rootDir });
86
+ return stdout.trim();
87
87
  }
88
88
  catch {
89
- return [];
89
+ return undefined;
90
90
  }
91
91
  }
92
92
  }
@@ -3,7 +3,7 @@ import type { ContextOutput, ProjectInfo, FileAnalysis, GitInfo, GeneratorOption
3
3
  interface BuildContext {
4
4
  projectInfo: ProjectInfo;
5
5
  fileAnalyses: FileAnalysis[];
6
- gitInfo?: GitInfo;
6
+ gitInfo?: GitInfo | null;
7
7
  options: GeneratorOptions;
8
8
  contextDir?: string;
9
9
  outputPath?: string;
@@ -26,6 +26,7 @@ export declare class ContextBuilder {
26
26
  private buildGitSection;
27
27
  private buildFooter;
28
28
  private buildSummarySection;
29
+ private buildFilesList;
29
30
  private buildDetailedStructure;
30
31
  private buildDetailedRoutes;
31
32
  private buildDetailedDependencies;
@@ -76,17 +76,36 @@ 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: '',
@@ -104,14 +123,16 @@ export class ContextBuilder {
104
123
  });
105
124
  }
106
125
  };
107
- // Detailed structure
126
+ // Files list (markdown)
127
+ contextFiles.files = this.buildFilesList(limitedFileAnalyses);
128
+ // Detailed structure (JSON with AST)
108
129
  const structureResult = this.buildDetailedStructure(limitedFileAnalyses);
109
130
  mergeSplitFiles(structureResult, 'structure');
110
131
  // Routes documentation
111
132
  const routes = limitedFileAnalyses.flatMap((f) => f.routes || []);
112
133
  if (routes.length > 0) {
113
134
  const routesResult = this.buildDetailedRoutes(routes, limitedFileAnalyses);
114
- mergeSplitFiles(routesResult, 'routes');
135
+ mergeSplitFiles(routesResult, 'api-endpoints');
115
136
  }
116
137
  // Dependencies
117
138
  const dependenciesResult = this.buildDetailedDependencies(projectInfo);
@@ -124,28 +145,54 @@ export class ContextBuilder {
124
145
  }
125
146
  // Git information
126
147
  if (gitInfo) {
127
- const gitResult = this.buildDetailedGit(gitInfo);
128
- mergeSplitFiles(gitResult, 'git');
148
+ contextFiles.gitDiff = this.buildDetailedGit(gitInfo);
129
149
  }
130
150
  return {
131
151
  parseme: mainContent,
132
152
  context: contextFiles,
133
153
  };
134
154
  }
135
- buildHeader(projectInfo) {
136
- return `# ${projectInfo.name}
137
-
138
- *AI-Generated Project Context*
155
+ buildHeader(linkPath) {
156
+ return `## PARSEME - AI Agent Context
157
+ Auto-generated project summary optimized for AI coding agents. This file provides complete project context without requiring full codebase traversal, designed for token efficiency.
139
158
 
140
- ${projectInfo.description || 'No description available.'}`;
159
+ **Usage Instructions for AI Agents:**
160
+ 1. Read this PARSEME.md file completely first before accessing individual project files
161
+ 2. Basic project information, script availability and dependency information provides basic understanding of code base and tech stack without checking package.json
162
+ 3. Use the provided file list (${linkPath}/files.md) to see all tracked files in the project
163
+ 4. Utilize the structure and AST data (${linkPath}/structure.json) for code analysis without manual parsing
164
+ 5. Follow the instruction in the "Git Information" section of this file to validate the actuality of the provided information.
165
+ 6. Only dive deeper into specific files after reviewing this summary, that replaces the need for initial project exploration and significantly reduces token usage for project comprehension.`;
141
166
  }
142
167
  buildProjectOverview(projectInfo) {
143
- return `## Project Overview
168
+ let content = `## Basic Project Information
144
169
 
145
- - **Type**: ${projectInfo.type} project
146
- - **Package Manager**: ${projectInfo.packageManager}
147
- - **Framework**: ${projectInfo.framework?.name || 'None detected'}
148
- - **Version**: ${projectInfo.version || 'Not specified'}`;
170
+ **Project:** ${projectInfo.name}${projectInfo.version ? ` v${projectInfo.version}` : ''}
171
+ **Description:** ${projectInfo.description || 'No description available.'}
172
+ **Type:** ${projectInfo.type} project
173
+ **Package Manager:** ${projectInfo.packageManager}
174
+ **Framework:** ${projectInfo.framework?.name || 'None detected'}`;
175
+ // Add main entry point if available
176
+ if (projectInfo.entryPoints && projectInfo.entryPoints.length > 0) {
177
+ content += `\n**Main Entry Point:** ${projectInfo.entryPoints[0]}`;
178
+ }
179
+ content += '\n';
180
+ // Add dependencies
181
+ const deps = Object.keys(projectInfo.dependencies);
182
+ if (deps.length > 0) {
183
+ content += '\n\n### Dependencies\n';
184
+ deps.forEach((dep) => {
185
+ content += `- ${dep}\n`;
186
+ });
187
+ }
188
+ // Add available scripts
189
+ if (projectInfo.scripts && Object.keys(projectInfo.scripts).length > 0) {
190
+ content += '\n### Available Scripts\n';
191
+ Object.entries(projectInfo.scripts).forEach(([name, script]) => {
192
+ content += `- **${name}**: \`${script}\`\n`;
193
+ });
194
+ }
195
+ return content;
149
196
  }
150
197
  buildFrameworkSection(framework) {
151
198
  if (!framework) {
@@ -212,42 +259,47 @@ ${structure}`;
212
259
  buildGitSection(gitInfo) {
213
260
  return `## Git Information
214
261
 
215
- - **Branch**: ${gitInfo.branch}
216
- - **Status**: ${gitInfo.status}
217
- - **Last Commit**: ${gitInfo.lastCommit}
218
- ${gitInfo.changedFiles.length > 0 ? `- **Changed Files**: ${gitInfo.changedFiles.join(', ')}` : ''}`;
262
+ **State when PARSEME.md and all linked files were automatically generated:**
263
+
264
+ - **Branch:** ${gitInfo.branch}
265
+ - **Commit:** ${gitInfo.lastCommit}${gitInfo.origin ? `\n- **Origin:** ${gitInfo.origin}` : ''}
266
+
267
+ ### Git Diff Statistics
268
+ Git diff statistics from the time of generation are available at \`parseme-context/gitDiff.md\`.
269
+
270
+ **AI Agent Command:** To check for changes since generation, run:
271
+ \`\`\`bash
272
+ git diff --stat
273
+ \`\`\`
274
+ Compare the output with the baseline in \`parseme-context/gitDiff.json\` to detect any modifications.`;
219
275
  }
220
276
  buildFooter() {
221
277
  return `---
222
278
 
223
279
  *Generated by PARSEME v1.0.0 on ${new Date().toISOString().split('T')[0]}*`;
224
280
  }
225
- buildSummarySection(context) {
226
- const { fileAnalyses, contextDir, outputPath } = context;
227
- const totalFiles = fileAnalyses.length;
281
+ buildSummarySection(context, linkPath) {
282
+ const { fileAnalyses } = context;
228
283
  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
284
+ let content = `## Project Files
285
+ A complete list of all tracked files in the project is available at \`${linkPath}/files.md\`. This list excludes files ignored by git and provides a quick overview of the project structure.
247
286
 
248
- This project contains ${totalFiles} analyzed files${routes > 0 ? ` with ${routes} API endpoints` : ''}.
249
287
 
250
- For detailed information, see the files in the \`${linkPath}/\` directory.`;
288
+ ## Project Structure & AST
289
+ Detailed structure and Abstract Syntax Tree data for all tracked files is available at \`${linkPath}/structure.json\`. This includes file paths, types, imports, exports, functions, classes, interfaces, and routes for comprehensive code analysis without manual parsing.`;
290
+ if (routes > 0) {
291
+ content += `\n\n\n## API Endpoints
292
+ A comprehensive list of all discovered API endpoints is available at \`${linkPath}/api-endpoints.json\`. This includes HTTP methods, paths, handler names, and source file locations for backend routes (Express, NestJS, and decorator-based routing).`;
293
+ }
294
+ return content;
295
+ }
296
+ buildFilesList(fileAnalyses) {
297
+ let content = `# Project Files\n\n`;
298
+ content += `This is a complete list of all analyzed files in the project.\n\n`;
299
+ fileAnalyses.forEach((file) => {
300
+ content += `- ${file.path}\n`;
301
+ });
302
+ return content;
251
303
  }
252
304
  buildDetailedStructure(fileAnalyses) {
253
305
  const structureData = fileAnalyses.map((file) => ({
@@ -317,16 +369,18 @@ For detailed information, see the files in the \`${linkPath}/\` directory.`;
317
369
  return content;
318
370
  }
319
371
  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;
372
+ let content = `# Git Diff Statistics
373
+ # Generated at time of PARSEME.md creation
374
+ # To check for changes since then, run: git diff --stat
375
+ # Compare the output with the content below
376
+
377
+ `;
378
+ if (gitInfo.diffStat && gitInfo.diffStat.length > 0) {
379
+ content += gitInfo.diffStat;
329
380
  }
330
- return result;
381
+ else {
382
+ content += 'No changes detected at time of generation.';
383
+ }
384
+ return content;
331
385
  }
332
386
  }
package/dist/cli.js CHANGED
File without changes
package/dist/config.js CHANGED
@@ -191,7 +191,8 @@ export default config;
191
191
  return pattern + '**';
192
192
  }
193
193
  if (!pattern.includes('/') && !pattern.includes('*')) {
194
- return '**/' + pattern;
194
+ // Convert simple names to match directory patterns
195
+ return pattern + '/**';
195
196
  }
196
197
  return pattern;
197
198
  });
package/dist/generator.js CHANGED
@@ -40,7 +40,7 @@ export class ParsemeGenerator {
40
40
  // Step 4: Get git information if enabled
41
41
  const gitInfo = configData.includeGitInfo
42
42
  ? await this.gitAnalyzer.analyze(configData.rootDir)
43
- : undefined;
43
+ : null;
44
44
  // Calculate final output path for link generation
45
45
  const finalOutputPath = outputPath || configData.outputPath || join(configData.rootDir, 'PARSEME.md');
46
46
  // Step 5: Build the context output
@@ -81,7 +81,9 @@ export class ParsemeGenerator {
81
81
  await writeFile(finalOutputPath, context.parseme);
82
82
  if (context.context) {
83
83
  for (const [filename, content] of Object.entries(context.context)) {
84
- await writeFile(join(parsemeDir, `${filename}.json`), content);
84
+ // Use .md extension for markdown files, .json for others
85
+ const extension = filename === 'gitDiff' || filename === 'files' ? '.md' : '.json';
86
+ await writeFile(join(parsemeDir, `${filename}${extension}`), content);
85
87
  }
86
88
  }
87
89
  }
package/dist/types.d.ts CHANGED
@@ -79,4 +79,6 @@ export interface GitInfo {
79
79
  lastCommit: string;
80
80
  changedFiles: string[];
81
81
  status: 'clean' | 'dirty';
82
+ origin?: string;
83
+ diffStat?: string;
82
84
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parseme/cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Adds a PARSEME.md file—a README.md for AI agents—to your project, providing context and structure for automated tools.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",