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.
- package/CHANGELOG.md +22 -0
- package/README.md +340 -19
- package/index.js +1482 -142
- package/package.json +22 -1
- 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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
assert.match(
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
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
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
assert.ok(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
-
assert.ok(
|
|
112
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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.
|
|
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: "
|
|
255
|
+
stdio: "pipe",
|
|
162
256
|
});
|
|
163
|
-
content = fs.readFileSync(OUTPUT_FILE, "utf8");
|
|
257
|
+
const content = fs.readFileSync(OUTPUT_FILE, "utf8");
|
|
164
258
|
|
|
165
|
-
|
|
166
|
-
assert.ok(
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"
|
|
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
|
-
|
|
177
|
-
assert.ok(content.includes("
|
|
178
|
-
assert.ok(content.includes("
|
|
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
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
"
|
|
329
|
+
fs.existsSync(path.join(recreateDir, "src/index.js")),
|
|
330
|
+
"src/index.js should be recreated"
|
|
186
331
|
);
|
|
187
332
|
assert.ok(
|
|
188
|
-
|
|
189
|
-
"
|
|
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
|
-
//
|
|
193
|
-
|
|
194
|
-
assert.ok(
|
|
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
|
-
|
|
197
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
565
|
+
runTests();
|