combicode 1.5.4 → 1.7.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 +24 -1
- package/README.md +15 -0
- package/index.js +56 -14
- package/package.json +1 -1
- package/test/test.js +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## [1.7.2](https://github.com/aaurelions/combicode/compare/combicode-js-v1.7.1...combicode-js-v1.7.2) (2025-01-XX)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **skip-content:** fix total size calculation to exclude files with skipped content
|
|
8
|
+
- **skip-content:** remove extra blank line after placeholder text in output
|
|
9
|
+
|
|
10
|
+
## [1.7.1](https://github.com/aaurelions/combicode/compare/combicode-js-v1.7.0...combicode-js-v1.7.1) (2025-01-XX)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **ci:** restore NPM_TOKEN for npm publishing to fix authentication issues
|
|
15
|
+
|
|
16
|
+
## [1.7.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.6.0...combicode-js-v1.7.0) (2025-01-XX)
|
|
17
|
+
|
|
18
|
+
### Maintenance
|
|
19
|
+
|
|
20
|
+
- **ci:** migrate to npm trusted publishing (OIDC), remove NPM_TOKEN requirement
|
|
21
|
+
|
|
22
|
+
## [1.6.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.4...combicode-js-v1.6.0) (2025-01-XX)
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
- **skip-content:** add `--skip-content` option to include files in tree but omit their content (useful for large test files)
|
|
4
27
|
|
|
5
28
|
## [1.5.4](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.3...combicode-js-v1.5.4) (2025-12-03)
|
|
6
29
|
|
package/README.md
CHANGED
|
@@ -81,6 +81,20 @@ Use the `--exclude` or `-e` flag with comma-separated glob patterns.
|
|
|
81
81
|
npx combicode -e "**/*_test.py,docs/**"
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
### Skip content for specific files
|
|
85
|
+
|
|
86
|
+
Use the `--skip-content` flag to include files in the tree structure but omit their content. This is useful for large files (like test files) that you want visible in the project overview but don't need their full content.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Include .test.ts files in tree but skip their content
|
|
90
|
+
npx combicode --skip-content "**/*.test.ts"
|
|
91
|
+
|
|
92
|
+
# Skip content for multiple patterns
|
|
93
|
+
npx combicode --skip-content "**/*.test.ts,**/*.spec.ts,**/tests/**"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Files with skipped content will be marked with `(content omitted)` in the file tree and will show a placeholder in the content section.
|
|
97
|
+
|
|
84
98
|
### Generating Context for `llms.txt`
|
|
85
99
|
|
|
86
100
|
The `--llms.txt` or `-l` flag is designed for projects that use an [`llms.txt`](https://llmstxt.org/) file to specify important documentation. When this flag is used, Combicode inserts a specialized system prompt telling the LLM that the provided context is the project's definitive documentation for a specific version. This helps the LLM provide more accurate answers and avoid using deprecated functions.
|
|
@@ -98,6 +112,7 @@ npx combicode -l -i .md -o llms.txt
|
|
|
98
112
|
| `--dry-run` | `-d` | Preview files without creating the output file. | `false` |
|
|
99
113
|
| `--include-ext` | `-i` | Comma-separated list of extensions to exclusively include. | (include all) |
|
|
100
114
|
| `--exclude` | `-e` | Comma-separated list of additional glob patterns to exclude. | (none) |
|
|
115
|
+
| `--skip-content` | | Comma-separated glob patterns for files to include in tree but omit content. | (none) |
|
|
101
116
|
| `--llms-txt` | `-l` | Use a specialized system prompt for context generated from an `llms.txt` file. | `false` |
|
|
102
117
|
| `--no-gitignore` | | Do not use patterns from the project's `.gitignore` file. | `false` |
|
|
103
118
|
| `--no-header` | | Omit the introductory prompt and file tree from the output. | `false` |
|
package/index.js
CHANGED
|
@@ -169,7 +169,7 @@ function walkDirectory(
|
|
|
169
169
|
return results;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
function generateFileTree(filesWithSize, root) {
|
|
172
|
+
function generateFileTree(filesWithSize, root, skipContentSet = null) {
|
|
173
173
|
let tree = `${path.basename(root)}/\n`;
|
|
174
174
|
const structure = {};
|
|
175
175
|
|
|
@@ -179,7 +179,8 @@ function generateFileTree(filesWithSize, root) {
|
|
|
179
179
|
parts.forEach((part, index) => {
|
|
180
180
|
const isFile = index === parts.length - 1;
|
|
181
181
|
if (isFile) {
|
|
182
|
-
|
|
182
|
+
const shouldSkipContent = skipContentSet && skipContentSet.has(relativePath);
|
|
183
|
+
currentLevel[part] = { size: formattedSize, skipContent: shouldSkipContent };
|
|
183
184
|
} else {
|
|
184
185
|
if (!currentLevel[part]) {
|
|
185
186
|
currentLevel[part] = {};
|
|
@@ -194,11 +195,12 @@ function generateFileTree(filesWithSize, root) {
|
|
|
194
195
|
entries.forEach((entry, index) => {
|
|
195
196
|
const isLast = index === entries.length - 1;
|
|
196
197
|
const value = level[entry];
|
|
197
|
-
const isFile = typeof value === "
|
|
198
|
+
const isFile = typeof value === "object" && value.size !== undefined;
|
|
198
199
|
const connector = isLast ? "└── " : "├── ";
|
|
199
200
|
|
|
200
201
|
if (isFile) {
|
|
201
|
-
|
|
202
|
+
const marker = value.skipContent ? " (content omitted)" : "";
|
|
203
|
+
tree += `${prefix}${connector}[${value.size}] ${entry}${marker}\n`;
|
|
202
204
|
} else {
|
|
203
205
|
tree += `${prefix}${connector}${entry}\n`;
|
|
204
206
|
buildTree(value, `${prefix}${isLast ? " " : "│ "}`);
|
|
@@ -258,6 +260,10 @@ async function main() {
|
|
|
258
260
|
type: "boolean",
|
|
259
261
|
default: false,
|
|
260
262
|
})
|
|
263
|
+
.option("skip-content", {
|
|
264
|
+
describe: "Comma-separated glob patterns for files to include in tree but omit content",
|
|
265
|
+
type: "string",
|
|
266
|
+
})
|
|
261
267
|
.version(version)
|
|
262
268
|
.alias("v", "version")
|
|
263
269
|
.help()
|
|
@@ -277,6 +283,12 @@ async function main() {
|
|
|
277
283
|
rootIgnoreManager.add(argv.exclude.split(","));
|
|
278
284
|
}
|
|
279
285
|
|
|
286
|
+
// Create skip-content manager
|
|
287
|
+
const skipContentManager = ignore();
|
|
288
|
+
if (argv.skipContent) {
|
|
289
|
+
skipContentManager.add(argv.skipContent.split(","));
|
|
290
|
+
}
|
|
291
|
+
|
|
280
292
|
const absoluteOutputPath = path.resolve(projectRoot, argv.output);
|
|
281
293
|
|
|
282
294
|
const allowedExtensions = argv.includeExt
|
|
@@ -306,9 +318,26 @@ async function main() {
|
|
|
306
318
|
|
|
307
319
|
includedFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
308
320
|
|
|
309
|
-
//
|
|
321
|
+
// Determine which files should have content skipped
|
|
322
|
+
const skipContentSet = new Set();
|
|
323
|
+
if (argv.skipContent) {
|
|
324
|
+
includedFiles.forEach((file) => {
|
|
325
|
+
const relativePath = file.relativePath.replace(/\\/g, "/");
|
|
326
|
+
if (skipContentManager.ignores(relativePath)) {
|
|
327
|
+
skipContentSet.add(file.relativePath);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Calculate total size of included files (excluding files with skipped content)
|
|
310
333
|
const totalSizeBytes = includedFiles.reduce(
|
|
311
|
-
(acc, file) =>
|
|
334
|
+
(acc, file) => {
|
|
335
|
+
// Don't count files with skipped content in the total size
|
|
336
|
+
if (skipContentSet.has(file.relativePath)) {
|
|
337
|
+
return acc;
|
|
338
|
+
}
|
|
339
|
+
return acc + file.size;
|
|
340
|
+
},
|
|
312
341
|
0
|
|
313
342
|
);
|
|
314
343
|
|
|
@@ -321,7 +350,7 @@ async function main() {
|
|
|
321
350
|
|
|
322
351
|
if (argv.dryRun) {
|
|
323
352
|
console.log("\n📋 Files to be included (Dry Run):\n");
|
|
324
|
-
const tree = generateFileTree(includedFiles, projectRoot);
|
|
353
|
+
const tree = generateFileTree(includedFiles, projectRoot, skipContentSet);
|
|
325
354
|
console.log(tree);
|
|
326
355
|
console.log("\n📊 Summary (Dry Run):");
|
|
327
356
|
console.log(
|
|
@@ -329,6 +358,9 @@ async function main() {
|
|
|
329
358
|
totalSizeBytes
|
|
330
359
|
)})`
|
|
331
360
|
);
|
|
361
|
+
if (skipContentSet.size > 0) {
|
|
362
|
+
console.log(` • Content omitted: ${skipContentSet.size} files`);
|
|
363
|
+
}
|
|
332
364
|
console.log(` • Ignored: ${stats.ignored} files/dirs`);
|
|
333
365
|
return;
|
|
334
366
|
}
|
|
@@ -345,7 +377,7 @@ async function main() {
|
|
|
345
377
|
|
|
346
378
|
outputStream.write("## Project File Tree\n\n");
|
|
347
379
|
outputStream.write("```\n");
|
|
348
|
-
const tree = generateFileTree(includedFiles, projectRoot);
|
|
380
|
+
const tree = generateFileTree(includedFiles, projectRoot, skipContentSet);
|
|
349
381
|
outputStream.write(tree);
|
|
350
382
|
outputStream.write("```\n\n");
|
|
351
383
|
outputStream.write("---\n\n");
|
|
@@ -355,14 +387,21 @@ async function main() {
|
|
|
355
387
|
|
|
356
388
|
for (const fileObj of includedFiles) {
|
|
357
389
|
const relativePath = fileObj.relativePath.replace(/\\/g, "/");
|
|
390
|
+
const shouldSkipContent = skipContentSet.has(fileObj.relativePath);
|
|
391
|
+
|
|
358
392
|
outputStream.write(`### **FILE:** \`${relativePath}\`\n`);
|
|
359
393
|
outputStream.write("```\n");
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
394
|
+
if (shouldSkipContent) {
|
|
395
|
+
outputStream.write(`(Content omitted - file size: ${fileObj.formattedSize})`);
|
|
396
|
+
totalLines += 1;
|
|
397
|
+
} else {
|
|
398
|
+
try {
|
|
399
|
+
const content = fs.readFileSync(fileObj.path, "utf8");
|
|
400
|
+
outputStream.write(content);
|
|
401
|
+
totalLines += content.split("\n").length;
|
|
402
|
+
} catch (e) {
|
|
403
|
+
outputStream.write(`... (error reading file: ${e.message}) ...`);
|
|
404
|
+
}
|
|
366
405
|
}
|
|
367
406
|
outputStream.write("\n```\n\n");
|
|
368
407
|
totalLines += 4; // Headers/footers lines
|
|
@@ -375,6 +414,9 @@ async function main() {
|
|
|
375
414
|
totalSizeBytes
|
|
376
415
|
)})`
|
|
377
416
|
);
|
|
417
|
+
if (skipContentSet.size > 0) {
|
|
418
|
+
console.log(` • Content omitted: ${skipContentSet.size} files`);
|
|
419
|
+
}
|
|
378
420
|
console.log(` • Ignored: ${stats.ignored} files/dirs`);
|
|
379
421
|
console.log(` • Output: ${argv.output} (~${totalLines} lines)`);
|
|
380
422
|
console.log(`\n✅ Done!`);
|
package/package.json
CHANGED
package/test/test.js
CHANGED
|
@@ -141,6 +141,64 @@ function runTest() {
|
|
|
141
141
|
"Output file included itself"
|
|
142
142
|
);
|
|
143
143
|
|
|
144
|
+
// --- Scenario 5: Skip Content Feature ---
|
|
145
|
+
console.log(" [5/5] Checking Skip Content Feature...");
|
|
146
|
+
teardown();
|
|
147
|
+
fs.mkdirSync(TEST_DIR);
|
|
148
|
+
|
|
149
|
+
createStructure(TEST_DIR, {
|
|
150
|
+
"main.js": "console.log('main');",
|
|
151
|
+
"test.js": "describe('test', () => { it('works', () => {}); });",
|
|
152
|
+
"large.test.ts": "const data = " + '"x'.repeat(1000) + '";',
|
|
153
|
+
subdir: {
|
|
154
|
+
"spec.ts": "describe('spec', () => {});",
|
|
155
|
+
"utils.js": "export function util() {}",
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
execSync(`node ${CLI_PATH} -o combicode.txt --skip-content "**/*test.ts,**/*spec.ts"`, {
|
|
160
|
+
cwd: TEST_DIR,
|
|
161
|
+
stdio: "inherit",
|
|
162
|
+
});
|
|
163
|
+
content = fs.readFileSync(OUTPUT_FILE, "utf8");
|
|
164
|
+
|
|
165
|
+
// Files should appear in tree with (content omitted) marker
|
|
166
|
+
assert.ok(
|
|
167
|
+
content.includes("large.test.ts (content omitted)"),
|
|
168
|
+
"Tree should show (content omitted) marker for large.test.ts"
|
|
169
|
+
);
|
|
170
|
+
// Check for spec.ts - it might be in subdir/spec.ts path
|
|
171
|
+
assert.ok(
|
|
172
|
+
content.includes("spec.ts (content omitted)") || content.includes("subdir/spec.ts (content omitted)"),
|
|
173
|
+
"Tree should show (content omitted) marker for spec.ts"
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// Files should have FILE headers
|
|
177
|
+
assert.ok(content.includes("### **FILE:** `large.test.ts`"), "File header should exist");
|
|
178
|
+
assert.ok(content.includes("### **FILE:** `subdir/spec.ts`"), "File header should exist");
|
|
179
|
+
|
|
180
|
+
// Content should be omitted (placeholder instead)
|
|
181
|
+
const largeTestMatch = content.match(/### \*\*FILE:\*\* `large\.test\.ts`[\s\S]*?```([\s\S]*?)```/);
|
|
182
|
+
assert.ok(largeTestMatch, "Should find large.test.ts content section");
|
|
183
|
+
assert.ok(
|
|
184
|
+
largeTestMatch[1].includes("Content omitted"),
|
|
185
|
+
"Content should be replaced with placeholder"
|
|
186
|
+
);
|
|
187
|
+
assert.ok(
|
|
188
|
+
largeTestMatch[1].includes("file size:"),
|
|
189
|
+
"Placeholder should include file size"
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// Regular files should have full content
|
|
193
|
+
assert.ok(content.includes("console.log('main');"), "main.js should have full content");
|
|
194
|
+
assert.ok(content.includes("export function util() {}"), "utils.js should have full content");
|
|
195
|
+
|
|
196
|
+
// Dry run should show content omitted count
|
|
197
|
+
const skipContentDryRunOutput = execSync(`node ${CLI_PATH} --dry-run --skip-content "**/*.test.ts"`, {
|
|
198
|
+
cwd: TEST_DIR,
|
|
199
|
+
}).toString();
|
|
200
|
+
assert.match(skipContentDryRunOutput, /Content omitted:/, "Dry run should show content omitted count");
|
|
201
|
+
|
|
144
202
|
console.log("✅ All Node.js tests passed!");
|
|
145
203
|
} catch (error) {
|
|
146
204
|
console.error("❌ Test Failed:", error.message);
|