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 CHANGED
@@ -1,6 +1,22 @@
1
1
  # Changelog
2
2
 
3
- # Changelog
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
- currentLevel[part] = formattedSize;
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 === "string";
198
+ const isFile = typeof value === "object" && value.size !== undefined;
198
199
  const connector = isLast ? "└── " : "├── ";
199
200
 
200
201
  if (isFile) {
201
- tree += `${prefix}${connector}[${value}] ${entry}\n`;
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
- try {
361
- const content = fs.readFileSync(fileObj.path, "utf8");
362
- outputStream.write(content);
363
- totalLines += content.split("\n").length;
364
- } catch (e) {
365
- outputStream.write(`... (error reading file: ${e.message}) ...`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "combicode",
3
- "version": "1.5.4",
3
+ "version": "1.7.1",
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": {
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);