repomeld 2.0.2 โ†’ 2.0.4

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 (3) hide show
  1. package/README.md +118 -251
  2. package/bin/cli.js +208 -310
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,11 @@
2
2
 
3
3
  > Meld your entire repo into a single file โ€” perfect for AI context, code reviews & sharing.
4
4
 
5
- > ๐Ÿ’ผ **Author [`susheelhbti@gmail.com`](mailto:susheelhbti@gmail.com) is open to freelance & full-time work.**
5
+ ---
6
+
7
+ > ## ๐Ÿ’ผ Open to Work
8
+ > **The author is available for freelance & full-time work.**
9
+ > ๐Ÿ“ง [susheelhbti@gmail.com](mailto:susheelhbti@gmail.com) โ€” reach out anytime!
6
10
 
7
11
  ---
8
12
 
@@ -21,320 +25,181 @@ cd your-project
21
25
  repomeld
22
26
  ```
23
27
 
24
- **No flags needed.** repomeld will walk you through 7 quick questions and configure itself:
25
-
26
- ```
27
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
28
- โ•‘ repomeld v1.2.0 โ•‘
29
- โ•‘ Meld your repo into one file ๐Ÿ”ฅ โ•‘
30
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
31
-
32
- ๐Ÿ’ผ Author susheelhbti@gmail.com is open to freelance & full-time work
33
-
34
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
35
- โ”‚ Interactive setup โ€” answer 7 quick questions โ”‚
36
- โ”‚ Press Enter to accept the [default] value โ”‚
37
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
38
-
39
- ๐Ÿ“„ 1/7 Output filename? [repomeld_output.txt]
40
- >
41
-
42
- ๐ŸŽจ 2/7 Header style?
43
- 1) banner โ€” clear dividers with file info (default)
44
- 2) markdown โ€” great for pasting into AI prompts
45
- 3) minimal โ€” filename only, no extra formatting
46
- >
47
-
48
- ๐Ÿ” 3/7 Filter to specific file extensions?
49
- e.g. js ts php py (leave blank = include ALL files)
50
- >
51
-
52
- ๐Ÿ—๏ธ 4/7 Enable structure-only mode? (y/n) [n]
53
- Strips function bodies โ€” keeps class names + signatures only.
54
- Cuts a 400-line file to ~30 lines. Perfect for AI architecture review.
55
- >
56
-
57
- ๐Ÿ”— 5/7 Generate a code review graph? (y/n) [n]
58
- Adds a Mermaid call graph at the top of the output.
59
- AI models read Mermaid natively โ€” shows which files connect to which.
60
- >
61
-
62
- ๐Ÿ“‹ 6/7 Include a table of contents? (y/n) [y]
63
- >
64
-
65
- ๐Ÿงช 7/7 Dry run only? (y/n) [n]
66
- Preview which files would be included without writing anything.
67
- >
68
- ```
69
-
70
- After answering, it shows a summary and runs:
71
-
72
- ```
73
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
74
- โ”‚ Your configuration โ”‚
75
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
76
- โ”‚ Output : repomeld_output.txt โ”‚
77
- โ”‚ Style : markdown โ”‚
78
- โ”‚ Extensions : php ts โ”‚
79
- โ”‚ Structure only : yes โœ… โ”‚
80
- โ”‚ Review graph : yes โœ… โ”‚
81
- โ”‚ Table of cont. : yes โ”‚
82
- โ”‚ Dry run : no โ”‚
83
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
84
- ```
28
+ That's it. repomeld walks your project, skips noise (node_modules, lock files, build folders, etc.) and writes everything into one readable file.
85
29
 
86
30
  ---
87
31
 
88
- ## Skip the Wizard (Power Users)
32
+ ## Auto-Numbered Output โ€” No Overwriting
89
33
 
90
- If you pass any flags, repomeld skips the wizard and runs directly:
34
+ Every time you run repomeld it creates a **new numbered file** so previous runs are never lost:
91
35
 
92
- ```bash
93
- repomeld --ext ts tsx --style markdown --output context.md
94
36
  ```
95
-
96
- Force the wizard at any time with:
97
-
98
- ```bash
99
- repomeld --wizard
37
+ repomeld_output.txt โ† first run
38
+ repomeld_output__2.txt โ† second run
39
+ repomeld_output__3.txt โ† third run
100
40
  ```
101
41
 
102
- ---
42
+ All previous output files are also **automatically excluded** from the next run's content โ€” so you'll never get repomeld's own output included inside itself.
103
43
 
104
- ## New Features in v1.2.0
105
-
106
- ### `--structure-only` โ€” Skeleton mode
44
+ ---
107
45
 
108
- Extracts **class names + function signatures** from every file. Strips all body code.
46
+ ## All Options
109
47
 
110
- ```bash
111
- repomeld --structure-only
112
48
  ```
49
+ Usage: repomeld [options]
113
50
 
114
- **Before** (400 lines of PHP):
115
- ```php
116
- public function available(Request $request)
117
- {
118
- $validated = $request->validate([...]);
119
- $guards = SecurityGuard::with('user')
120
- ->where('is_available', true)
121
- ...
122
- // 50 more lines
123
- }
124
- ```
51
+ Options:
52
+ -V, --version Show version number
53
+ -h, --help Show help
125
54
 
126
- **After** (1 line):
127
- ```php
128
- public function available(Request $request) { ... }
129
- ```
55
+ Output:
56
+ -o, --output <filename> Output file name
57
+ Default: "repomeld_output.txt"
58
+ Auto-numbered if the file already exists.
130
59
 
131
- Supports **PHP, JavaScript, TypeScript, Python**. Other languages fall back to the first 30 lines.
60
+ Filtering:
61
+ -e, --ext <exts...> Only include files with these extensions
62
+ e.g. --ext js ts jsx tsx
132
63
 
133
- Perfect for:
134
- - Giving AI a full architecture overview without hitting context limits
135
- - Code review focusing on structure, not implementation
136
- - Onboarding โ€” understand a codebase in seconds
64
+ --include <patterns...> Only include files whose path matches a pattern
65
+ e.g. --include src/
137
66
 
138
- ---
67
+ --exclude <patterns...> Skip files whose path matches a pattern
68
+ e.g. --exclude test spec __tests__
139
69
 
140
- ### `--review-graph` โ€” Mermaid call graph
70
+ -i, --ignore <names...> Extra folder or file names to ignore
71
+ e.g. --ignore dist .next coverage
141
72
 
142
- Generates a **Mermaid flowchart** at the top of your output showing which files import/call which.
73
+ --force-include <names...> Force-include something that would normally be ignored
74
+ e.g. --force-include vendor bootstrap
143
75
 
144
- ```bash
145
- repomeld --review-graph
146
- ```
76
+ --max-size <kb> Skip files larger than N kilobytes
77
+ Default: 500
147
78
 
148
- Output (inside the file, readable by AI natively):
79
+ Formatting:
80
+ -s, --style <style> Header style for each file block:
81
+ banner โ€” clear dividers with file info (default)
82
+ markdown โ€” fenced code blocks, great for AI prompts
83
+ minimal โ€” filename only, no extra formatting
149
84
 
150
- ````
151
- ```mermaid
152
- flowchart TD
153
- %% Code Review Graph โ€” generated by repomeld
85
+ --no-toc Don't include a table of contents at the top
154
86
 
155
- src_index_js["๐Ÿ“„ index.js\n[repomeld, getAllFiles, buildHeader]"]
156
- src_utils_js["๐Ÿ“„ utils.js\n[formatSize, getLanguage]"]
157
- src_graph_js["๐Ÿ“„ graph.js\n[buildReviewGraph]"]
87
+ --no-meta Hide per-file metadata (line count, size, language)
158
88
 
159
- src_index_js --> src_utils_js
160
- src_index_js --> src_graph_js
161
- ```
162
- ````
89
+ --trim Trim leading/trailing whitespace from each file
163
90
 
164
- AI models like Claude, GPT-4, and Gemini read Mermaid natively โ€” no image needed.
91
+ Advanced:
92
+ --lines-before <n> Skip the first N lines of every file
93
+ --lines-after <n> Skip the last N lines of every file
94
+ --dry-run Preview which files would be included โ€” nothing is written
95
+ ```
165
96
 
166
97
  ---
167
98
 
168
- ### Power combo: both together
99
+ ## Examples
169
100
 
170
101
  ```bash
171
- repomeld --structure-only --review-graph
172
- ```
102
+ # Basic โ€” include everything, auto-numbered output
103
+ repomeld
173
104
 
174
- AI gets:
175
- 1. **The map** โ€” which files connect to which (graph)
176
- 2. **The skeleton** โ€” every class and function signature (structure)
177
- 3. **Zero noise** โ€” no implementation details
105
+ # Only TypeScript files
106
+ repomeld --ext ts tsx
178
107
 
179
- This is the best way to give AI a complete understanding of a large codebase without wasting context.
108
+ # Only files inside src/
109
+ repomeld --include src/
180
110
 
181
- ---
111
+ # Skip test files
112
+ repomeld --exclude test spec __tests__
182
113
 
183
- ## Smart Auto-Ignore
184
-
185
- repomeld automatically skips common public libraries and vendor files.
186
-
187
- | Category | Examples |
188
- |---|---|
189
- | Project meta | `package.json`, `README.md`, `yarn.lock`, `.gitignore` |
190
- | Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/` |
191
- | Vendor folders | `vendor/`, `libs/`, `plugins/`, `assets/vendor/` |
192
- | Bootstrap | `bootstrap.min.css`, `bootstrap.bundle.min.js` |
193
- | jQuery | `jquery.min.js`, `jquery-ui.min.js` |
194
- | Font Awesome | `all.min.css`, `fontawesome-free/` |
195
- | Tailwind | `tailwind.min.css`, `tailwindcss/` |
196
- | Chart / D3 / Three | `chart.min.js`, `d3.min.js`, `three.min.js` |
197
- | Axios / Lodash | `axios.min.js`, `lodash.min.js` |
198
- | Rich text editors | `tinymce/`, `ckeditor/`, `codemirror/` |
199
- | Admin templates | `AdminLTE/`, `metronic/`, `coreui/` |
200
- | Env / Secrets | `.env`, `.env.local`, `.env.production` |
114
+ # Markdown style โ€” great for pasting into AI chats
115
+ repomeld --style markdown --output context.md
201
116
 
202
- ---
117
+ # Preview what would be included without writing anything
118
+ repomeld --dry-run
203
119
 
204
- ## All Options
120
+ # Ignore extra folders on top of defaults
121
+ repomeld --ignore coverage logs tmp
205
122
 
206
- ```
207
- Usage: repomeld [options]
123
+ # Only small files โ€” skip anything over 100 KB
124
+ repomeld --max-size 100
208
125
 
209
- Options:
210
- -V, --version Show version
211
- -h, --help Show help
212
- --wizard Interactive step-by-step setup
213
-
214
- Output:
215
- -o, --output <filename> Output file name (default: "repomeld_output.txt")
216
-
217
- Filtering:
218
- -e, --ext <exts...> Only include specific extensions
219
- e.g. --ext js ts jsx tsx
220
- --include <patterns...> Only include files matching patterns
221
- e.g. --include src/
222
- --exclude <patterns...> Exclude files matching patterns
223
- e.g. --exclude test spec
224
- -i, --ignore <names...> Extra folders/files to ignore
225
- --force-include <names...> Force-include files ignored by default
226
- --max-size <kb> Skip files larger than N KB (default: 500)
227
-
228
- Formatting:
229
- -s, --style <style> Header style: banner | markdown | minimal
230
- --no-toc Disable table of contents
231
- --no-meta Hide file metadata (lines, size, lang)
232
- --trim Trim leading/trailing whitespace per file
233
-
234
- Advanced:
235
- --lines-before <n> Skip first N lines of each file
236
- --lines-after <n> Skip last N lines of each file
237
- --dry-run Preview without writing output
238
-
239
- AI / Code Review:
240
- --structure-only Extract class + function signatures only (no body)
241
- --review-graph Prepend a Mermaid call graph to the output
242
- ```
126
+ # Force-include a vendor file that's normally ignored
127
+ repomeld --force-include bootstrap
243
128
 
244
- ---
129
+ # No table of contents, no metadata
130
+ repomeld --no-toc --no-meta
245
131
 
246
- ## Examples
132
+ # Combine filters
133
+ repomeld --ext php --include Controllers --exclude test --style markdown
134
+ ```
247
135
 
248
- ```bash
249
- # Wizard mode (auto when no flags given)
250
- repomeld
136
+ ---
251
137
 
252
- # Force wizard even with flags
253
- repomeld --wizard
138
+ ## Auto-Ignored by Default
254
139
 
255
- # Only PHP controllers, structure only
256
- repomeld --ext php --include Controllers --structure-only
140
+ repomeld automatically skips these so your output stays clean:
257
141
 
258
- # Full code + call graph
259
- repomeld --review-graph
142
+ | Category | What's skipped |
143
+ |-----------------|-----------------------------------------------------|
144
+ | Dependencies | `node_modules/` |
145
+ | Version control | `.git/` |
146
+ | Env / secrets | `.env`, `.env.local`, `.env.production` |
147
+ | Lock files | `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml` |
148
+ | Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `.cache/` |
149
+ | OS files | `.DS_Store` |
150
+ | Project meta | `package.json`, `README.md` |
151
+ | repomeld output | `repomeld_output.txt` and all `repomeld_output__N.txt` files |
260
152
 
261
- # Best for AI: skeleton + graph, markdown style
262
- repomeld --structure-only --review-graph --style markdown --output ai_context.md
153
+ You can add your own permanent ignore rules via a `repomeld.ignore.json` file (see below).
263
154
 
264
- # Only TypeScript inside src/
265
- repomeld --ext ts tsx --include src/
155
+ ---
266
156
 
267
- # Exclude tests
268
- repomeld --exclude test spec __tests__
157
+ ## Custom Ignore File
269
158
 
270
- # Dry run โ€” preview only
271
- repomeld --dry-run
159
+ Create a `repomeld.ignore.json` in your project root:
272
160
 
273
- # Large repo: structure only, skip files over 100 KB
274
- repomeld --structure-only --max-size 100
161
+ ```json
162
+ {
163
+ "ignore": [
164
+ "coverage",
165
+ "logs",
166
+ "tmp",
167
+ "*.min.js"
168
+ ]
169
+ }
275
170
  ```
276
171
 
172
+ These are merged with the defaults every time repomeld runs.
173
+
277
174
  ---
278
175
 
279
176
  ## Output Format
280
177
 
281
- ### With `--review-graph`
178
+ Each run produces a file like this:
282
179
 
283
180
  ```
284
- # Generated by repomeld v1.2.0
285
- # Date : 2024-01-01T00:00:00.000Z
181
+ # Generated by repomeld v1.0.0
182
+ # Date : 2025-04-20T10:00:00.000Z
286
183
  # Source : /your/project
287
- # Files : 8
288
- # Lines : 312
289
- # Graph : review-graph enabled
290
- # Author : susheelhbti@gmail.com (open to work)
184
+ # Files : 12
185
+ # Lines : 847
291
186
 
187
+ TABLE OF CONTENTS
292
188
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
293
- CODE REVIEW GRAPH
189
+ 1. src/index.js
190
+ 2. src/utils.js
191
+ 3. src/config.js
192
+ ...
294
193
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
295
194
 
296
- ```mermaid
297
- flowchart TD
298
- ...
299
- ```
195
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
196
+ FILE: src/index.js [120 lines | 3.2 KB | javascript]
197
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
300
198
 
301
- TABLE OF CONTENTS
302
- ...
199
+ ... file contents ...
303
200
  ```
304
201
 
305
- ### With `--structure-only` (PHP example)
306
-
307
- ```php
308
- namespace App\Http\Controllers;
309
- use App\Models\SecurityGuard;
310
- use App\Models\SecurityRequest;
311
-
312
- class GuardController extends Controller
313
- {
314
- /** Get guard's own profile */
315
- public function profile() { ... }
316
- public function updateProfile(Request $request) { ... }
317
- public function bookings() { ... }
318
- public function available(Request $request) { ... }
319
- public function toggleAvailability() { ... }
320
- public function rate(Request $request, $id) { ... }
321
- }
322
- ```
323
-
324
- ---
325
-
326
- ## Publish / Update
327
-
328
- ```bash
329
- npm version patch # 1.2.0 โ†’ 1.2.1
330
- npm publish
331
- ```
332
-
333
- ---
334
-
335
- ## Contributing
336
-
337
- Found a popular library that should be in the ignore list? Open a PR!
202
+ With `--style markdown` each file becomes a fenced code block โ€” paste directly into Claude, ChatGPT, or any AI tool.
338
203
 
339
204
  ---
340
205
 
@@ -344,4 +209,6 @@ MIT
344
209
 
345
210
  ---
346
211
 
347
- > ๐Ÿ’ผ **Looking to hire?** The author [`susheelhbti@gmail.com`](mailto:susheelhbti@gmail.com) is available for freelance and full-time work.
212
+ > ## ๐Ÿ’ผ Hire the Author
213
+ > Built by a developer available for **freelance and full-time opportunities**.
214
+ > Got a project? Let's talk โ€” ๐Ÿ“ง **[susheelhbti@gmail.com](mailto:susheelhbti@gmail.com)**
package/bin/cli.js CHANGED
@@ -2,18 +2,27 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
- const readline = require("readline");
6
5
  const { program } = require("commander");
7
6
 
8
- const VERSION = "1.2.0";
9
-
10
- const AUTHOR_NOTE = " ๐Ÿ’ผ Author susheelhbti@gmail.com is open to freelance & full-time work";
7
+ const VERSION = "1.0.0";
11
8
 
12
9
  const DEFAULT_IGNORE = [
13
- "node_modules", ".git", ".env", ".env.local", ".env.production",
14
- ".DS_Store", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
15
- ".next", ".nuxt", "dist", "build", ".cache", "package.json",
16
- "README.md", "repomeld_output.txt",
10
+ "node_modules",
11
+ ".git",
12
+ ".env",
13
+ ".env.local",
14
+ ".env.production",
15
+ ".DS_Store",
16
+ "package-lock.json",
17
+ "yarn.lock",
18
+ "pnpm-lock.yaml",
19
+ ".next",
20
+ ".nuxt",
21
+ "dist",
22
+ "build",
23
+ ".cache",
24
+ "package.json",
25
+ "README.md",
17
26
  ];
18
27
 
19
28
  function loadIgnoreConfig() {
@@ -23,7 +32,9 @@ function loadIgnoreConfig() {
23
32
  if (fs.existsSync(loc)) {
24
33
  try {
25
34
  const data = JSON.parse(fs.readFileSync(loc, "utf8"));
26
- if (Array.isArray(data.ignore)) return data.ignore;
35
+ if (Array.isArray(data.ignore)) {
36
+ return data.ignore;
37
+ }
27
38
  } catch {
28
39
  console.warn(` โš ๏ธ Could not parse ${loc}, using defaults.`);
29
40
  }
@@ -50,17 +61,31 @@ function getLanguage(filePath) {
50
61
 
51
62
  function getAllFiles(dirPath, ignoreList, fileList = []) {
52
63
  let entries;
53
- try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); }
54
- catch { return fileList; }
64
+ try {
65
+ entries = fs.readdirSync(dirPath, { withFileTypes: true });
66
+ } catch {
67
+ return fileList;
68
+ }
69
+
55
70
  for (const entry of entries) {
56
71
  const fullPath = path.join(dirPath, entry.name);
57
72
  const relativePath = path.relative(process.cwd(), fullPath);
58
- if (ignoreList.some(
59
- (ig) => entry.name === ig || relativePath.startsWith(ig + path.sep) || relativePath === ig
60
- )) continue;
61
- if (entry.isDirectory()) getAllFiles(fullPath, ignoreList, fileList);
62
- else if (entry.isFile()) fileList.push(fullPath);
73
+
74
+ if (
75
+ ignoreList.some(
76
+ (ig) => entry.name === ig || relativePath.startsWith(ig + path.sep) || relativePath === ig
77
+ )
78
+ ) {
79
+ continue;
80
+ }
81
+
82
+ if (entry.isDirectory()) {
83
+ getAllFiles(fullPath, ignoreList, fileList);
84
+ } else if (entry.isFile()) {
85
+ fileList.push(fullPath);
86
+ }
63
87
  }
88
+
64
89
  return fileList;
65
90
  }
66
91
 
@@ -70,9 +95,13 @@ function isBinaryFile(filePath) {
70
95
  const fd = fs.openSync(filePath, "r");
71
96
  const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
72
97
  fs.closeSync(fd);
73
- for (let i = 0; i < bytesRead; i++) if (buffer[i] === 0) return true;
98
+ for (let i = 0; i < bytesRead; i++) {
99
+ if (buffer[i] === 0) return true;
100
+ }
74
101
  return false;
75
- } catch { return true; }
102
+ } catch {
103
+ return true;
104
+ }
76
105
  }
77
106
 
78
107
  function matchesExtensions(filePath, exts) {
@@ -95,308 +124,184 @@ function formatSize(bytes) {
95
124
 
96
125
  function printBanner() {
97
126
  console.log(`
98
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
99
- โ•‘ repomeld v${VERSION} โ•‘
100
- โ•‘ Meld your repo into one file ๐Ÿ”ฅ โ•‘
101
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•`);
102
- console.log(AUTHOR_NOTE);
103
- console.log();
127
+ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
128
+ โ•‘ repomeld v${VERSION} โ•‘
129
+ โ•‘ Meld your repo into one file ๐Ÿ”ฅ โ•‘
130
+ โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ
131
+ โ•‘ ๐Ÿ’ผ Author available for freelance & full-time work โ•‘
132
+ โ•‘ ๐Ÿ“ง susheelhbti@gmail.com โ•‘
133
+ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•`);
104
134
  }
105
135
 
106
136
  function buildHeader(style, relativePath, filePath, lineCount, showMeta) {
107
137
  const lang = getLanguage(filePath);
108
138
  const size = formatSize(fs.statSync(filePath).size);
109
139
  const meta = showMeta ? ` [${lineCount} lines | ${size}${lang ? " | " + lang : ""}]` : "";
110
- if (style === "markdown") return `\n## ๐Ÿ“„ ${relativePath}${meta}\n\n\`\`\`${lang}\n`;
111
- if (style === "minimal") return `\n# ${relativePath}\n`;
140
+
141
+ if (style === "markdown") {
142
+ return `\n## ๐Ÿ“„ ${relativePath}${meta}\n\n\`\`\`${lang}\n`;
143
+ }
144
+ if (style === "minimal") {
145
+ return `\n# ${relativePath}\n`;
146
+ }
147
+ // default: banner style
112
148
  const divider = "โ”€".repeat(60);
113
149
  return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
114
150
  }
115
151
 
116
152
  function buildFooter(style) {
117
- return style === "markdown" ? "\n```\n" : "\n";
153
+ if (style === "markdown") return "\n```\n";
154
+ return "\n";
118
155
  }
119
156
 
120
157
  function buildTableOfContents(files, cwd) {
121
158
  let toc = "TABLE OF CONTENTS\n" + "โ•".repeat(60) + "\n";
122
159
  files.forEach((f, i) => {
123
- toc += ` ${String(i + 1).padStart(3, " ")}. ${path.relative(cwd, f)}\n`;
160
+ const rel = path.relative(cwd, f);
161
+ toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
124
162
  });
125
163
  toc += "โ•".repeat(60) + "\n\n";
126
164
  return toc;
127
165
  }
128
166
 
129
- // โ”€โ”€โ”€ STRUCTURE EXTRACTION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
130
-
131
- function extractStructure(content, filePath) {
132
- const lang = getLanguage(filePath);
133
- const lines = content.split("\n");
134
- const output = [];
135
-
136
- if (lang === "php") {
137
- let insideClass = false;
138
- let braceDepth = 0;
139
- let classStartDepth = 0;
140
- for (const line of lines) {
141
- const trimmed = line.trim();
142
- if (trimmed.startsWith("namespace ") || trimmed.startsWith("use ")) { output.push(line); continue; }
143
- const classMatch = trimmed.match(/^(abstract\s+class|class|interface|trait)\s+(\w+)(\s+extends\s+\w+)?(\s+implements\s+[\w,\s]+)?/);
144
- if (classMatch) {
145
- if (output.length && output[output.length - 1] !== "") output.push("");
146
- output.push(line); insideClass = true; classStartDepth = braceDepth;
147
- }
148
- braceDepth += (line.match(/\{/g) || []).length;
149
- braceDepth -= (line.match(/\}/g) || []).length;
150
- if (insideClass && braceDepth <= classStartDepth && trimmed === "}") {
151
- output.push("}"); insideClass = false; output.push(""); continue;
152
- }
153
- if (trimmed.startsWith("/**") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
154
- if (insideClass) output.push(line); continue;
155
- }
156
- if (insideClass) {
157
- const methodMatch = trimmed.match(/^(public|protected|private|static|abstract|final|\s)*(function\s+\w+\s*\([^)]*\)(\s*:\s*[\w\?\|\\]+)?)/);
158
- if (methodMatch) output.push(` ${methodMatch[0].trim()} { ... }`);
159
- if (trimmed.match(/^(public|protected|private|const)\s+/) && trimmed.endsWith(";") && !trimmed.includes("function"))
160
- output.push(` ${trimmed}`);
161
- }
162
- }
163
- return output.join("\n");
164
- }
165
-
166
- if (["javascript", "typescript"].includes(lang)) {
167
- for (const line of lines) {
168
- const trimmed = line.trim();
169
- if (trimmed.match(/^(export\s+)?(abstract\s+)?class\s+\w+/)) { output.push(line); continue; }
170
- if (trimmed.match(/^(export\s+)?(async\s+)?function\s+\w+\s*\(/)) {
171
- output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
172
- }
173
- if (trimmed.match(/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/)) {
174
- output.push(`${line.match(/^\s*/)[0]}${trimmed.split("=>")[0].trim()} => { ... }`); continue;
175
- }
176
- if (trimmed.match(/^(async\s+)?(\w+)\s*\([^)]*\)\s*(\:\s*[\w<>\[\]]+)?\s*\{/) &&
177
- !["if","while","for","switch"].some(k => trimmed.startsWith(k))) {
178
- output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
179
- }
180
- if (trimmed.startsWith("import ")) output.push(line);
181
- if (trimmed === "}") output.push(line);
182
- }
183
- return output.join("\n");
184
- }
185
-
186
- if (lang === "python") {
187
- for (const line of lines) {
188
- const t = line.trim();
189
- if (t.startsWith("class ") || t.startsWith("def ") || t.startsWith("async def "))
190
- output.push(line.replace(/:[\s\S]*$/, ":"));
191
- if (t.startsWith("import ") || t.startsWith("from ")) output.push(line);
192
- }
193
- return output.join("\n");
194
- }
195
-
196
- return lines.slice(0, 30).join("\n") + "\n... (structure extraction not supported for this language)";
197
- }
198
-
199
- // โ”€โ”€โ”€ REVIEW GRAPH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
200
-
201
- function buildReviewGraph(files, cwd) {
202
- const fileFunctions = {};
203
- const fileImports = {};
204
-
205
- for (const filePath of files.slice(0, 25)) {
206
- const lang = getLanguage(filePath);
207
- if (!["javascript", "typescript", "php", "python"].includes(lang)) continue;
208
- let content;
209
- try { content = fs.readFileSync(filePath, "utf8"); } catch { continue; }
210
- const rel = path.relative(cwd, filePath);
211
- const nodeId = rel.replace(/[^a-zA-Z0-9]/g, "_");
212
- fileFunctions[rel] = { nodeId, functions: [] };
213
- fileImports[rel] = [];
214
- for (const line of content.split("\n")) {
215
- const t = line.trim();
216
- const fnMatch = t.match(/^(export\s+)?(async\s+)?function\s+(\w+)/) ||
217
- t.match(/^(public|protected|private|static)?\s*(function\s+(\w+))/) ||
218
- t.match(/^def\s+(\w+)/);
219
- if (fnMatch) {
220
- const name = fnMatch[3] || fnMatch[2] || fnMatch[1];
221
- if (name && name.length > 2 && !["if","for","while","switch"].includes(name))
222
- fileFunctions[rel].functions.push(name);
223
- }
224
- const imp = t.match(/require\(['"]([^'"]+)['"]\)/) ||
225
- t.match(/^import\s+.*from\s+['"]([^'"]+)['"]/) ||
226
- t.match(/^use\s+(App\\[^;]+)/);
227
- if (imp) fileImports[rel].push(imp[1]);
228
- }
229
- fileFunctions[rel].functions = [...new Set(fileFunctions[rel].functions)].slice(0, 5);
230
- }
231
-
232
- const fileList = Object.keys(fileFunctions);
233
- if (fileList.length === 0) return null;
234
-
235
- let mermaid = "```mermaid\nflowchart TD\n %% Code Review Graph โ€” generated by repomeld\n\n";
236
- for (const rel of fileList) {
237
- const { nodeId, functions } = fileFunctions[rel];
238
- const label = path.basename(rel);
239
- const fnList = functions.length ? `\\n[${functions.join(", ")}]` : "";
240
- mermaid += ` ${nodeId}["๐Ÿ“„ ${label}${fnList}"]\n`;
241
- }
242
- mermaid += "\n";
243
- let edgeCount = 0;
244
- for (const rel of fileList) {
245
- for (const imp of fileImports[rel]) {
246
- const matched = fileList.find(f => {
247
- const base = path.basename(f, path.extname(f));
248
- return imp.endsWith(base) || imp.includes(base);
249
- });
250
- if (matched && matched !== rel && edgeCount < 40) {
251
- mermaid += ` ${fileFunctions[rel].nodeId} --> ${fileFunctions[matched].nodeId}\n`;
252
- edgeCount++;
253
- }
254
- }
167
+ /**
168
+ * Given a desired output path like "repomeld_output.txt", returns a
169
+ * numbered path that does not yet exist, e.g. "repomeld_output__2.txt".
170
+ * If the base name doesn't exist yet, returns it unchanged.
171
+ */
172
+ function resolveOutputPath(desiredPath) {
173
+ if (!fs.existsSync(desiredPath)) return desiredPath;
174
+
175
+ const ext = path.extname(desiredPath);
176
+ const base = desiredPath.slice(0, desiredPath.length - ext.length);
177
+
178
+ let counter = 2;
179
+ while (true) {
180
+ const candidate = `${base}__${counter}${ext}`;
181
+ if (!fs.existsSync(candidate)) return candidate;
182
+ counter++;
255
183
  }
256
- return mermaid + "```\n";
257
184
  }
258
185
 
259
- // โ”€โ”€โ”€ INTERACTIVE WIZARD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
260
-
261
- function ask(rl, question) {
262
- return new Promise((resolve) => rl.question(question, resolve));
186
+ /**
187
+ * Returns true if the given filePath looks like a repomeld output file
188
+ * (matches the base name pattern with optional __N suffix).
189
+ */
190
+ function isRepomeldOutput(filePath, baseOutputName) {
191
+ const fileName = path.basename(filePath);
192
+ const ext = path.extname(baseOutputName);
193
+ const base = path.basename(baseOutputName, ext);
194
+ const pattern = new RegExp(`^${escapeRegex(base)}(__\\d+)?${escapeRegex(ext)}$`);
195
+ return pattern.test(fileName);
263
196
  }
264
197
 
265
- async function runWizard() {
266
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
267
-
268
- console.log(" โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
269
- console.log(" โ”‚ Interactive setup โ€” answer 7 quick questions โ”‚");
270
- console.log(" โ”‚ Press Enter to accept the [default] value โ”‚");
271
- console.log(" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n");
272
-
273
- const opts = {};
274
-
275
- // 1 โ€” Output file
276
- const outRaw = await ask(rl, " ๐Ÿ“„ 1/7 Output filename? [repomeld_output.txt]\n > ");
277
- opts.output = outRaw.trim() || "repomeld_output.txt";
278
-
279
- // 2 โ€” Style
280
- console.log("\n ๐ŸŽจ 2/7 Header style?");
281
- console.log(" 1) banner โ€” clear dividers with file info (default)");
282
- console.log(" 2) markdown โ€” great for pasting into AI prompts");
283
- console.log(" 3) minimal โ€” filename only, no extra formatting");
284
- const styleRaw = await ask(rl, " > ");
285
- opts.style = ({ "2": "markdown", "3": "minimal" })[styleRaw.trim()] || "banner";
286
-
287
- // 3 โ€” Extensions
288
- console.log("\n ๐Ÿ” 3/7 Filter to specific file extensions?");
289
- console.log(" e.g. js ts php py (leave blank = include ALL files)");
290
- const extRaw = await ask(rl, " > ");
291
- opts.ext = extRaw.trim() ? extRaw.trim().split(/\s+/) : [];
292
-
293
- // 4 โ€” Structure only
294
- console.log("\n ๐Ÿ—๏ธ 4/7 Enable structure-only mode? (y/n) [n]");
295
- console.log(" Strips function bodies โ€” keeps class names + signatures only.");
296
- console.log(" Cuts a 400-line file to ~30 lines. Perfect for AI architecture review.");
297
- const structRaw = await ask(rl, " > ");
298
- opts.structureOnly = structRaw.trim().toLowerCase() === "y";
299
-
300
- // 5 โ€” Review graph
301
- console.log("\n ๐Ÿ”— 5/7 Generate a code review graph? (y/n) [n]");
302
- console.log(" Adds a Mermaid call graph at the top of the output.");
303
- console.log(" AI models read Mermaid natively โ€” shows which files connect to which.");
304
- const graphRaw = await ask(rl, " > ");
305
- opts.reviewGraph = graphRaw.trim().toLowerCase() === "y";
306
-
307
- // 6 โ€” Table of contents
308
- console.log("\n ๐Ÿ“‹ 6/7 Include a table of contents? (y/n) [y]");
309
- console.log(" Lists every included file at the top โ€” useful for large repos.");
310
- const tocRaw = await ask(rl, " > ");
311
- opts.noToc = tocRaw.trim().toLowerCase() === "n";
312
-
313
- // 7 โ€” Dry run
314
- console.log("\n ๐Ÿงช 7/7 Dry run only? (y/n) [n]");
315
- console.log(" Preview which files would be included without writing anything.");
316
- const dryRaw = await ask(rl, " > ");
317
- opts.dryRun = dryRaw.trim().toLowerCase() === "y";
318
-
319
- rl.close();
320
-
321
- // Summary
322
- console.log("\n โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
323
- console.log(" โ”‚ Your configuration โ”‚");
324
- console.log(" โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค");
325
- console.log(` โ”‚ Output : ${opts.output.padEnd(29)}โ”‚`);
326
- console.log(` โ”‚ Style : ${opts.style.padEnd(29)}โ”‚`);
327
- console.log(` โ”‚ Extensions : ${(opts.ext.length ? opts.ext.join(", ") : "all").padEnd(29)}โ”‚`);
328
- console.log(` โ”‚ Structure only : ${(opts.structureOnly ? "yes โœ…" : "no").padEnd(29)}โ”‚`);
329
- console.log(` โ”‚ Review graph : ${(opts.reviewGraph ? "yes โœ…" : "no").padEnd(29)}โ”‚`);
330
- console.log(` โ”‚ Table of cont. : ${(opts.noToc ? "no" : "yes").padEnd(29)}โ”‚`);
331
- console.log(` โ”‚ Dry run : ${(opts.dryRun ? "yes ๐Ÿงช" : "no").padEnd(29)}โ”‚`);
332
- console.log(" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n");
333
-
334
- return opts;
198
+ function escapeRegex(str) {
199
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
335
200
  }
336
201
 
337
- // โ”€โ”€โ”€ CORE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
338
-
339
202
  function repomeld(options) {
203
+ printBanner();
204
+
340
205
  const cwd = process.cwd();
341
- const outputFile = path.resolve(cwd, options.output);
206
+
207
+ // Resolve a unique (non-colliding) output path
208
+ const desiredOutput = path.resolve(cwd, options.output);
209
+ const outputFile = resolveOutputPath(desiredOutput);
210
+ const outputBaseName = path.basename(options.output);
211
+
342
212
  const forceInclude = options.forceInclude || [];
343
213
  const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
344
214
  const ignoreList = forceInclude.length
345
- ? rawIgnore.filter(ig => !forceInclude.some(fi => ig.includes(fi) || fi.includes(ig)))
215
+ ? rawIgnore.filter((ig) => !forceInclude.some((fi) => ig.includes(fi) || fi.includes(ig)))
346
216
  : rawIgnore;
347
- const filterExts = options.ext || [];
217
+ const filterExts = options.ext || [];
348
218
  const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
349
- const headerStyle = options.style || "banner";
350
- const showMeta = options.noMeta !== true;
351
- const showToc = options.noToc !== true;
352
- const dryRun = options.dryRun || false;
353
- const include = options.include || [];
354
- const exclude = options.exclude || [];
355
- const linesBefore = parseInt(options.linesBefore) || 0;
356
- const linesAfter = parseInt(options.linesAfter) || 0;
357
- const structureOnly = options.structureOnly || false;
358
- const reviewGraph = options.reviewGraph || false;
359
-
360
- console.log(` ๐Ÿ“‚ Source : ${cwd}`);
219
+ const headerStyle = options.style || "banner";
220
+ const showMeta = !options.noMeta;
221
+ const showToc = !options.noToc;
222
+ const dryRun = options.dryRun || false;
223
+ const include = options.include || [];
224
+ const exclude = options.exclude || [];
225
+ const linesBefore = parseInt(options.linesBefore) || 0;
226
+ const linesAfter = parseInt(options.linesAfter) || 0;
227
+
228
+ console.log(`\n ๐Ÿ“‚ Source : ${cwd}`);
361
229
  console.log(` ๐Ÿ“„ Output : ${path.relative(cwd, outputFile)}`);
362
230
  console.log(` ๐ŸŽจ Style : ${headerStyle}`);
363
231
  if (filterExts.length) console.log(` ๐Ÿ” Filter : .${filterExts.join(", .")}`);
364
- if (structureOnly) console.log(` ๐Ÿ—๏ธ Mode : structure only`);
365
- if (reviewGraph) console.log(` ๐Ÿ”— Graph : review graph enabled`);
366
- if (dryRun) console.log(` ๐Ÿงช Dry run : no file will be written`);
232
+ if (forceInclude.length) console.log(` ๐Ÿ“Œ Force : ${forceInclude.join(", ")}`);
233
+ if (dryRun) console.log(` ๐Ÿงช Dry run : no file will be written`);
367
234
  console.log();
368
235
 
369
236
  let allFiles = getAllFiles(cwd, ignoreList);
370
- if (filterExts.length) allFiles = allFiles.filter(f => matchesExtensions(f, filterExts));
371
- if (include.length) allFiles = allFiles.filter(f => matchesPattern(f, include));
372
- if (exclude.length) allFiles = allFiles.filter(f => !matchesPattern(f, exclude));
373
- allFiles = allFiles.filter(f => path.resolve(f) !== outputFile);
374
237
 
375
- if (allFiles.length === 0) { console.log(" โš ๏ธ No matching files found.\n"); return; }
238
+ // Filter by extension
239
+ if (filterExts.length) {
240
+ allFiles = allFiles.filter((f) => matchesExtensions(f, filterExts));
241
+ }
242
+
243
+ // Include pattern filter
244
+ if (include.length) {
245
+ allFiles = allFiles.filter((f) => matchesPattern(f, include));
246
+ }
247
+
248
+ // Exclude pattern filter
249
+ if (exclude.length) {
250
+ allFiles = allFiles.filter((f) => !matchesPattern(f, exclude));
251
+ }
252
+
253
+ // Remove ALL repomeld output files (current run + any previous numbered ones)
254
+ allFiles = allFiles.filter((f) => !isRepomeldOutput(f, outputBaseName));
255
+
256
+ if (allFiles.length === 0) {
257
+ console.log(" โš ๏ธ No matching files found.\n");
258
+ return;
259
+ }
376
260
 
377
261
  let combinedContent = "";
378
- let skipped = 0, included = 0, totalLines = 0;
262
+ let skipped = 0;
263
+ let included = 0;
264
+ let totalLines = 0;
379
265
  const includedFiles = [];
380
266
 
381
267
  for (const filePath of allFiles) {
382
268
  const relativePath = path.relative(cwd, filePath);
383
- if (isBinaryFile(filePath)) { console.log(` โญ Binary : ${relativePath}`); skipped++; continue; }
269
+
270
+ if (isBinaryFile(filePath)) {
271
+ console.log(` โญ Binary : ${relativePath}`);
272
+ skipped++;
273
+ continue;
274
+ }
275
+
384
276
  const stat = fs.statSync(filePath);
385
- if (stat.size > maxFileSizeBytes) { console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`); skipped++; continue; }
277
+ if (stat.size > maxFileSizeBytes) {
278
+ console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`);
279
+ skipped++;
280
+ continue;
281
+ }
282
+
386
283
  try {
387
284
  let content = fs.readFileSync(filePath, "utf8");
388
- if (options.trim) content = content.trim();
285
+
286
+ if (options.trim) {
287
+ content = content.trim();
288
+ }
289
+
389
290
  if (linesBefore > 0 || linesAfter > 0) {
390
291
  const lines = content.split("\n");
391
- content = lines.slice(linesBefore, linesAfter > 0 ? lines.length - linesAfter : lines.length).join("\n");
292
+ const start = linesBefore;
293
+ const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
294
+ content = lines.slice(start, end).join("\n");
392
295
  }
393
- if (structureOnly) content = extractStructure(content, filePath);
296
+
394
297
  const lineCount = content.split("\n").length;
395
298
  totalLines += lineCount;
396
299
  includedFiles.push(filePath);
300
+
397
301
  combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
398
302
  combinedContent += content;
399
303
  combinedContent += buildFooter(headerStyle);
304
+
400
305
  console.log(` โœ… ${relativePath}`);
401
306
  included++;
402
307
  } catch (err) {
@@ -405,80 +310,73 @@ function repomeld(options) {
405
310
  }
406
311
  }
407
312
 
313
+ // Build final output
408
314
  let finalOutput = "";
315
+
316
+ const timestamp = new Date().toISOString();
409
317
  finalOutput += `# Generated by repomeld v${VERSION}\n`;
410
- finalOutput += `# Date : ${new Date().toISOString()}\n`;
318
+ finalOutput += `# Date : ${timestamp}\n`;
411
319
  finalOutput += `# Source : ${cwd}\n`;
412
320
  finalOutput += `# Files : ${included}\n`;
413
321
  finalOutput += `# Lines : ${totalLines}\n`;
414
- if (structureOnly) finalOutput += `# Mode : structure-only (signatures)\n`;
415
- if (reviewGraph) finalOutput += `# Graph : review-graph enabled\n`;
416
- finalOutput += `# Author : susheelhbti@gmail.com (open to work)\n\n`;
417
-
418
- if (reviewGraph) {
419
- const graph = buildReviewGraph(includedFiles, cwd);
420
- if (graph) {
421
- finalOutput += "โ•".repeat(60) + "\n CODE REVIEW GRAPH\n" + "โ•".repeat(60) + "\n\n";
422
- finalOutput += graph + "\n";
423
- }
322
+ finalOutput += `# Author : susheelhbti@gmail.com โ€” available for freelance & full-time work\n\n`;
323
+
324
+ if (showToc) {
325
+ finalOutput += buildTableOfContents(includedFiles, cwd);
424
326
  }
425
327
 
426
- if (showToc) finalOutput += buildTableOfContents(includedFiles, cwd);
427
328
  finalOutput += combinedContent;
428
329
 
429
- if (!dryRun) fs.writeFileSync(outputFile, finalOutput, "utf8");
330
+ if (!dryRun) {
331
+ fs.writeFileSync(outputFile, finalOutput, "utf8");
332
+ }
430
333
 
431
334
  const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
335
+
432
336
  console.log(`
433
337
  โœจ repomeld complete!
434
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
338
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
435
339
  โœ… Included : ${included} files
436
340
  โญ Skipped : ${skipped} files
437
341
  ๐Ÿ“ Lines : ${totalLines}
438
342
  ๐Ÿ’พ Size : ${outputSize}
439
- ๐Ÿ“„ Output : ${options.output}${dryRun ? " (dry run โ€” not written)" : ""}
343
+ ๐Ÿ“„ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run โ€” not written)" : ""}
344
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
345
+ ๐Ÿ’ผ Need a developer? susheelhbti@gmail.com
440
346
  `);
441
- console.log(AUTHOR_NOTE + "\n");
442
347
  }
443
348
 
444
- // โ”€โ”€โ”€ CLI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
349
+ // โ”€โ”€โ”€ CLI Definition โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
445
350
 
446
351
  program
447
352
  .name("repomeld")
448
353
  .description("Meld your entire repo into a single file โ€” perfect for AI context, code reviews & sharing")
449
354
  .version(VERSION)
450
- .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
451
- .option("-e, --ext <exts...>", "Only include specific extensions")
452
- .option("--include <patterns...>", "Only include files matching patterns")
453
- .option("--exclude <patterns...>", "Exclude files matching patterns")
454
- .option("-i, --ignore <names...>", "Extra folders/files to ignore")
455
- .option("--force-include <names...>", "Force-include files ignored by default")
456
- .option("--max-size <kb>", "Skip files larger than N KB", "500")
457
- .option("-s, --style <style>", "Header style: banner | markdown | minimal","banner")
458
- .option("--no-toc", "Disable table of contents")
459
- .option("--no-meta", "Hide file metadata")
460
- .option("--trim", "Trim leading/trailing whitespace per file")
461
- .option("--lines-before <n>", "Skip first N lines of each file")
462
- .option("--lines-after <n>", "Skip last N lines of each file")
463
- .option("--dry-run", "Preview without writing output")
464
- .option("--structure-only", "Extract signatures only โ€” no body code")
465
- .option("--review-graph", "Prepend a Mermaid call graph to output")
466
- .option("--wizard", "Interactive step-by-step setup (auto when no flags given)")
467
- .action(async (options) => {
468
- printBanner();
469
-
470
- // Auto-wizard when user runs plain `repomeld` with no flags
471
- const hasFlags = options.ext || options.include || options.exclude ||
472
- options.structureOnly || options.reviewGraph ||
473
- options.style !== "banner" || options.output !== "repomeld_output.txt" ||
474
- options.dryRun;
475
-
476
- if (options.wizard || !hasFlags) {
477
- const wizardOpts = await runWizard();
478
- repomeld({ ...options, ...wizardOpts });
479
- } else {
480
- repomeld(options);
481
- }
355
+
356
+ // Output
357
+ .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
358
+
359
+ // Filtering
360
+ .option("-e, --ext <exts...>", "Only include specific extensions e.g. --ext js ts jsx")
361
+ .option("--include <patterns...>", "Only include files matching patterns e.g. --include src/")
362
+ .option("--exclude <patterns...>", "Exclude files matching patterns e.g. --exclude test spec")
363
+ .option("-i, --ignore <names...>", "Extra folders/files to ignore e.g. --ignore dist .next")
364
+ .option("--force-include <names...>", "Force-include files ignored by default e.g. --force-include vendor")
365
+ .option("--max-size <kb>", "Skip files larger than N KB (default 500)","500")
366
+
367
+ // Formatting
368
+ .option("-s, --style <style>", "Header style: banner | markdown | minimal (default: banner)", "banner")
369
+ .option("--no-toc", "Disable table of contents")
370
+ .option("--no-meta", "Hide file metadata (lines, size, lang)")
371
+ .option("--trim", "Trim leading/trailing whitespace per file")
372
+
373
+ // Advanced
374
+ .option("--lines-before <n>", "Skip first N lines of each file")
375
+ .option("--lines-after <n>", "Skip last N lines of each file")
376
+ .option("--dry-run", "Preview what would be included โ€” don't write output")
377
+
378
+ .action((options) => {
379
+ repomeld(options);
482
380
  });
483
381
 
484
- program.parse(process.argv);
382
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repomeld",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Meld your entire repo into a single file โ€” perfect for AI context, code reviews & sharing",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {