combicode 1.7.3 โ†’ 2.0.0

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.
Files changed (5) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +340 -19
  3. package/index.js +1482 -142
  4. package/package.json +22 -1
  5. package/test/test.js +473 -119
package/test/test.js CHANGED
@@ -7,7 +7,6 @@ const CLI_PATH = path.resolve(__dirname, "../index.js");
7
7
  const TEST_DIR = path.resolve(__dirname, "temp_env");
8
8
  const OUTPUT_FILE = path.join(TEST_DIR, "combicode.txt");
9
9
 
10
- // Helper to create directory structure
11
10
  function createStructure(base, structure) {
12
11
  Object.entries(structure).forEach(([name, content]) => {
13
12
  const fullPath = path.join(base, name);
@@ -20,26 +19,39 @@ function createStructure(base, structure) {
20
19
  });
21
20
  }
22
21
 
23
- // Teardown: Cleanup temp directory
24
22
  function teardown() {
25
23
  if (fs.existsSync(TEST_DIR)) {
26
24
  fs.rmSync(TEST_DIR, { recursive: true, force: true });
27
25
  }
28
26
  }
29
27
 
30
- function runTest() {
31
- console.log("๐Ÿงช Starting Node.js Integration Tests...");
28
+ function runTests() {
29
+ console.log("๐Ÿงช Starting Node.js Integration Tests (v2.0.0)...\n");
30
+ let passed = 0;
31
+ let total = 0;
32
32
 
33
- try {
34
- // Clean start
35
- teardown();
36
- fs.mkdirSync(TEST_DIR);
33
+ function test(name, fn) {
34
+ total++;
35
+ try {
36
+ teardown();
37
+ fs.mkdirSync(TEST_DIR);
38
+ fn();
39
+ passed++;
40
+ console.log(` โœ… ${name}`);
41
+ } catch (error) {
42
+ console.error(` โŒ ${name}`);
43
+ console.error(` ${error.message}`);
44
+ }
45
+ }
37
46
 
38
- // --- Scenario 1: Basic Functionality ---
39
- console.log(" [1/4] Checking Basic Functionality & Version...");
40
- const versionOutput = execSync(`node ${CLI_PATH} --version`).toString();
41
- assert.match(versionOutput, /Combicode \(JavaScript\), version/);
47
+ // --- Test 1: Version ---
48
+ test("Version output", () => {
49
+ const out = execSync(`node ${CLI_PATH} --version`).toString();
50
+ assert.match(out, /Combicode \(JavaScript\), version/);
51
+ });
42
52
 
53
+ // --- Test 2: Basic generation with code_index and merged_code ---
54
+ test("Basic generation with <code_index> and <merged_code>", () => {
43
55
  createStructure(TEST_DIR, {
44
56
  "alpha.js": "console.log('alpha');",
45
57
  subdir: {
@@ -47,29 +59,134 @@ function runTest() {
47
59
  },
48
60
  });
49
61
 
50
- // Capture dry-run output to verify structure
51
- const dryRunOutput = execSync(`node ${CLI_PATH} --dry-run`, {
52
- cwd: TEST_DIR,
53
- }).toString();
54
- assert.match(dryRunOutput, /Files to be included \(Dry Run\)/);
55
- assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/); // Size check
62
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
63
+ assert.ok(fs.existsSync(OUTPUT_FILE));
64
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
56
65
 
57
- // Run generation
58
- execSync(`node ${CLI_PATH} --output combicode.txt`, {
59
- cwd: TEST_DIR,
60
- stdio: "inherit",
66
+ assert.ok(content.includes("<code_index>"), "Should contain <code_index>");
67
+ assert.ok(content.includes("</code_index>"), "Should contain </code_index>");
68
+ assert.ok(content.includes("<merged_code>"), "Should contain <merged_code>");
69
+ assert.ok(content.includes("</merged_code>"), "Should contain </merged_code>");
70
+ assert.ok(content.includes("# FILE: alpha.js"), "Should contain file header for alpha.js");
71
+ assert.ok(content.includes("# FILE: subdir/beta.txt"), "Should contain file header for beta.txt");
72
+ assert.ok(content.includes("console.log('alpha');"), "Should contain alpha.js content");
73
+ assert.ok(content.includes("Hello World"), "Should contain beta.txt content");
74
+ });
75
+
76
+ // --- Test 3: OL/ML/SIZE in code_index ---
77
+ test("OL/ML/SIZE references in code_index", () => {
78
+ createStructure(TEST_DIR, {
79
+ "main.py": "x = 1\ny = 2\nz = 3\n",
61
80
  });
62
81
 
63
- assert.ok(fs.existsSync(OUTPUT_FILE), "Output file should exist");
64
- let content = fs.readFileSync(OUTPUT_FILE, "utf8");
65
- assert.ok(content.includes("### **FILE:** `alpha.js`"));
66
- assert.ok(content.includes("### **FILE:** `subdir/beta.txt`"));
82
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
83
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
67
84
 
68
- // --- Scenario 2: Nested .gitignore Support ---
69
- console.log(" [2/4] Checking Nested .gitignore Support...");
70
- teardown();
71
- fs.mkdirSync(TEST_DIR);
85
+ // code_index should have OL, ML, SIZE references
86
+ assert.match(content, /OL: 1-\d+/, "code_index should contain OL references");
87
+ assert.match(content, /ML: \d+-\d+/, "code_index should contain ML references");
88
+ assert.match(content, /\d+(\.\d+)?[BKMGT]/, "code_index should contain size references");
89
+ });
72
90
 
91
+ // --- Test 4: Code map with parsed Python elements ---
92
+ test("Code map parses Python classes and functions", () => {
93
+ createStructure(TEST_DIR, {
94
+ "server.py": [
95
+ "class Server:",
96
+ " def __init__(self, host: str, port: int):",
97
+ " self.host = host",
98
+ " self.port = port",
99
+ "",
100
+ " def start(self):",
101
+ " print('starting')",
102
+ " print('started')",
103
+ "",
104
+ "def main():",
105
+ " s = Server('localhost', 8080)",
106
+ " s.start()",
107
+ "",
108
+ ].join("\n"),
109
+ });
110
+
111
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
112
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
113
+
114
+ assert.ok(content.includes("class Server"), "Code map should include class Server");
115
+ assert.ok(content.includes("ctor __init__"), "Code map should include ctor __init__");
116
+ assert.ok(content.includes("fn start"), "Code map should include fn start");
117
+ assert.ok(content.includes("fn main"), "Code map should include fn main");
118
+ });
119
+
120
+ // --- Test 5: Code map with parsed JS elements ---
121
+ test("Code map parses JavaScript classes and functions", () => {
122
+ createStructure(TEST_DIR, {
123
+ "app.js": [
124
+ "class App {",
125
+ " constructor(name) {",
126
+ " this.name = name;",
127
+ " }",
128
+ "",
129
+ " async start() {",
130
+ " console.log('start');",
131
+ " return true;",
132
+ " }",
133
+ "}",
134
+ "",
135
+ "function main() {",
136
+ " const app = new App('test');",
137
+ " app.start();",
138
+ "}",
139
+ "",
140
+ ].join("\n"),
141
+ });
142
+
143
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
144
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
145
+
146
+ assert.ok(content.includes("class App"), "Code map should include class App");
147
+ assert.ok(content.includes("fn main"), "Code map should include fn main");
148
+ });
149
+
150
+ // --- Test 6: --no-parse flag ---
151
+ test("--no-parse disables code structure parsing", () => {
152
+ createStructure(TEST_DIR, {
153
+ "server.py": [
154
+ "class Server:",
155
+ " def start(self):",
156
+ " pass",
157
+ "",
158
+ ].join("\n"),
159
+ });
160
+
161
+ execSync(`node ${CLI_PATH} -o combicode.txt --no-parse`, { cwd: TEST_DIR, stdio: "pipe" });
162
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
163
+
164
+ assert.ok(content.includes("<code_index>"), "Should still have code_index");
165
+ assert.ok(content.includes("server.py"), "Should list the file");
166
+ // With --no-parse, the code map should NOT contain class/fn elements inside files
167
+ const codeIndex = content.split("<code_index>")[1].split("</code_index>")[0];
168
+ assert.ok(!codeIndex.includes("class Server"), "No-parse should not show class Server in code_index");
169
+ assert.ok(!codeIndex.includes("fn start"), "No-parse should not show fn start in code_index");
170
+ });
171
+
172
+ // --- Test 7: Dry run with code_index ---
173
+ test("Dry run shows code_index-style output", () => {
174
+ createStructure(TEST_DIR, {
175
+ "main.py": "x = 1\n",
176
+ });
177
+
178
+ const out = execSync(`node ${CLI_PATH} --dry-run`, { cwd: TEST_DIR }).toString();
179
+
180
+ assert.match(out, /Files to include/, "Dry run should show files header");
181
+ assert.match(out, /Total files:/, "Should show total files");
182
+ assert.match(out, /Total size:/, "Should show total size");
183
+ assert.ok(out.includes("main.py"), "Should list main.py");
184
+ assert.ok(out.includes("OL:"), "Should contain OL references");
185
+ assert.ok(out.includes("ML:"), "Should contain ML references");
186
+ });
187
+
188
+ // --- Test 8: Nested .gitignore support ---
189
+ test("Nested .gitignore support", () => {
73
190
  createStructure(TEST_DIR, {
74
191
  "root.js": "root",
75
192
  "ignore_me_root.log": "log",
@@ -86,70 +203,47 @@ function runTest() {
86
203
  },
87
204
  });
88
205
 
89
- execSync(`node ${CLI_PATH} -o combicode.txt`, {
90
- cwd: TEST_DIR,
91
- stdio: "inherit",
92
- });
93
- content = fs.readFileSync(OUTPUT_FILE, "utf8");
94
-
95
- // Should include:
96
- assert.ok(content.includes("### **FILE:** `root.js`"), "root.js missing");
97
- assert.ok(
98
- content.includes("### **FILE:** `nested/child.js`"),
99
- "child.js missing"
100
- );
101
- assert.ok(
102
- content.includes("### **FILE:** `nested/deep/deep.js`"),
103
- "deep.js missing"
104
- );
206
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
207
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
105
208
 
106
- // Should exclude (Checking Headers, not content):
107
- assert.ok(
108
- !content.includes("### **FILE:** `ignore_me_root.log`"),
109
- "Root gitignore failed (*.log)"
110
- );
111
- assert.ok(
112
- !content.includes("### **FILE:** `nested/ignore_me_child.tmp`"),
113
- "Nested gitignore failed (*.tmp)"
114
- );
115
- assert.ok(
116
- !content.includes("### **FILE:** `nested/deep/ignore_local.txt`"),
117
- "Deep nested gitignore failed (specific file)"
118
- );
209
+ assert.ok(content.includes("# FILE: root.js"), "root.js should be included");
210
+ assert.ok(content.includes("# FILE: nested/child.js"), "child.js should be included");
211
+ assert.ok(content.includes("# FILE: nested/deep/deep.js"), "deep.js should be included");
212
+ assert.ok(!content.includes("# FILE: ignore_me_root.log"), "*.log should be excluded");
213
+ assert.ok(!content.includes("# FILE: nested/ignore_me_child.tmp"), "*.tmp should be excluded");
214
+ assert.ok(!content.includes("# FILE: nested/deep/ignore_local.txt"), "ignore_local.txt should be excluded");
215
+ });
119
216
 
120
- // --- Scenario 3: CLI Exclude Override ---
121
- console.log(" [3/4] Checking CLI Exclude Flags...");
122
- execSync(`node ${CLI_PATH} -o combicode.txt -e "**/deep.js"`, {
123
- cwd: TEST_DIR,
124
- stdio: "inherit",
217
+ // --- Test 9: CLI exclude override ---
218
+ test("CLI --exclude flag", () => {
219
+ createStructure(TEST_DIR, {
220
+ "keep.js": "keep",
221
+ "skip.js": "skip",
125
222
  });
126
- content = fs.readFileSync(OUTPUT_FILE, "utf8");
127
- assert.ok(
128
- !content.includes("### **FILE:** `nested/deep/deep.js`"),
129
- "CLI exclude flag failed"
130
- );
131
223
 
132
- // --- Scenario 4: Output File Self-Exclusion ---
133
- console.log(" [4/4] Checking Output File Self-Exclusion...");
134
- execSync(`node ${CLI_PATH} -o combicode.txt`, {
135
- cwd: TEST_DIR,
136
- stdio: "inherit",
224
+ execSync(`node ${CLI_PATH} -o combicode.txt -e "skip.js"`, { cwd: TEST_DIR, stdio: "pipe" });
225
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
226
+
227
+ assert.ok(content.includes("# FILE: keep.js"), "keep.js should be included");
228
+ assert.ok(!content.includes("# FILE: skip.js"), "skip.js should be excluded");
229
+ });
230
+
231
+ // --- Test 10: Output file self-exclusion ---
232
+ test("Output file self-exclusion", () => {
233
+ createStructure(TEST_DIR, {
234
+ "alpha.js": "alpha",
137
235
  });
138
- content = fs.readFileSync(OUTPUT_FILE, "utf8");
139
- assert.ok(
140
- !content.includes("### **FILE:** `combicode.txt`"),
141
- "Output file included itself"
142
- );
143
236
 
144
- // --- Scenario 5: Skip Content Feature ---
145
- console.log(" [5/5] Checking Skip Content Feature...");
146
- teardown();
147
- fs.mkdirSync(TEST_DIR);
237
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
238
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
239
+ assert.ok(!content.includes("# FILE: combicode.txt"), "Output file should not include itself");
240
+ });
148
241
 
242
+ // --- Test 11: Skip content feature ---
243
+ test("Skip content feature", () => {
149
244
  createStructure(TEST_DIR, {
150
245
  "main.js": "console.log('main');",
151
- "test.js": "describe('test', () => { it('works', () => {}); });",
152
- "large.test.ts": "const data = " + '"x'.repeat(1000) + '";',
246
+ "large.test.ts": 'const data = "' + "x".repeat(1000) + '";',
153
247
  subdir: {
154
248
  "spec.ts": "describe('spec', () => {});",
155
249
  "utils.js": "export function util() {}",
@@ -158,54 +252,314 @@ function runTest() {
158
252
 
159
253
  execSync(`node ${CLI_PATH} -o combicode.txt --skip-content "**/*test.ts,**/*spec.ts"`, {
160
254
  cwd: TEST_DIR,
161
- stdio: "inherit",
255
+ stdio: "pipe",
162
256
  });
163
- content = fs.readFileSync(OUTPUT_FILE, "utf8");
257
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
164
258
 
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
- );
259
+ assert.ok(content.includes("Content omitted"), "Should have content omitted marker");
260
+ assert.ok(content.includes("console.log('main');"), "main.js should have full content");
261
+ assert.ok(content.includes("export function util() {}"), "utils.js should have full content");
262
+ });
263
+
264
+ // --- Test 12: Updated system prompt ---
265
+ test("Updated v2.0.0 system prompt", () => {
266
+ createStructure(TEST_DIR, {
267
+ "main.js": "x = 1",
268
+ });
269
+
270
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
271
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
175
272
 
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");
273
+ assert.ok(content.includes("code map"), "System prompt should mention code map");
274
+ assert.ok(content.includes("OL = Original Line"), "System prompt should explain OL");
275
+ assert.ok(content.includes("ML = Merged Line"), "System prompt should explain ML");
276
+ });
277
+
278
+ // --- Test 13: llms-txt system prompt ---
279
+ test("llms-txt system prompt", () => {
280
+ createStructure(TEST_DIR, {
281
+ "docs.md": "# Hello",
282
+ });
283
+
284
+ execSync(`node ${CLI_PATH} -o combicode.txt -l`, { cwd: TEST_DIR, stdio: "pipe" });
285
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
286
+
287
+ assert.ok(content.includes("definitive source of truth"), "Should use llms-txt prompt");
288
+ assert.ok(!content.includes("OL = Original Line"), "Should NOT use default prompt");
289
+ });
290
+
291
+ // --- Test 14: File header format with OL/ML/SIZE ---
292
+ test("File headers have OL/ML/SIZE in merged_code", () => {
293
+ createStructure(TEST_DIR, {
294
+ "hello.py": "print('hello')\n",
295
+ });
179
296
 
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");
297
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
298
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
299
+
300
+ // Use regex on full content since <merged_code> appears in system prompt text too
301
+ assert.match(content, /# FILE: hello\.py \[OL: 1-\d+ \| ML: \d+-\d+ \| \d+(\.\d+)?[BKMGT]?B?\]/, "File header should have OL/ML/SIZE");
302
+ });
303
+
304
+ // --- Test 15: Recreate from combicode.txt ---
305
+ test("Recreate from combicode.txt", () => {
306
+ // First, generate a combicode.txt
307
+ createStructure(TEST_DIR, {
308
+ "src": {
309
+ "index.js": "console.log('hello');",
310
+ "utils.js": "function add(a, b) { return a + b; }",
311
+ },
312
+ "config.json": '{"key": "value"}',
313
+ });
314
+
315
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
316
+ assert.ok(fs.existsSync(OUTPUT_FILE));
317
+
318
+ // Now recreate in a new directory
319
+ const recreateDir = path.join(TEST_DIR, "recreated");
320
+ fs.mkdirSync(recreateDir);
321
+
322
+ execSync(`node ${CLI_PATH} --recreate --input combicode.txt -o ${recreateDir}`, {
323
+ cwd: TEST_DIR,
324
+ stdio: "pipe",
325
+ });
326
+
327
+ // Check files were recreated
183
328
  assert.ok(
184
- largeTestMatch[1].includes("Content omitted"),
185
- "Content should be replaced with placeholder"
329
+ fs.existsSync(path.join(recreateDir, "src/index.js")),
330
+ "src/index.js should be recreated"
186
331
  );
187
332
  assert.ok(
188
- largeTestMatch[1].includes("file size:"),
189
- "Placeholder should include file size"
333
+ fs.existsSync(path.join(recreateDir, "src/utils.js")),
334
+ "src/utils.js should be recreated"
335
+ );
336
+ assert.ok(
337
+ fs.existsSync(path.join(recreateDir, "config.json")),
338
+ "config.json should be recreated"
190
339
  );
191
340
 
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");
341
+ // Check content
342
+ const indexContent = fs.readFileSync(path.join(recreateDir, "src/index.js"), "utf8");
343
+ assert.ok(indexContent.includes("console.log('hello');"), "Recreated index.js should have correct content");
344
+ });
345
+
346
+ // --- Test 16: Recreate dry run ---
347
+ test("Recreate dry run", () => {
348
+ createStructure(TEST_DIR, {
349
+ "main.js": "console.log('hello');",
350
+ });
195
351
 
196
- // Dry run should show content omitted count
197
- const skipContentDryRunOutput = execSync(`node ${CLI_PATH} --dry-run --skip-content "**/*.test.ts"`, {
352
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
353
+
354
+ const out = execSync(`node ${CLI_PATH} --recreate --input combicode.txt --dry-run`, {
198
355
  cwd: TEST_DIR,
199
356
  }).toString();
200
- assert.match(skipContentDryRunOutput, /Content omitted:/, "Dry run should show content omitted count");
201
357
 
202
- console.log("โœ… All Node.js tests passed!");
203
- } catch (error) {
204
- console.error("โŒ Test Failed:", error.message);
358
+ assert.match(out, /Files to recreate/, "Should show 'Files to recreate'");
359
+ assert.ok(out.includes("main.js"), "Should list main.js");
360
+ });
361
+
362
+ // --- Test 17: Recreate with --overwrite ---
363
+ test("Recreate with --overwrite", () => {
364
+ createStructure(TEST_DIR, {
365
+ "main.js": "console.log('hello');",
366
+ });
367
+
368
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
369
+
370
+ // Create existing file with different content
371
+ const recreateDir = path.join(TEST_DIR, "out");
372
+ fs.mkdirSync(recreateDir);
373
+ fs.mkdirSync(path.join(recreateDir, ""), { recursive: true });
374
+ fs.writeFileSync(path.join(recreateDir, "main.js"), "OLD CONTENT");
375
+
376
+ // Without overwrite - should skip
377
+ execSync(`node ${CLI_PATH} --recreate --input combicode.txt -o ${recreateDir}`, {
378
+ cwd: TEST_DIR,
379
+ stdio: "pipe",
380
+ });
381
+ let existingContent = fs.readFileSync(path.join(recreateDir, "main.js"), "utf8");
382
+ assert.ok(existingContent.includes("OLD CONTENT"), "Without --overwrite, file should keep old content");
383
+
384
+ // With overwrite
385
+ execSync(`node ${CLI_PATH} --recreate --input combicode.txt -o ${recreateDir} --overwrite`, {
386
+ cwd: TEST_DIR,
387
+ stdio: "pipe",
388
+ });
389
+ existingContent = fs.readFileSync(path.join(recreateDir, "main.js"), "utf8");
390
+ assert.ok(existingContent.includes("console.log('hello');"), "With --overwrite, file should be updated");
391
+ });
392
+
393
+ // --- Test 18: --no-header flag ---
394
+ test("--no-header omits system prompt and code_index", () => {
395
+ createStructure(TEST_DIR, {
396
+ "main.js": "x = 1",
397
+ });
398
+
399
+ execSync(`node ${CLI_PATH} -o combicode.txt --no-header`, { cwd: TEST_DIR, stdio: "pipe" });
400
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
401
+
402
+ assert.ok(!content.includes("<code_index>"), "Should NOT contain <code_index>");
403
+ assert.ok(!content.includes("expert software architect"), "Should NOT contain system prompt");
404
+ assert.ok(content.includes("<merged_code>"), "Should still have <merged_code>");
405
+ assert.ok(content.includes("# FILE: main.js"), "Should still contain file content");
406
+ });
407
+
408
+ // --- Test 19: Include ext filter ---
409
+ test("Include extension filter", () => {
410
+ createStructure(TEST_DIR, {
411
+ "main.py": "print('hello')",
412
+ "style.css": "body { }",
413
+ "readme.md": "# Hello",
414
+ });
415
+
416
+ execSync(`node ${CLI_PATH} -o combicode.txt -i .py`, { cwd: TEST_DIR, stdio: "pipe" });
417
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
418
+
419
+ assert.ok(content.includes("# FILE: main.py"), "main.py should be included");
420
+ assert.ok(!content.includes("# FILE: style.css"), "style.css should be excluded");
421
+ assert.ok(!content.includes("# FILE: readme.md"), "readme.md should be excluded");
422
+ });
423
+
424
+ // --- Test 20: TypeScript interface parsing ---
425
+ test("Code map parses TypeScript interfaces", () => {
426
+ createStructure(TEST_DIR, {
427
+ "types.ts": [
428
+ "export interface Config {",
429
+ " host: string;",
430
+ " port: number;",
431
+ " debug: boolean;",
432
+ "}",
433
+ "",
434
+ "export class Server {",
435
+ " constructor(config: Config) {",
436
+ " this.config = config;",
437
+ " }",
438
+ "}",
439
+ "",
440
+ ].join("\n"),
441
+ });
442
+
443
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
444
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
445
+
446
+ assert.ok(content.includes("interface Config"), "Code map should include interface Config");
447
+ assert.ok(content.includes("class Server"), "Code map should include class Server");
448
+ });
449
+
450
+ // --- Test 21: ML line numbers accuracy ---
451
+ test("ML line numbers point to actual content lines", () => {
452
+ createStructure(TEST_DIR, {
453
+ "first.py": "print('first')\nprint('second')\nprint('third')\n",
454
+ "second.js": "const a = 1;\nconst b = 2;\n",
455
+ subdir: {
456
+ "third.txt": "hello\nworld\n",
457
+ },
458
+ });
459
+
460
+ execSync(`node ${CLI_PATH} -o combicode.txt`, { cwd: TEST_DIR, stdio: "pipe" });
461
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
462
+ const outputLines = content.split("\n");
463
+
464
+ // Parse all file headers and verify ML ranges
465
+ const fileHeaderRegex = /# FILE:\s*(\S+)\s*\[OL: (\d+)-(\d+) \| ML: (\d+)-(\d+) \|/g;
466
+ let match;
467
+ let verified = 0;
468
+
469
+ while ((match = fileHeaderRegex.exec(content)) !== null) {
470
+ const fileName = match[1];
471
+ const mlStart = parseInt(match[4], 10);
472
+ const mlEnd = parseInt(match[5], 10);
473
+
474
+ // ML lines are 1-indexed, array is 0-indexed
475
+ const firstContentLine = outputLines[mlStart - 1];
476
+ const lastContentLine = outputLines[mlEnd - 1];
477
+
478
+ // Verify the line BEFORE mlStart is the opening backticks
479
+ const lineBeforeMl = outputLines[mlStart - 2];
480
+ assert.strictEqual(
481
+ lineBeforeMl, "````",
482
+ `Line before ML start for ${fileName} should be opening backticks, got: "${lineBeforeMl}" (line ${mlStart - 1})`
483
+ );
484
+
485
+ // Verify the line AFTER mlEnd is the closing backticks
486
+ const lineAfterMl = outputLines[mlEnd];
487
+ assert.strictEqual(
488
+ lineAfterMl, "````",
489
+ `Line after ML end for ${fileName} should be closing backticks, got: "${lineAfterMl}" (line ${mlEnd + 1})`
490
+ );
491
+
492
+ // Verify content is NOT backticks (i.e., we're inside the content block)
493
+ assert.notStrictEqual(
494
+ firstContentLine, "````",
495
+ `ML start for ${fileName} should point to content, not backticks`
496
+ );
497
+
498
+ verified++;
499
+ }
500
+
501
+ assert.ok(verified >= 3, `Should have verified at least 3 files, got ${verified}`);
502
+ });
503
+
504
+ // --- Test 22: ML accuracy with multiple files of varying sizes ---
505
+ test("ML accuracy across many files with different line counts", () => {
506
+ const files = {};
507
+ // Create files with varying numbers of lines (1 line to 20 lines)
508
+ for (let i = 1; i <= 5; i++) {
509
+ const lines = [];
510
+ for (let j = 1; j <= i * 4; j++) {
511
+ lines.push(`line_${j}_of_file_${i}`);
512
+ }
513
+ files[`file${i}.py`] = lines.join("\n") + "\n";
514
+ }
515
+ createStructure(TEST_DIR, files);
516
+
517
+ execSync(`node ${CLI_PATH} -o combicode.txt --no-parse`, { cwd: TEST_DIR, stdio: "pipe" });
518
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
519
+ const outputLines = content.split("\n");
520
+
521
+ const fileHeaderRegex = /# FILE:\s*(\S+)\s*\[OL: (\d+)-(\d+) \| ML: (\d+)-(\d+) \|/g;
522
+ let match;
523
+
524
+ while ((match = fileHeaderRegex.exec(content)) !== null) {
525
+ const fileName = match[1];
526
+ const olStart = parseInt(match[2], 10);
527
+ const olEnd = parseInt(match[3], 10);
528
+ const mlStart = parseInt(match[4], 10);
529
+ const mlEnd = parseInt(match[5], 10);
530
+
531
+ // Verify OL range matches content line count
532
+ const expectedLineCount = olEnd - olStart + 1;
533
+ const mlLineCount = mlEnd - mlStart + 1;
534
+ assert.strictEqual(
535
+ mlLineCount, expectedLineCount,
536
+ `${fileName}: ML range (${mlLineCount} lines) should match OL range (${expectedLineCount} lines)`
537
+ );
538
+
539
+ // Verify first content line matches
540
+ const firstLine = outputLines[mlStart - 1];
541
+ assert.ok(
542
+ firstLine && firstLine.startsWith("line_1_of_"),
543
+ `${fileName}: ML ${mlStart} should point to first line of content, got: "${firstLine}"`
544
+ );
545
+
546
+ // Verify last content line matches
547
+ const lastLine = outputLines[mlEnd - 1];
548
+ assert.ok(
549
+ lastLine && lastLine.startsWith(`line_${expectedLineCount}_of_`),
550
+ `${fileName}: ML ${mlEnd} should point to last line of content, got: "${lastLine}"`
551
+ );
552
+ }
553
+ });
554
+
555
+ // --- Done ---
556
+ console.log(`\n๐Ÿ“Š Results: ${passed}/${total} tests passed`);
557
+ if (passed < total) {
205
558
  process.exit(1);
206
- } finally {
207
- teardown();
208
559
  }
560
+ console.log("โœ… All Node.js tests passed!");
561
+
562
+ teardown();
209
563
  }
210
564
 
211
- runTest();
565
+ runTests();