combicode 1.5.4 → 1.7.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 +17 -1
- package/README.md +15 -0
- package/index.js +48 -12
- package/package.json +1 -1
- package/test/test.js +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## [1.7.1](https://github.com/aaurelions/combicode/compare/combicode-js-v1.7.0...combicode-js-v1.7.1) (2025-01-XX)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
- **ci:** restore NPM_TOKEN for npm publishing to fix authentication issues
|
|
8
|
+
|
|
9
|
+
## [1.7.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.6.0...combicode-js-v1.7.0) (2025-01-XX)
|
|
10
|
+
|
|
11
|
+
### Maintenance
|
|
12
|
+
|
|
13
|
+
- **ci:** migrate to npm trusted publishing (OIDC), remove NPM_TOKEN requirement
|
|
14
|
+
|
|
15
|
+
## [1.6.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.4...combicode-js-v1.6.0) (2025-01-XX)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **skip-content:** add `--skip-content` option to include files in tree but omit their content (useful for large test files)
|
|
4
20
|
|
|
5
21
|
## [1.5.4](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.3...combicode-js-v1.5.4) (2025-12-03)
|
|
6
22
|
|
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,6 +318,17 @@ async function main() {
|
|
|
306
318
|
|
|
307
319
|
includedFiles.sort((a, b) => a.path.localeCompare(b.path));
|
|
308
320
|
|
|
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
|
+
|
|
309
332
|
// Calculate total size of included files
|
|
310
333
|
const totalSizeBytes = includedFiles.reduce(
|
|
311
334
|
(acc, file) => acc + file.size,
|
|
@@ -321,7 +344,7 @@ async function main() {
|
|
|
321
344
|
|
|
322
345
|
if (argv.dryRun) {
|
|
323
346
|
console.log("\n📋 Files to be included (Dry Run):\n");
|
|
324
|
-
const tree = generateFileTree(includedFiles, projectRoot);
|
|
347
|
+
const tree = generateFileTree(includedFiles, projectRoot, skipContentSet);
|
|
325
348
|
console.log(tree);
|
|
326
349
|
console.log("\n📊 Summary (Dry Run):");
|
|
327
350
|
console.log(
|
|
@@ -329,6 +352,9 @@ async function main() {
|
|
|
329
352
|
totalSizeBytes
|
|
330
353
|
)})`
|
|
331
354
|
);
|
|
355
|
+
if (skipContentSet.size > 0) {
|
|
356
|
+
console.log(` • Content omitted: ${skipContentSet.size} files`);
|
|
357
|
+
}
|
|
332
358
|
console.log(` • Ignored: ${stats.ignored} files/dirs`);
|
|
333
359
|
return;
|
|
334
360
|
}
|
|
@@ -345,7 +371,7 @@ async function main() {
|
|
|
345
371
|
|
|
346
372
|
outputStream.write("## Project File Tree\n\n");
|
|
347
373
|
outputStream.write("```\n");
|
|
348
|
-
const tree = generateFileTree(includedFiles, projectRoot);
|
|
374
|
+
const tree = generateFileTree(includedFiles, projectRoot, skipContentSet);
|
|
349
375
|
outputStream.write(tree);
|
|
350
376
|
outputStream.write("```\n\n");
|
|
351
377
|
outputStream.write("---\n\n");
|
|
@@ -355,14 +381,21 @@ async function main() {
|
|
|
355
381
|
|
|
356
382
|
for (const fileObj of includedFiles) {
|
|
357
383
|
const relativePath = fileObj.relativePath.replace(/\\/g, "/");
|
|
384
|
+
const shouldSkipContent = skipContentSet.has(fileObj.relativePath);
|
|
385
|
+
|
|
358
386
|
outputStream.write(`### **FILE:** \`${relativePath}\`\n`);
|
|
359
387
|
outputStream.write("```\n");
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
388
|
+
if (shouldSkipContent) {
|
|
389
|
+
outputStream.write(`(Content omitted - file size: ${fileObj.formattedSize})\n`);
|
|
390
|
+
totalLines += 1;
|
|
391
|
+
} else {
|
|
392
|
+
try {
|
|
393
|
+
const content = fs.readFileSync(fileObj.path, "utf8");
|
|
394
|
+
outputStream.write(content);
|
|
395
|
+
totalLines += content.split("\n").length;
|
|
396
|
+
} catch (e) {
|
|
397
|
+
outputStream.write(`... (error reading file: ${e.message}) ...`);
|
|
398
|
+
}
|
|
366
399
|
}
|
|
367
400
|
outputStream.write("\n```\n\n");
|
|
368
401
|
totalLines += 4; // Headers/footers lines
|
|
@@ -375,6 +408,9 @@ async function main() {
|
|
|
375
408
|
totalSizeBytes
|
|
376
409
|
)})`
|
|
377
410
|
);
|
|
411
|
+
if (skipContentSet.size > 0) {
|
|
412
|
+
console.log(` • Content omitted: ${skipContentSet.size} files`);
|
|
413
|
+
}
|
|
378
414
|
console.log(` • Ignored: ${stats.ignored} files/dirs`);
|
|
379
415
|
console.log(` • Output: ${argv.output} (~${totalLines} lines)`);
|
|
380
416
|
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);
|