codesummary 1.1.1 → 1.2.1
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/CHANGELOG.md +234 -190
- package/LICENSE +674 -674
- package/README.md +483 -607
- package/bin/codesummary.js +12 -12
- package/features.md +418 -502
- package/package.json +95 -95
- package/rag-schema.json +113 -113
- package/src/cli.js +599 -540
- package/src/configManager.js +880 -827
- package/src/errorHandler.js +474 -477
- package/src/index.js +25 -25
- package/src/llmGenerator.js +189 -0
- package/src/pdfGenerator.js +408 -475
- package/src/ragConfig.js +369 -373
- package/src/ragGenerator.js +1739 -1757
- package/src/scanner.js +386 -467
- package/src/utils.js +139 -0
package/src/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import CLI from './cli.js';
|
|
4
|
-
import ErrorHandler from './errorHandler.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* CodeSummary - Main Entry Point
|
|
8
|
-
* A cross-platform CLI tool for generating PDF documentation from source code
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
async function main() {
|
|
12
|
-
try {
|
|
13
|
-
// Setup global error handlers and validate environment
|
|
14
|
-
ErrorHandler.setupGlobalHandlers();
|
|
15
|
-
await ErrorHandler.validateEnvironment();
|
|
16
|
-
|
|
17
|
-
const cli = new CLI();
|
|
18
|
-
const args = process.argv.slice(2);
|
|
19
|
-
await cli.run(args);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
ErrorHandler.handleError(error, 'Main Application');
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Execute main function
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import CLI from './cli.js';
|
|
4
|
+
import ErrorHandler from './errorHandler.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CodeSummary - Main Entry Point
|
|
8
|
+
* A cross-platform CLI tool for generating PDF documentation from source code
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
try {
|
|
13
|
+
// Setup global error handlers and validate environment
|
|
14
|
+
ErrorHandler.setupGlobalHandlers();
|
|
15
|
+
await ErrorHandler.validateEnvironment();
|
|
16
|
+
|
|
17
|
+
const cli = new CLI();
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
await cli.run(args);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
ErrorHandler.handleError(error, 'Main Application');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Execute main function
|
|
26
26
|
main();
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { formatFileSize } from './utils.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* LLM Generator for CodeSummary
|
|
7
|
+
* Generates a single Markdown file optimised for direct consumption by LLMs.
|
|
8
|
+
* Applies lossless content optimisations to reduce token count.
|
|
9
|
+
*/
|
|
10
|
+
export class LlmGenerator {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.stats = {
|
|
13
|
+
filesProcessed: 0,
|
|
14
|
+
filesSkipped: 0,
|
|
15
|
+
startTime: null,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Generate an LLM-optimised Markdown file
|
|
21
|
+
* @param {object} filesByExtension - Files grouped by extension
|
|
22
|
+
* @param {Array} selectedExtensions - Extensions selected by user
|
|
23
|
+
* @param {string} outputPath - Output .md file path
|
|
24
|
+
* @param {string} projectName - Project name
|
|
25
|
+
* @returns {Promise<object>} Result with outputPath and stats
|
|
26
|
+
*/
|
|
27
|
+
async generateLlmOutput(filesByExtension, selectedExtensions, outputPath, projectName) {
|
|
28
|
+
this.stats.startTime = Date.now();
|
|
29
|
+
|
|
30
|
+
// Collect and sort all selected files
|
|
31
|
+
const allFiles = [];
|
|
32
|
+
for (const ext of selectedExtensions) {
|
|
33
|
+
for (const file of (filesByExtension[ext] || [])) {
|
|
34
|
+
allFiles.push(file);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
allFiles.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
38
|
+
|
|
39
|
+
const stream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
|
|
40
|
+
|
|
41
|
+
await this.writeLine(stream, this.buildHeader(projectName, allFiles));
|
|
42
|
+
await this.writeLine(stream, this.buildFileTree(allFiles));
|
|
43
|
+
|
|
44
|
+
for (const file of allFiles) {
|
|
45
|
+
const block = await this.buildFileBlock(file);
|
|
46
|
+
await this.writeLine(stream, block);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await new Promise((resolve, reject) => {
|
|
50
|
+
stream.end();
|
|
51
|
+
stream.on('finish', resolve);
|
|
52
|
+
stream.on('error', reject);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const duration = (Date.now() - this.stats.startTime) / 1000;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
outputPath,
|
|
59
|
+
totalFiles: this.stats.filesProcessed,
|
|
60
|
+
skippedFiles: this.stats.filesSkipped,
|
|
61
|
+
duration,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Private helpers
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
writeLine(stream, text) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const ok = stream.write(text);
|
|
72
|
+
if (!ok) {
|
|
73
|
+
stream.once('drain', resolve);
|
|
74
|
+
} else {
|
|
75
|
+
resolve();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
buildHeader(projectName, allFiles) {
|
|
81
|
+
const totalSize = allFiles.reduce((sum, f) => sum + (f.size || 0), 0);
|
|
82
|
+
const date = new Date().toISOString().split('T')[0];
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
`# ${projectName} — Code Summary\n\n` +
|
|
86
|
+
`**Generated:** ${date} | ` +
|
|
87
|
+
`**Files:** ${allFiles.length} | ` +
|
|
88
|
+
`**Total size:** ${formatFileSize(totalSize)}\n\n` +
|
|
89
|
+
`---\n\n`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
buildFileTree(allFiles) {
|
|
94
|
+
const lines = allFiles.map(f => ` ${f.relativePath}`).join('\n');
|
|
95
|
+
return `## File Tree\n\n\`\`\`\n${lines}\n\`\`\`\n\n---\n\n`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async buildFileBlock(file) {
|
|
99
|
+
try {
|
|
100
|
+
const raw = await fs.readFile(file.absolutePath, 'utf8');
|
|
101
|
+
const ext = path.extname(file.relativePath).toLowerCase();
|
|
102
|
+
const optimized = this.optimizeContent(raw, ext);
|
|
103
|
+
const lang = this.fenceLang(ext);
|
|
104
|
+
const fence = this.fence(optimized);
|
|
105
|
+
|
|
106
|
+
this.stats.filesProcessed++;
|
|
107
|
+
return `## ${file.relativePath}\n\n${fence}${lang}\n${optimized}\n${fence}\n\n`;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.stats.filesSkipped++;
|
|
110
|
+
return `## ${file.relativePath}\n\n> Error: ${error.message}\n\n`;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Apply lossless content optimisations
|
|
116
|
+
* @param {string} content - Raw file content
|
|
117
|
+
* @param {string} ext - File extension (e.g. ".js")
|
|
118
|
+
* @returns {string} Optimised content
|
|
119
|
+
*/
|
|
120
|
+
optimizeContent(content, ext) {
|
|
121
|
+
// 1. Normalize line endings
|
|
122
|
+
let result = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
123
|
+
|
|
124
|
+
// 2. Trim trailing whitespace on every line (safe for all languages)
|
|
125
|
+
result = result.split('\n').map(line => line.trimEnd()).join('\n');
|
|
126
|
+
|
|
127
|
+
// 3. Compact JSON (parse → re-serialise without indentation)
|
|
128
|
+
if (ext === '.json') {
|
|
129
|
+
try {
|
|
130
|
+
result = JSON.stringify(JSON.parse(result));
|
|
131
|
+
return result; // Already single-line, no further blank-line processing needed
|
|
132
|
+
} catch {
|
|
133
|
+
// Invalid / JSON5 / JSONC — fall through to generic processing
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 4. Remove leading and trailing blank lines from the file
|
|
138
|
+
result = result.trim();
|
|
139
|
+
|
|
140
|
+
// 5. Collapse consecutive blank lines
|
|
141
|
+
// Markdown: max 2 consecutive blank lines (paragraph semantics preserved)
|
|
142
|
+
// Everything else: max 1 consecutive blank line
|
|
143
|
+
if (ext === '.md' || ext === '.mdx') {
|
|
144
|
+
result = result.replace(/\n{4,}/g, '\n\n\n');
|
|
145
|
+
} else {
|
|
146
|
+
result = result.replace(/\n{3,}/g, '\n\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Map a file extension to a Markdown fence language identifier
|
|
154
|
+
*/
|
|
155
|
+
fenceLang(ext) {
|
|
156
|
+
const map = {
|
|
157
|
+
'.js': 'js', '.jsx': 'jsx', '.ts': 'ts', '.tsx': 'tsx',
|
|
158
|
+
'.py': 'python', '.java': 'java', '.cs': 'csharp', '.cpp': 'cpp',
|
|
159
|
+
'.c': 'c', '.h': 'c', '.html': 'html', '.css': 'css',
|
|
160
|
+
'.scss': 'scss', '.sass': 'sass', '.json': 'json', '.yaml': 'yaml',
|
|
161
|
+
'.yml': 'yaml', '.xml': 'xml', '.md': 'markdown', '.mdx': 'mdx',
|
|
162
|
+
'.txt': '', '.sh': 'bash', '.bat': 'bat', '.ps1': 'powershell',
|
|
163
|
+
'.sql': 'sql', '.graphql': 'graphql', '.gql': 'graphql', '.proto': 'protobuf',
|
|
164
|
+
'.toml': 'toml', '.ini': 'ini', '.properties': 'properties',
|
|
165
|
+
'.tf': 'hcl', '.tfvars': 'hcl', '.prisma': 'prisma',
|
|
166
|
+
'.dart': 'dart', '.lua': 'lua', '.r': 'r',
|
|
167
|
+
'.ex': 'elixir', '.exs': 'elixir', '.pl': 'perl',
|
|
168
|
+
'.rb': 'ruby', '.go': 'go', '.rs': 'rust',
|
|
169
|
+
'.swift': 'swift', '.kt': 'kotlin', '.php': 'php',
|
|
170
|
+
'.scala': 'scala', '.vue': 'vue', '.svelte': 'svelte',
|
|
171
|
+
'.astro': 'astro', '.env': 'bash', '.cfg': 'ini',
|
|
172
|
+
'.conf': 'nginx', '.cmake': 'cmake', '.ino': 'cpp',
|
|
173
|
+
'.mk': 'makefile', '.csv': 'csv', '.tsv': 'tsv',
|
|
174
|
+
'.j2': 'jinja', '.lua': 'lua',
|
|
175
|
+
};
|
|
176
|
+
return map[ext] ?? '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Return a fence string with enough backticks to safely wrap the content.
|
|
181
|
+
* Ensures the opening fence cannot appear inside the content block.
|
|
182
|
+
*/
|
|
183
|
+
fence(content) {
|
|
184
|
+
const max = (content.match(/`+/g) ?? []).reduce((m, s) => Math.max(m, s.length), 2);
|
|
185
|
+
return '`'.repeat(Math.max(3, max + 1));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default LlmGenerator;
|