combicode 1.4.0 โ 1.5.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/CHANGELOG.md +23 -20
- package/index.js +52 -27
- package/package.json +5 -3
- package/test/test.js +90 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,42 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [1.
|
|
3
|
+
## [1.5.2](https://github.com/aaurelions/combicode/compare/combicode-js-v1.4.0...combicode-js-v1.5.2) (2025-11-30)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
4
6
|
|
|
7
|
+
- **tree:** display file sizes in the generated file tree (e.g., `[1.2KB]`)
|
|
8
|
+
- **output:** improve file header format (`### **FILE:**`) for better LLM parsing
|
|
9
|
+
- **tests:** add integration test suite using native Node.js modules
|
|
10
|
+
|
|
11
|
+
## [1.4.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.3.0...combicode-js-v1.4.0) (2025-08-13)
|
|
5
12
|
|
|
6
13
|
### Features
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
- Add --llms-txt flag for llms.txt documentation context ([0817554](https://github.com/aaurelions/combicode/commit/081755435594b0ca5208609b2724eb47bd73c2dc))
|
|
9
16
|
|
|
10
17
|
## [1.3.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.2.1...combicode-js-v1.3.0) (2025-06-25)
|
|
11
18
|
|
|
12
|
-
|
|
13
19
|
### Features
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
- Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
|
|
22
|
+
- **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
|
|
23
|
+
- improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
|
|
24
|
+
- improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
|
|
20
25
|
|
|
21
26
|
### Bug Fixes
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
- **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
|
|
29
|
+
- **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
|
|
30
|
+
- Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
|
|
26
31
|
|
|
27
32
|
## [1.2.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.1.0...combicode-js-v1.2.0) (2025-06-25)
|
|
28
33
|
|
|
29
|
-
|
|
30
34
|
### Features
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
- Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
|
|
37
|
+
- **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
|
|
38
|
+
- improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
|
|
39
|
+
- improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
|
|
37
40
|
|
|
38
41
|
### Bug Fixes
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
- **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
|
|
44
|
+
- **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
|
|
45
|
+
- Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
|
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const { version } = require("./package.json");
|
|
|
10
10
|
|
|
11
11
|
const DEFAULT_SYSTEM_PROMPT = `You are an expert software architect. The user is providing you with the complete source code for a project, contained in a single file. Your task is to meticulously analyze the provided codebase to gain a comprehensive understanding of its structure, functionality, dependencies, and overall architecture.
|
|
12
12
|
|
|
13
|
-
A file tree is provided below to give you a high-level overview. The subsequent sections contain the full content of each file, clearly marked with
|
|
13
|
+
A file tree is provided below to give you a high-level overview. The subsequent sections contain the full content of each file, clearly marked with a file header.
|
|
14
14
|
|
|
15
15
|
Your instructions are:
|
|
16
16
|
1. **Analyze Thoroughly:** Read through every file to understand its purpose and how it interacts with other files.
|
|
@@ -21,7 +21,7 @@ const LLMS_TXT_SYSTEM_PROMPT = `You are an expert software architect. The user i
|
|
|
21
21
|
|
|
22
22
|
When answering questions or writing code, adhere strictly to the functions, variables, and methods described in this context. Do not use or suggest any deprecated or older functionalities that are not present here.
|
|
23
23
|
|
|
24
|
-
A file tree of the documentation source is provided below for a high-level overview. The subsequent sections contain the full content of each file, clearly marked with
|
|
24
|
+
A file tree of the documentation source is provided below for a high-level overview. The subsequent sections contain the full content of each file, clearly marked with a file header.
|
|
25
25
|
`;
|
|
26
26
|
|
|
27
27
|
function loadDefaultIgnorePatterns() {
|
|
@@ -53,18 +53,33 @@ function isLikelyBinary(file) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function
|
|
56
|
+
function formatBytes(bytes, decimals = 1) {
|
|
57
|
+
if (bytes === 0) return "0 B";
|
|
58
|
+
const k = 1024;
|
|
59
|
+
const dm = decimals < 0 ? 0 : decimals;
|
|
60
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
61
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
62
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + "" + sizes[i];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function generateFileTree(filesWithSize, root) {
|
|
57
66
|
let tree = `${path.basename(root)}/\n`;
|
|
58
67
|
const structure = {};
|
|
59
68
|
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
// Build the structure
|
|
70
|
+
filesWithSize.forEach(({ relativePath, formattedSize }) => {
|
|
71
|
+
const parts = relativePath.split(path.sep);
|
|
62
72
|
let currentLevel = structure;
|
|
63
|
-
parts.forEach((part) => {
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
parts.forEach((part, index) => {
|
|
74
|
+
const isFile = index === parts.length - 1;
|
|
75
|
+
if (isFile) {
|
|
76
|
+
currentLevel[part] = formattedSize;
|
|
77
|
+
} else {
|
|
78
|
+
if (!currentLevel[part]) {
|
|
79
|
+
currentLevel[part] = {};
|
|
80
|
+
}
|
|
81
|
+
currentLevel = currentLevel[part];
|
|
66
82
|
}
|
|
67
|
-
currentLevel = currentLevel[part];
|
|
68
83
|
});
|
|
69
84
|
});
|
|
70
85
|
|
|
@@ -72,9 +87,16 @@ function generateFileTree(files, root) {
|
|
|
72
87
|
const entries = Object.keys(level);
|
|
73
88
|
entries.forEach((entry, index) => {
|
|
74
89
|
const isLast = index === entries.length - 1;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
90
|
+
const value = level[entry];
|
|
91
|
+
const isFile = typeof value === "string";
|
|
92
|
+
|
|
93
|
+
const connector = isLast ? "โโโ " : "โโโ ";
|
|
94
|
+
|
|
95
|
+
if (isFile) {
|
|
96
|
+
tree += `${prefix}${connector}[${value}] ${entry}\n`;
|
|
97
|
+
} else {
|
|
98
|
+
tree += `${prefix}${connector}${entry}\n`;
|
|
99
|
+
buildTree(value, `${prefix}${isLast ? " " : "โ "}`);
|
|
78
100
|
}
|
|
79
101
|
});
|
|
80
102
|
};
|
|
@@ -90,6 +112,7 @@ async function main() {
|
|
|
90
112
|
process.exit(0);
|
|
91
113
|
}
|
|
92
114
|
|
|
115
|
+
// Yargs singleton usage works correctly with arguments passed here
|
|
93
116
|
const argv = yargs(rawArgv)
|
|
94
117
|
.scriptName("combicode")
|
|
95
118
|
.usage("$0 [options]")
|
|
@@ -163,7 +186,7 @@ async function main() {
|
|
|
163
186
|
dot: true,
|
|
164
187
|
ignore: ignorePatterns,
|
|
165
188
|
absolute: true,
|
|
166
|
-
stats:
|
|
189
|
+
stats: true,
|
|
167
190
|
});
|
|
168
191
|
|
|
169
192
|
const allowedExtensions = argv.includeExt
|
|
@@ -175,28 +198,30 @@ async function main() {
|
|
|
175
198
|
: null;
|
|
176
199
|
|
|
177
200
|
const includedFiles = allFiles
|
|
178
|
-
.filter((
|
|
179
|
-
const
|
|
180
|
-
if (!stats || stats.isDirectory()) return false;
|
|
201
|
+
.filter((fileObj) => {
|
|
202
|
+
const file = fileObj.path;
|
|
203
|
+
if (!fileObj.stats || fileObj.stats.isDirectory()) return false;
|
|
181
204
|
if (isLikelyBinary(file)) return false;
|
|
182
205
|
if (allowedExtensions && !allowedExtensions.has(path.extname(file)))
|
|
183
206
|
return false;
|
|
184
207
|
return true;
|
|
185
208
|
})
|
|
186
|
-
.
|
|
209
|
+
.map((fileObj) => ({
|
|
210
|
+
path: fileObj.path,
|
|
211
|
+
relativePath: path.relative(projectRoot, fileObj.path),
|
|
212
|
+
size: fileObj.stats.size,
|
|
213
|
+
formattedSize: formatBytes(fileObj.stats.size),
|
|
214
|
+
}))
|
|
215
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
187
216
|
|
|
188
217
|
if (includedFiles.length === 0) {
|
|
189
218
|
console.error("โ No files to include. Check your path or filters.");
|
|
190
219
|
process.exit(1);
|
|
191
220
|
}
|
|
192
221
|
|
|
193
|
-
const relativeFiles = includedFiles.map((file) =>
|
|
194
|
-
path.relative(projectRoot, file)
|
|
195
|
-
);
|
|
196
|
-
|
|
197
222
|
if (argv.dryRun) {
|
|
198
223
|
console.log("\n๐ Files to be included (Dry Run):\n");
|
|
199
|
-
const tree = generateFileTree(
|
|
224
|
+
const tree = generateFileTree(includedFiles, projectRoot);
|
|
200
225
|
console.log(tree);
|
|
201
226
|
console.log(`\nTotal: ${includedFiles.length} files.`);
|
|
202
227
|
return;
|
|
@@ -211,18 +236,18 @@ async function main() {
|
|
|
211
236
|
outputStream.write(systemPrompt + "\n");
|
|
212
237
|
outputStream.write("## Project File Tree\n\n");
|
|
213
238
|
outputStream.write("```\n");
|
|
214
|
-
const tree = generateFileTree(
|
|
239
|
+
const tree = generateFileTree(includedFiles, projectRoot);
|
|
215
240
|
outputStream.write(tree);
|
|
216
241
|
outputStream.write("```\n\n");
|
|
217
242
|
outputStream.write("---\n\n");
|
|
218
243
|
}
|
|
219
244
|
|
|
220
|
-
for (const
|
|
221
|
-
const relativePath =
|
|
222
|
-
outputStream.write(
|
|
245
|
+
for (const fileObj of includedFiles) {
|
|
246
|
+
const relativePath = fileObj.relativePath.replace(/\\/g, "/");
|
|
247
|
+
outputStream.write(`### **FILE:** \`${relativePath}\`\n`);
|
|
223
248
|
outputStream.write("```\n");
|
|
224
249
|
try {
|
|
225
|
-
const content = fs.readFileSync(
|
|
250
|
+
const content = fs.readFileSync(fileObj.path, "utf8");
|
|
226
251
|
outputStream.write(content);
|
|
227
252
|
} catch (e) {
|
|
228
253
|
outputStream.write(`... (error reading file: ${e.message}) ...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "combicode",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "A CLI tool to combine a project's codebase into a single file for LLM context.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
12
|
"scripts": {
|
|
13
|
-
"
|
|
13
|
+
"prepack": "mkdir -p config && cp ../configs/ignore.json config/ignore.json",
|
|
14
|
+
"pretest": "mkdir -p config && cp ../configs/ignore.json config/ignore.json",
|
|
15
|
+
"test": "node test/test.js"
|
|
14
16
|
},
|
|
15
17
|
"repository": {
|
|
16
18
|
"type": "git",
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
"author": "A. Aurelions",
|
|
33
35
|
"license": "MIT",
|
|
34
36
|
"dependencies": {
|
|
35
|
-
"fast-glob": "^3.3.
|
|
37
|
+
"fast-glob": "^3.3.3",
|
|
36
38
|
"yargs": "^17.7.2"
|
|
37
39
|
}
|
|
38
40
|
}
|
package/test/test.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
const assert = require("assert");
|
|
5
|
+
|
|
6
|
+
const CLI_PATH = path.resolve(__dirname, "../index.js");
|
|
7
|
+
const TEST_DIR = path.resolve(__dirname, "temp_env");
|
|
8
|
+
const OUTPUT_FILE = path.join(TEST_DIR, "combicode.txt");
|
|
9
|
+
|
|
10
|
+
// Setup: Create a temp directory with dummy files
|
|
11
|
+
function setup() {
|
|
12
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
13
|
+
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
14
|
+
}
|
|
15
|
+
fs.mkdirSync(TEST_DIR);
|
|
16
|
+
|
|
17
|
+
// Create a dummy JS file
|
|
18
|
+
fs.writeFileSync(path.join(TEST_DIR, "alpha.js"), "console.log('alpha');");
|
|
19
|
+
|
|
20
|
+
// Create a dummy text file in a subdir
|
|
21
|
+
const subDir = path.join(TEST_DIR, "subdir");
|
|
22
|
+
fs.mkdirSync(subDir);
|
|
23
|
+
fs.writeFileSync(path.join(subDir, "beta.txt"), "Hello World");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Teardown: Cleanup temp directory
|
|
27
|
+
function teardown() {
|
|
28
|
+
if (fs.existsSync(TEST_DIR)) {
|
|
29
|
+
fs.rmSync(TEST_DIR, { recursive: true, force: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function runTest() {
|
|
34
|
+
console.log("๐งช Starting Node.js Integration Tests...");
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
setup();
|
|
38
|
+
|
|
39
|
+
// 1. Test Version Flag
|
|
40
|
+
console.log(" Checking --version...");
|
|
41
|
+
const versionOutput = execSync(`node ${CLI_PATH} --version`).toString();
|
|
42
|
+
assert.match(versionOutput, /Combicode \(JavaScript\), version/);
|
|
43
|
+
|
|
44
|
+
// 2. Test Dry Run
|
|
45
|
+
console.log(" Checking --dry-run...");
|
|
46
|
+
const dryRunOutput = execSync(`node ${CLI_PATH} --dry-run`, {
|
|
47
|
+
cwd: TEST_DIR,
|
|
48
|
+
}).toString();
|
|
49
|
+
assert.match(dryRunOutput, /Files to be included \(Dry Run\)/);
|
|
50
|
+
// Check for file size format in tree (e.g., [21B])
|
|
51
|
+
assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/);
|
|
52
|
+
|
|
53
|
+
// 3. Test Actual Generation
|
|
54
|
+
console.log(" Checking file generation...");
|
|
55
|
+
execSync(`node ${CLI_PATH} --output combicode.txt`, { cwd: TEST_DIR });
|
|
56
|
+
|
|
57
|
+
assert.ok(fs.existsSync(OUTPUT_FILE), "Output file should exist");
|
|
58
|
+
|
|
59
|
+
const content = fs.readFileSync(OUTPUT_FILE, "utf8");
|
|
60
|
+
|
|
61
|
+
// Check for System Prompt
|
|
62
|
+
assert.ok(
|
|
63
|
+
content.includes("You are an expert software architect"),
|
|
64
|
+
"System prompt missing"
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// Check for Tree structure
|
|
68
|
+
assert.ok(content.includes("subdir"), "Tree should show subdirectory");
|
|
69
|
+
|
|
70
|
+
// Check for new Header format
|
|
71
|
+
assert.ok(
|
|
72
|
+
content.includes("### **FILE:** `alpha.js`"),
|
|
73
|
+
"New header format missing for alpha.js"
|
|
74
|
+
);
|
|
75
|
+
assert.ok(
|
|
76
|
+
content.includes("### **FILE:** `subdir/beta.txt`"),
|
|
77
|
+
"New header format missing for beta.txt"
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
console.log("โ
All Node.js tests passed!");
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("โ Test Failed:", error.message);
|
|
83
|
+
if (error.stdout) console.log(error.stdout.toString());
|
|
84
|
+
process.exit(1);
|
|
85
|
+
} finally {
|
|
86
|
+
teardown();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
runTest();
|