vg-coder-cli 2.0.1 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +318 -92
- package/{vg/apps/api/src/assets/.gitkeep → change.sh} +0 -0
- package/package.json +4 -11
- package/src/index.js +0 -56
- package/src/server/views/dashboard.html +391 -613
- package/vg-coder-cli-1.0.17.tgz +0 -0
- package/vg-coder-cli-2.0.4.tgz +0 -0
- package/CHANGELOG.md +0 -59
- package/PUBLISHING.md +0 -86
- package/SYSTEM_PROMPT.md +0 -157
- package/init-nx-monorepo.sh +0 -107
- package/vg/.vscode/extensions.json +0 -8
- package/vg/.vscode/launch.json +0 -23
- package/vg/README.md +0 -85
- package/vg/apps/api/project.json +0 -83
- package/vg/apps/api/src/app/analyze.controller.ts +0 -17
- package/vg/apps/api/src/app/analyze.service.ts +0 -57
- package/vg/apps/api/src/app/app.controller.ts +0 -12
- package/vg/apps/api/src/app/app.module.ts +0 -29
- package/vg/apps/api/src/app/app.service.ts +0 -8
- package/vg/apps/api/src/app/clean.controller.ts +0 -40
- package/vg/apps/api/src/app/execute.controller.ts +0 -19
- package/vg/apps/api/src/app/execute.service.ts +0 -46
- package/vg/apps/api/src/app/info.controller.ts +0 -12
- package/vg/apps/api/src/app/info.service.ts +0 -65
- package/vg/apps/api/src/main.ts +0 -28
- package/vg/apps/api/webpack.config.js +0 -25
- package/vg/apps/api-e2e/jest.config.cts +0 -18
- package/vg/apps/api-e2e/project.json +0 -17
- package/vg/apps/api-e2e/src/support/global-setup.ts +0 -16
- package/vg/apps/api-e2e/src/support/global-teardown.ts +0 -10
- package/vg/apps/api-e2e/src/support/test-setup.ts +0 -9
- package/vg/apps/ng-app/jest.config.ts +0 -21
- package/vg/apps/ng-app/project.json +0 -110
- package/vg/apps/ng-app/proxy.conf.json +0 -8
- package/vg/apps/ng-app/public/favicon.ico +0 -0
- package/vg/apps/ng-app/src/app/app.config.ts +0 -17
- package/vg/apps/ng-app/src/app/app.html +0 -1
- package/vg/apps/ng-app/src/app/app.routes.ts +0 -7
- package/vg/apps/ng-app/src/app/app.scss +0 -0
- package/vg/apps/ng-app/src/app/app.ts +0 -12
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.html +0 -87
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.scss +0 -290
- package/vg/apps/ng-app/src/app/dashboard/dashboard.component.ts +0 -236
- package/vg/apps/ng-app/src/app/nx-welcome.ts +0 -872
- package/vg/apps/ng-app/src/app/services/api.service.ts +0 -28
- package/vg/apps/ng-app/src/index.html +0 -13
- package/vg/apps/ng-app/src/main.ts +0 -5
- package/vg/apps/ng-app/src/styles.scss +0 -1
- package/vg/apps/ng-app/src/test-setup.ts +0 -6
- package/vg/jest.config.ts +0 -6
- package/vg/nx.json +0 -85
- package/vg/package-lock.json +0 -30707
- package/vg/package.json +0 -75
- package/vg/packages/client/data-access/README.md +0 -7
- package/vg/packages/client/data-access/jest.config.ts +0 -21
- package/vg/packages/client/data-access/project.json +0 -21
- package/vg/packages/client/data-access/src/index.ts +0 -1
- package/vg/packages/client/data-access/src/lib/data-access/data-access.html +0 -1
- package/vg/packages/client/data-access/src/lib/data-access/data-access.scss +0 -0
- package/vg/packages/client/data-access/src/lib/data-access/data-access.ts +0 -9
- package/vg/packages/client/data-access/src/test-setup.ts +0 -6
- package/vg/packages/core/README.md +0 -11
- package/vg/packages/core/jest.config.ts +0 -10
- package/vg/packages/core/package.json +0 -11
- package/vg/packages/core/project.json +0 -26
- package/vg/packages/core/src/index.ts +0 -6
- package/vg/packages/core/src/lib/core.ts +0 -3
- package/vg/packages/core/src/lib/detectors/project-detector.ts +0 -343
- package/vg/packages/core/src/lib/ignore/ignore-manager.ts +0 -315
- package/vg/packages/core/src/lib/scanner/file-scanner.ts +0 -675
- package/vg/packages/core/src/lib/tokenizer/token-manager.ts +0 -435
- package/vg/packages/core/src/lib/utils/bash-executor.ts +0 -146
- package/vg/packages/shared/data-types/README.md +0 -11
- package/vg/packages/shared/data-types/jest.config.ts +0 -10
- package/vg/packages/shared/data-types/package.json +0 -11
- package/vg/packages/shared/data-types/project.json +0 -26
- package/vg/packages/shared/data-types/src/index.ts +0 -1
- package/vg/packages/shared/data-types/src/lib/data-types.ts +0 -3
- package/vg/start-dev.sh +0 -22
|
@@ -1,675 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs-extra';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { IgnoreManager } from '../ignore/ignore-manager';
|
|
4
|
-
|
|
5
|
-
export interface ScannerOptions {
|
|
6
|
-
maxFileSize?: number;
|
|
7
|
-
extensions?: string[];
|
|
8
|
-
includeHidden?: boolean;
|
|
9
|
-
maxDepth?: number;
|
|
10
|
-
[key: string]: any;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface FileNode {
|
|
14
|
-
path: string;
|
|
15
|
-
name: string;
|
|
16
|
-
size: number;
|
|
17
|
-
type: 'directory' | 'file';
|
|
18
|
-
extension?: string;
|
|
19
|
-
children?: FileNode[];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface FileContent {
|
|
23
|
-
path: string;
|
|
24
|
-
relativePath: string;
|
|
25
|
-
name: string;
|
|
26
|
-
extension?: string;
|
|
27
|
-
size: number;
|
|
28
|
-
content: string;
|
|
29
|
-
lines: number;
|
|
30
|
-
encoding: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ScanResult {
|
|
34
|
-
tree: FileNode;
|
|
35
|
-
files: FileContent[];
|
|
36
|
-
stats: {
|
|
37
|
-
totalFiles: number;
|
|
38
|
-
filteredFiles: number;
|
|
39
|
-
processedFiles: number;
|
|
40
|
-
scanTime: number;
|
|
41
|
-
projectPath: string;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Scanner to traverse and concatenate source code files
|
|
47
|
-
*/
|
|
48
|
-
export class FileScanner {
|
|
49
|
-
private options: ScannerOptions;
|
|
50
|
-
private ignoreManager: IgnoreManager;
|
|
51
|
-
|
|
52
|
-
constructor(private projectPath: string, options: ScannerOptions = {}) {
|
|
53
|
-
this.options = {
|
|
54
|
-
maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB default
|
|
55
|
-
extensions: options.extensions || this.getDefaultExtensions(),
|
|
56
|
-
includeHidden: options.includeHidden || false,
|
|
57
|
-
maxDepth: options.maxDepth || 10,
|
|
58
|
-
...options
|
|
59
|
-
};
|
|
60
|
-
this.ignoreManager = new IgnoreManager(projectPath);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get list of default extensions
|
|
65
|
-
*/
|
|
66
|
-
getDefaultExtensions(): string[] {
|
|
67
|
-
return [
|
|
68
|
-
// Web Frontend
|
|
69
|
-
'.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte',
|
|
70
|
-
'.html', '.htm', '.css', '.scss', '.sass', '.less',
|
|
71
|
-
|
|
72
|
-
// Backend
|
|
73
|
-
'.java', '.kt', '.scala', '.groovy',
|
|
74
|
-
'.py', '.rb', '.php', '.go', '.rs',
|
|
75
|
-
'.cs', '.vb', '.fs',
|
|
76
|
-
'.cpp', '.c', '.h', '.hpp',
|
|
77
|
-
|
|
78
|
-
// Config files
|
|
79
|
-
'.json', '.yaml', '.yml', '.toml', '.ini', '.conf',
|
|
80
|
-
'.xml', '.properties',
|
|
81
|
-
|
|
82
|
-
// Build files
|
|
83
|
-
'.gradle', '.maven', '.sbt',
|
|
84
|
-
|
|
85
|
-
// Scripts
|
|
86
|
-
'.sh', '.bat', '.ps1', '.cmd',
|
|
87
|
-
|
|
88
|
-
// Documentation
|
|
89
|
-
'.md', '.txt', '.rst',
|
|
90
|
-
|
|
91
|
-
// SQL
|
|
92
|
-
'.sql', '.ddl', '.dml'
|
|
93
|
-
];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Scan entire project and return file structure
|
|
98
|
-
*/
|
|
99
|
-
async scanProject(): Promise<ScanResult> {
|
|
100
|
-
const startTime = Date.now();
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
// Get directory structure
|
|
104
|
-
const tree = await this.buildFileTree();
|
|
105
|
-
|
|
106
|
-
// Extract files
|
|
107
|
-
const files = await this.extractFiles(tree);
|
|
108
|
-
|
|
109
|
-
// Filter files by ignore rules
|
|
110
|
-
const filteredFiles = await this.filterFiles(files);
|
|
111
|
-
|
|
112
|
-
// Read file contents
|
|
113
|
-
const filesWithContent = await this.readFilesContent(filteredFiles);
|
|
114
|
-
|
|
115
|
-
const endTime = Date.now();
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
tree,
|
|
119
|
-
files: filesWithContent,
|
|
120
|
-
stats: {
|
|
121
|
-
totalFiles: files.length,
|
|
122
|
-
filteredFiles: filteredFiles.length,
|
|
123
|
-
processedFiles: filesWithContent.length,
|
|
124
|
-
scanTime: endTime - startTime,
|
|
125
|
-
projectPath: this.projectPath
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
} catch (error: any) {
|
|
129
|
-
throw new Error(`Failed to scan project: ${error.message}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Build file tree
|
|
135
|
-
*/
|
|
136
|
-
async buildFileTree(): Promise<FileNode> {
|
|
137
|
-
// Use manual scanning instead of directory-tree to ensure all Java files are found
|
|
138
|
-
return await this.scanDirectoryManually(this.projectPath);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Manually scan directory to ensure all files are found
|
|
143
|
-
*/
|
|
144
|
-
async scanDirectoryManually(dirPath: string, relativePath = ''): Promise<FileNode> {
|
|
145
|
-
const stats = await fs.stat(dirPath);
|
|
146
|
-
const name = path.basename(dirPath);
|
|
147
|
-
|
|
148
|
-
const node: FileNode = {
|
|
149
|
-
path: dirPath,
|
|
150
|
-
name: name,
|
|
151
|
-
size: stats.size,
|
|
152
|
-
type: stats.isDirectory() ? 'directory' : 'file',
|
|
153
|
-
extension: stats.isFile() ? path.extname(name) : undefined
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
if (stats.isDirectory()) {
|
|
157
|
-
try {
|
|
158
|
-
const entries = await fs.readdir(dirPath);
|
|
159
|
-
const children: FileNode[] = [];
|
|
160
|
-
|
|
161
|
-
for (const entry of entries) {
|
|
162
|
-
const entryPath = path.join(dirPath, entry);
|
|
163
|
-
const entryRelativePath = path.join(relativePath, entry);
|
|
164
|
-
|
|
165
|
-
// Skip ignored directories
|
|
166
|
-
if (this.shouldIgnoreDirectoryName(entry)) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const childNode = await this.scanDirectoryManually(entryPath, entryRelativePath);
|
|
172
|
-
if (childNode) {
|
|
173
|
-
children.push(childNode);
|
|
174
|
-
}
|
|
175
|
-
} catch (error) {
|
|
176
|
-
// Skip files/directories that can't be accessed
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (children.length > 0) {
|
|
182
|
-
node.children = children;
|
|
183
|
-
}
|
|
184
|
-
} catch (error) {
|
|
185
|
-
// Skip directories that can't be read
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return node;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Get regex for build directories and hidden files to ignore
|
|
194
|
-
*/
|
|
195
|
-
getBuildIgnoreRegex(): RegExp {
|
|
196
|
-
// Create regex pattern for directories to ignore
|
|
197
|
-
const ignorePatterns = [
|
|
198
|
-
// Hidden directories (starting with .)
|
|
199
|
-
'^\\..*',
|
|
200
|
-
|
|
201
|
-
// Build directories
|
|
202
|
-
'^build$',
|
|
203
|
-
'^target$',
|
|
204
|
-
'^dist$',
|
|
205
|
-
'^out$',
|
|
206
|
-
'^bin$',
|
|
207
|
-
|
|
208
|
-
// Dependencies
|
|
209
|
-
'^node_modules$',
|
|
210
|
-
'^vendor$',
|
|
211
|
-
|
|
212
|
-
// Temporary files
|
|
213
|
-
'^tmp$',
|
|
214
|
-
'^temp$',
|
|
215
|
-
|
|
216
|
-
// Logs
|
|
217
|
-
'^logs$',
|
|
218
|
-
'^log$',
|
|
219
|
-
|
|
220
|
-
// Coverage reports
|
|
221
|
-
'^coverage$'
|
|
222
|
-
];
|
|
223
|
-
|
|
224
|
-
// Combine all patterns into one regex
|
|
225
|
-
const combinedPattern = ignorePatterns.join('|');
|
|
226
|
-
return new RegExp(`(${combinedPattern})`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Extract list of files from tree
|
|
231
|
-
*/
|
|
232
|
-
async extractFiles(tree: FileNode): Promise<any[]> {
|
|
233
|
-
const files: any[] = [];
|
|
234
|
-
|
|
235
|
-
const traverse = (node: FileNode, currentPath = '') => {
|
|
236
|
-
// Skip ignored directories
|
|
237
|
-
if (this.shouldIgnorePath(node.path || node.name)) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// If node has children, it's a directory
|
|
242
|
-
if (node.children) {
|
|
243
|
-
node.children.forEach(child => traverse(child, currentPath));
|
|
244
|
-
} else {
|
|
245
|
-
// If no children, it's a file
|
|
246
|
-
const relativePath = path.relative(this.projectPath, node.path);
|
|
247
|
-
|
|
248
|
-
// Skip files in ignored directories
|
|
249
|
-
if (!this.shouldIgnorePath(relativePath)) {
|
|
250
|
-
// Filter by extension
|
|
251
|
-
const extensions = this.options.extensions || this.getDefaultExtensions();
|
|
252
|
-
const hasValidExtension = extensions.some(ext =>
|
|
253
|
-
node.name.toLowerCase().endsWith(ext.toLowerCase())
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
if (hasValidExtension) {
|
|
257
|
-
files.push({
|
|
258
|
-
path: node.path,
|
|
259
|
-
relativePath: relativePath,
|
|
260
|
-
name: node.name,
|
|
261
|
-
extension: node.extension,
|
|
262
|
-
size: node.size
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
traverse(tree);
|
|
270
|
-
return files;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Check if path should be ignored
|
|
275
|
-
*/
|
|
276
|
-
shouldIgnorePath(filePath: string): boolean {
|
|
277
|
-
const pathParts = filePath.split(path.sep);
|
|
278
|
-
|
|
279
|
-
// Check each part of the path
|
|
280
|
-
for (const part of pathParts) {
|
|
281
|
-
if (this.shouldIgnoreDirectoryName(part)) {
|
|
282
|
-
return true;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return false;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Check if directory/file should be ignored
|
|
291
|
-
*/
|
|
292
|
-
shouldIgnoreDirectoryName(name: string): boolean {
|
|
293
|
-
// List of directories/files to ignore
|
|
294
|
-
const ignoreList = [
|
|
295
|
-
// Hidden directories (starting with .)
|
|
296
|
-
'.git', '.svn', '.hg',
|
|
297
|
-
'.gradle', '.maven', '.cache', '.npm',
|
|
298
|
-
'.idea', '.vscode', '.eclipse',
|
|
299
|
-
'.DS_Store', '.tmp',
|
|
300
|
-
|
|
301
|
-
// Build directories
|
|
302
|
-
'build', 'target', 'dist', 'out', 'bin',
|
|
303
|
-
|
|
304
|
-
// Dependencies
|
|
305
|
-
'node_modules', 'vendor',
|
|
306
|
-
|
|
307
|
-
// Temporary files
|
|
308
|
-
'tmp', 'temp',
|
|
309
|
-
|
|
310
|
-
// Logs
|
|
311
|
-
'logs', 'log',
|
|
312
|
-
|
|
313
|
-
// Coverage reports
|
|
314
|
-
'coverage', '.nyc_output'
|
|
315
|
-
];
|
|
316
|
-
|
|
317
|
-
// Check if name starts with . (hidden files/dirs)
|
|
318
|
-
if (name.startsWith('.')) {
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Check if name is in ignore list
|
|
323
|
-
return ignoreList.includes(name);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Filter files by ignore rules
|
|
328
|
-
*/
|
|
329
|
-
async filterFiles(files: any[]): Promise<any[]> {
|
|
330
|
-
const filtered: any[] = [];
|
|
331
|
-
|
|
332
|
-
for (const file of files) {
|
|
333
|
-
// Check size
|
|
334
|
-
if (this.options.maxFileSize && file.size > this.options.maxFileSize) {
|
|
335
|
-
continue;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Check ignore rules
|
|
339
|
-
const shouldIgnore = await this.ignoreManager.shouldIgnore(file.relativePath);
|
|
340
|
-
if (!shouldIgnore) {
|
|
341
|
-
filtered.push(file);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return filtered;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Read file contents
|
|
350
|
-
*/
|
|
351
|
-
async readFilesContent(files: any[]): Promise<FileContent[]> {
|
|
352
|
-
const filesWithContent: FileContent[] = [];
|
|
353
|
-
|
|
354
|
-
for (const file of files) {
|
|
355
|
-
try {
|
|
356
|
-
const content = await this.readFileContent(file.path);
|
|
357
|
-
if (content !== null) {
|
|
358
|
-
filesWithContent.push({
|
|
359
|
-
...file,
|
|
360
|
-
content,
|
|
361
|
-
lines: content.split('\n').length,
|
|
362
|
-
encoding: 'utf8'
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
} catch (error: any) {
|
|
366
|
-
// Log error but continue with other files
|
|
367
|
-
console.warn(`Warning: Could not read file ${file.relativePath}: ${error.message}`);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return filesWithContent;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Read content of a single file
|
|
376
|
-
*/
|
|
377
|
-
async readFileContent(filePath: string): Promise<string | null> {
|
|
378
|
-
try {
|
|
379
|
-
// Check if file is binary
|
|
380
|
-
if (await this.isBinaryFile(filePath)) {
|
|
381
|
-
return null;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const content = await fs.readFile(filePath, 'utf8');
|
|
385
|
-
return content;
|
|
386
|
-
} catch (error: any) {
|
|
387
|
-
if (error.code === 'ENOENT') {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
throw error;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Check if file is binary
|
|
396
|
-
*/
|
|
397
|
-
async isBinaryFile(filePath: string): Promise<boolean> {
|
|
398
|
-
try {
|
|
399
|
-
const buffer = await fs.readFile(filePath);
|
|
400
|
-
|
|
401
|
-
// Check first 1024 bytes
|
|
402
|
-
const chunk = buffer.slice(0, 1024);
|
|
403
|
-
|
|
404
|
-
// If null bytes found, likely binary
|
|
405
|
-
for (let i = 0; i < chunk.length; i++) {
|
|
406
|
-
if (chunk[i] === 0) {
|
|
407
|
-
return true;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
return false;
|
|
412
|
-
} catch (error) {
|
|
413
|
-
return true; // Assume binary if can't read
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Create combined content from all files
|
|
419
|
-
*/
|
|
420
|
-
async createCombinedContent(files: FileContent[], options: any = {}): Promise<string> {
|
|
421
|
-
const {
|
|
422
|
-
includeStats = true,
|
|
423
|
-
includeTree = true,
|
|
424
|
-
headerTemplate = this.getDefaultHeaderTemplate(),
|
|
425
|
-
separatorTemplate = this.getDefaultSeparatorTemplate()
|
|
426
|
-
} = options;
|
|
427
|
-
|
|
428
|
-
let content = '';
|
|
429
|
-
|
|
430
|
-
// Header with project info
|
|
431
|
-
if (includeStats) {
|
|
432
|
-
content += this.generateProjectHeader(files);
|
|
433
|
-
content += '\n\n';
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Directory structure
|
|
437
|
-
if (includeTree) {
|
|
438
|
-
content += this.generateTreeStructure(files);
|
|
439
|
-
content += '\n\n';
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// File contents
|
|
443
|
-
for (let i = 0; i < files.length; i++) {
|
|
444
|
-
const file = files[i];
|
|
445
|
-
|
|
446
|
-
// File header
|
|
447
|
-
content += headerTemplate
|
|
448
|
-
.replace('{path}', file.relativePath)
|
|
449
|
-
.replace('{name}', file.name)
|
|
450
|
-
.replace('{extension}', file.extension || '')
|
|
451
|
-
.replace('{size}', file.size)
|
|
452
|
-
.replace('{lines}', file.lines);
|
|
453
|
-
|
|
454
|
-
content += '\n';
|
|
455
|
-
content += file.content;
|
|
456
|
-
content += '\n';
|
|
457
|
-
|
|
458
|
-
// Separator between files
|
|
459
|
-
if (i < files.length - 1) {
|
|
460
|
-
content += separatorTemplate;
|
|
461
|
-
content += '\n';
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
return content;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Default file header template
|
|
470
|
-
*/
|
|
471
|
-
getDefaultHeaderTemplate(): string {
|
|
472
|
-
return `
|
|
473
|
-
================================================================================
|
|
474
|
-
File: {path}
|
|
475
|
-
Size: {size} bytes | Lines: {lines}
|
|
476
|
-
================================================================================`;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/**
|
|
480
|
-
* Default separator template
|
|
481
|
-
*/
|
|
482
|
-
getDefaultSeparatorTemplate(): string {
|
|
483
|
-
return '\n\n';
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Create combined content for AI tools with correct formatting
|
|
488
|
-
*/
|
|
489
|
-
async createCombinedContentForAI(files: FileContent[], options: any = {}): Promise<string> {
|
|
490
|
-
const {
|
|
491
|
-
includeStats = false,
|
|
492
|
-
includeTree = false,
|
|
493
|
-
preserveLineNumbers = true
|
|
494
|
-
} = options;
|
|
495
|
-
|
|
496
|
-
let content = '';
|
|
497
|
-
|
|
498
|
-
// Header with project info (optional)
|
|
499
|
-
if (includeStats) {
|
|
500
|
-
content += this.generateProjectHeader(files);
|
|
501
|
-
content += '\n\n';
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// Directory structure (optional)
|
|
505
|
-
if (includeTree) {
|
|
506
|
-
content += this.generateTreeStructure(files);
|
|
507
|
-
content += '\n\n';
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// File contents with correct formatting
|
|
511
|
-
for (let i = 0; i < files.length; i++) {
|
|
512
|
-
const file = files[i];
|
|
513
|
-
|
|
514
|
-
// File boundary marker - does not affect line numbering
|
|
515
|
-
content += `// ===== FILE: ${file.relativePath} =====\n`;
|
|
516
|
-
|
|
517
|
-
// Original file content
|
|
518
|
-
content += file.content;
|
|
519
|
-
|
|
520
|
-
// Ensure file ends with newline
|
|
521
|
-
if (!file.content.endsWith('\n')) {
|
|
522
|
-
content += '\n';
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Separator between files
|
|
526
|
-
if (i < files.length - 1) {
|
|
527
|
-
content += '\n';
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
return content;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* Generate project info header
|
|
536
|
-
*/
|
|
537
|
-
generateProjectHeader(files: FileContent[]): string {
|
|
538
|
-
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
|
539
|
-
const totalLines = files.reduce((sum, file) => sum + file.lines, 0);
|
|
540
|
-
|
|
541
|
-
return `
|
|
542
|
-
# Project Analysis Report
|
|
543
|
-
Generated: ${new Date().toISOString()}
|
|
544
|
-
Project Path: ${this.projectPath}
|
|
545
|
-
|
|
546
|
-
## Statistics
|
|
547
|
-
- Total Files: ${files.length}
|
|
548
|
-
- Total Size: ${this.formatBytes(totalSize)}
|
|
549
|
-
- Total Lines: ${totalLines.toLocaleString()}
|
|
550
|
-
- Extensions: ${[...new Set(files.map(f => f.extension))].filter(Boolean).join(', ')}
|
|
551
|
-
`;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Generate tree structure
|
|
556
|
-
*/
|
|
557
|
-
generateTreeStructure(files: FileContent[]): string {
|
|
558
|
-
const tree: any = {};
|
|
559
|
-
|
|
560
|
-
// Build tree structure
|
|
561
|
-
files.forEach(file => {
|
|
562
|
-
const parts = file.relativePath.split(path.sep);
|
|
563
|
-
let current = tree;
|
|
564
|
-
|
|
565
|
-
parts.forEach((part, index) => {
|
|
566
|
-
if (index === parts.length - 1) {
|
|
567
|
-
// File
|
|
568
|
-
current[part] = file;
|
|
569
|
-
} else {
|
|
570
|
-
// Directory
|
|
571
|
-
if (!current[part]) {
|
|
572
|
-
current[part] = {};
|
|
573
|
-
}
|
|
574
|
-
current = current[part];
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
});
|
|
578
|
-
|
|
579
|
-
// Generate tree string
|
|
580
|
-
let treeStr = '\n## Project Structure\n```\n';
|
|
581
|
-
treeStr += this.renderTree(tree, '', true);
|
|
582
|
-
treeStr += '```\n';
|
|
583
|
-
|
|
584
|
-
return treeStr;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* Render tree structure
|
|
589
|
-
*/
|
|
590
|
-
renderTree(node: any, prefix = '', isLast = true): string {
|
|
591
|
-
let result = '';
|
|
592
|
-
const entries = Object.entries(node);
|
|
593
|
-
|
|
594
|
-
entries.forEach(([name, value]: [string, any], index) => {
|
|
595
|
-
const isLastEntry = index === entries.length - 1;
|
|
596
|
-
const connector = isLastEntry ? '└── ' : '├── ';
|
|
597
|
-
|
|
598
|
-
result += prefix + connector + name + '\n';
|
|
599
|
-
|
|
600
|
-
if (typeof value === 'object' && !value.relativePath) {
|
|
601
|
-
// Directory
|
|
602
|
-
const newPrefix = prefix + (isLastEntry ? ' ' : '│ ');
|
|
603
|
-
result += this.renderTree(value, newPrefix, isLastEntry);
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
return result;
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
/**
|
|
611
|
-
* Render project tree structure
|
|
612
|
-
*/
|
|
613
|
-
renderProjectTree(tree: FileNode, maxDepth = 50): string {
|
|
614
|
-
if (!tree) return 'No tree structure available';
|
|
615
|
-
|
|
616
|
-
const renderNode = (node: FileNode, prefix = '', depth = 0): string => {
|
|
617
|
-
if (depth > maxDepth) return '';
|
|
618
|
-
|
|
619
|
-
let result = '';
|
|
620
|
-
|
|
621
|
-
if (node.children && node.children.length > 0) {
|
|
622
|
-
// Filter out ignored directories
|
|
623
|
-
const filteredChildren = node.children.filter(child =>
|
|
624
|
-
!this.shouldIgnoreDirectoryName(child.name)
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
// Sort children: directories first, then files
|
|
628
|
-
filteredChildren.sort((a, b) => {
|
|
629
|
-
const aIsDir = a.children && a.children.length > 0;
|
|
630
|
-
const bIsDir = b.children && b.children.length > 0;
|
|
631
|
-
if (aIsDir && !bIsDir) return -1;
|
|
632
|
-
if (!aIsDir && bIsDir) return 1;
|
|
633
|
-
return a.name.localeCompare(b.name);
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
filteredChildren.forEach((child, index) => {
|
|
637
|
-
const isLast = index === filteredChildren.length - 1;
|
|
638
|
-
const connector = isLast ? '└── ' : '├── ';
|
|
639
|
-
const nextPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
640
|
-
const isDirectory = child.children !== undefined;
|
|
641
|
-
|
|
642
|
-
result += `${prefix}${connector}${child.name}`;
|
|
643
|
-
if (!isDirectory) {
|
|
644
|
-
// It's a file, show extension
|
|
645
|
-
result += ` (${child.extension || 'file'})`;
|
|
646
|
-
}
|
|
647
|
-
result += '\n';
|
|
648
|
-
|
|
649
|
-
if (isDirectory) {
|
|
650
|
-
// Always render all children, no depth limit for full structure
|
|
651
|
-
result += renderNode(child, nextPrefix, depth + 1);
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
return result;
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
let result = `${tree.name}/\n`;
|
|
660
|
-
result += renderNode(tree);
|
|
661
|
-
|
|
662
|
-
return result;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
/**
|
|
666
|
-
* Format bytes to human readable
|
|
667
|
-
*/
|
|
668
|
-
formatBytes(bytes: number): string {
|
|
669
|
-
if (bytes === 0) return '0 Bytes';
|
|
670
|
-
const k = 1024;
|
|
671
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
672
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
673
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
674
|
-
}
|
|
675
|
-
}
|