repomeld 2.0.1 โ†’ 2.0.3

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 +215 -124
  2. package/bin/cli.js +341 -178
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,6 +2,8 @@
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.**
6
+
5
7
  ---
6
8
 
7
9
  ## Install
@@ -19,88 +21,183 @@ cd your-project
19
21
  repomeld
20
22
  ```
21
23
 
22
- Creates `repomeld_output.txt` with all your source files combined, a table of contents, and file metadata โ€” with all public libraries, vendor folders, and boilerplate automatically excluded.
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
+ ```
23
85
 
24
86
  ---
25
87
 
26
- ## Smart Auto-Ignore
88
+ ## Skip the Wizard (Power Users)
89
+
90
+ If you pass any flags, repomeld skips the wizard and runs directly:
27
91
 
28
- repomeld ships with a built-in `repomeld.ignore.json` that **automatically skips** common public libraries and vendor files so your output stays small and focused on your own code.
92
+ ```bash
93
+ repomeld --ext ts tsx --style markdown --output context.md
94
+ ```
29
95
 
30
- ### What gets ignored by default
96
+ Force the wizard at any time with:
31
97
 
32
- | Category | Examples |
33
- |---|---|
34
- | Project meta | `package.json`, `README.md`, `yarn.lock`, `.gitignore` |
35
- | Build output | `dist/`, `build/`, `.next/`, `.nuxt/`, `coverage/` |
36
- | Vendor folders | `vendor/`, `libs/`, `plugins/`, `assets/vendor/`, `wwwroot/lib/` |
37
- | Bootstrap | `bootstrap.min.css`, `bootstrap.bundle.min.js`, `bootstrap-icons` |
38
- | jQuery | `jquery.min.js`, `jquery-ui.min.js`, `jquery.validate.min.js` |
39
- | Font Awesome | `all.min.css`, `fa-solid-900.woff2`, `fontawesome-free/` |
40
- | Tailwind CSS | `tailwind.min.css`, `tailwindcss/` |
41
- | Materialize | `materialize.min.css`, `materialize.min.js` |
42
- | Bulma | `bulma.min.css` |
43
- | Semantic UI | `semantic.min.css`, `semantic.min.js` |
44
- | Foundation | `foundation.min.css`, `foundation.min.js` |
45
- | UIkit | `uikit.min.css`, `uikit.min.js` |
46
- | Animate.css | `animate.min.css`, `animate.css` |
47
- | GSAP | `gsap.min.js`, `ScrollTrigger.min.js`, `TweenMax.min.js` |
48
- | Swiper / Slick / Owl | `swiper-bundle.min.js`, `slick.min.js`, `owl.carousel.min.js` |
49
- | Chart.js / D3 / Three.js | `chart.min.js`, `d3.min.js`, `three.min.js` |
50
- | Axios / Lodash / Moment | `axios.min.js`, `lodash.min.js`, `moment.min.js` |
51
- | Select2 / Flatpickr | `select2.min.js`, `flatpickr.min.js` |
52
- | DataTables | `datatables.min.js`, `jquery.dataTables.min.js` |
53
- | Toastr / SweetAlert | `toastr.min.js`, `sweetalert2.min.js` |
54
- | Lightbox / Fancybox | `lightbox.min.js`, `glightbox.min.js` |
55
- | Rich text editors | `quill.min.js`, `tinymce/`, `ckeditor/`, `codemirror/` |
56
- | Syntax highlighting | `prism.min.js`, `highlight.min.js` |
57
- | Video / Audio players | `video.js`, `plyr.min.js` |
58
- | Maps | `leaflet.min.js`, `mapbox-gl.min.js` |
59
- | Icon sets | `remixicon.css`, `boxicons.css`, `ionicons.css`, `lucide.min.js` |
60
- | Socket.IO | `socket.io.min.js` |
61
- | Misc utilities | `lazysizes.js`, `lottie.min.js`, `particles.js`, `typed.js`, `sortable.min.js`, `masonry.js` |
62
- | Admin templates | `AdminLTE/`, `metronic/`, `coreui/`, `gentelella/` |
63
- | Env / Secrets | `.env`, `.env.local`, `.env.production` |
98
+ ```bash
99
+ repomeld --wizard
100
+ ```
64
101
 
65
102
  ---
66
103
 
67
- ## Force-Include an Ignored Library
104
+ ## New Features in v1.2.0
105
+
106
+ ### `--structure-only` โ€” Skeleton mode
68
107
 
69
- If you need a normally-ignored file to appear in your output, use `--force-include`:
108
+ Extracts **class names + function signatures** from every file. Strips all body code.
70
109
 
71
110
  ```bash
72
- # Include your customized bootstrap.js even though bootstrap is ignored by default
73
- repomeld --force-include bootstrap
111
+ repomeld --structure-only
112
+ ```
74
113
 
75
- # Include multiple overrides
76
- repomeld --force-include jquery vendor bootstrap
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
+ ```
77
125
 
78
- # Combine with other options
79
- repomeld --force-include jquery --style markdown --output context.md
126
+ **After** (1 line):
127
+ ```php
128
+ public function available(Request $request) { ... }
80
129
  ```
81
130
 
82
- `--force-include` matches by name substring, so `--force-include bootstrap` will un-ignore both `bootstrap.min.css` and `bootstrap.min.js`.
131
+ Supports **PHP, JavaScript, TypeScript, Python**. Other languages fall back to the first 30 lines.
132
+
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
83
137
 
84
138
  ---
85
139
 
86
- ## Customize the Ignore List
140
+ ### `--review-graph` โ€” Mermaid call graph
87
141
 
88
- You can override or extend the ignore list by placing your own `repomeld.ignore.json` in your **project root**. It will take priority over the built-in config.
142
+ Generates a **Mermaid flowchart** at the top of your output showing which files import/call which.
89
143
 
90
- ```json
91
- {
92
- "ignore": [
93
- "my-custom-vendor-folder",
94
- "some-legacy-lib.js",
95
- "generated-report.html"
96
- ]
97
- }
144
+ ```bash
145
+ repomeld --review-graph
146
+ ```
147
+
148
+ Output (inside the file, readable by AI natively):
149
+
150
+ ````
151
+ ```mermaid
152
+ flowchart TD
153
+ %% Code Review Graph โ€” generated by repomeld
154
+
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]"]
158
+
159
+ src_index_js --> src_utils_js
160
+ src_index_js --> src_graph_js
161
+ ```
162
+ ````
163
+
164
+ AI models like Claude, GPT-4, and Gemini read Mermaid natively โ€” no image needed.
165
+
166
+ ---
167
+
168
+ ### Power combo: both together
169
+
170
+ ```bash
171
+ repomeld --structure-only --review-graph
98
172
  ```
99
173
 
100
- repomeld looks for config in this order:
101
- 1. `repomeld.ignore.json` in your **project root** (user override)
102
- 2. `repomeld.ignore.json` bundled with the npm package (built-in defaults)
103
- 3. Hardcoded `DEFAULT_IGNORE` list in the CLI
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
178
+
179
+ This is the best way to give AI a complete understanding of a large codebase without wasting context.
180
+
181
+ ---
182
+
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` |
104
201
 
105
202
  ---
106
203
 
@@ -112,6 +209,7 @@ Usage: repomeld [options]
112
209
  Options:
113
210
  -V, --version Show version
114
211
  -h, --help Show help
212
+ --wizard Interactive step-by-step setup
115
213
 
116
214
  Output:
117
215
  -o, --output <filename> Output file name (default: "repomeld_output.txt")
@@ -124,14 +222,11 @@ Options:
124
222
  --exclude <patterns...> Exclude files matching patterns
125
223
  e.g. --exclude test spec
126
224
  -i, --ignore <names...> Extra folders/files to ignore
127
- e.g. --ignore dist .next
128
225
  --force-include <names...> Force-include files ignored by default
129
- e.g. --force-include vendor bootstrap jquery
130
226
  --max-size <kb> Skip files larger than N KB (default: 500)
131
227
 
132
228
  Formatting:
133
229
  -s, --style <style> Header style: banner | markdown | minimal
134
- (default: banner)
135
230
  --no-toc Disable table of contents
136
231
  --no-meta Hide file metadata (lines, size, lang)
137
232
  --trim Trim leading/trailing whitespace per file
@@ -139,7 +234,11 @@ Options:
139
234
  Advanced:
140
235
  --lines-before <n> Skip first N lines of each file
141
236
  --lines-after <n> Skip last N lines of each file
142
- --dry-run Preview what would be included โ€” don't write output
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
143
242
  ```
144
243
 
145
244
  ---
@@ -147,99 +246,87 @@ Options:
147
246
  ## Examples
148
247
 
149
248
  ```bash
150
- # Basic usage โ€” outputs source files only, libraries auto-ignored
249
+ # Wizard mode (auto when no flags given)
151
250
  repomeld
152
251
 
153
- # Only TypeScript and JSX files inside src/
154
- repomeld --ext ts tsx --include src/
155
-
156
- # Exclude test files
157
- repomeld --exclude test spec __tests__
158
-
159
- # Force-include your customized jQuery even though it is ignored by default
160
- repomeld --force-include jquery
161
-
162
- # Force-include multiple libs
163
- repomeld --force-include vendor bootstrap select2
252
+ # Force wizard even with flags
253
+ repomeld --wizard
164
254
 
165
- # Markdown style output โ€” great for pasting into AI prompts
166
- repomeld --style markdown --output context.md
255
+ # Only PHP controllers, structure only
256
+ repomeld --ext php --include Controllers --structure-only
167
257
 
168
- # Minimal headers, no table of contents
169
- repomeld --style minimal --no-toc
258
+ # Full code + call graph
259
+ repomeld --review-graph
170
260
 
171
- # Custom output file
172
- repomeld --output all_code.txt
261
+ # Best for AI: skeleton + graph, markdown style
262
+ repomeld --structure-only --review-graph --style markdown --output ai_context.md
173
263
 
174
- # Skip large files (over 100 KB)
175
- repomeld --max-size 100
264
+ # Only TypeScript inside src/
265
+ repomeld --ext ts tsx --include src/
176
266
 
177
- # No metadata shown
178
- repomeld --no-toc --no-meta
267
+ # Exclude tests
268
+ repomeld --exclude test spec __tests__
179
269
 
180
- # Dry run โ€” preview what would be included without writing
270
+ # Dry run โ€” preview only
181
271
  repomeld --dry-run
182
272
 
183
- # Ignore additional folders not in the default list
184
- repomeld --ignore dist .next coverage
185
-
186
- # Combine options
187
- repomeld --ext ts tsx --include src/ --style markdown --output ai_context.md
273
+ # Large repo: structure only, skip files over 100 KB
274
+ repomeld --structure-only --max-size 100
188
275
  ```
189
276
 
190
277
  ---
191
278
 
192
- ## Output Format (banner style โ€” default)
279
+ ## Output Format
280
+
281
+ ### With `--review-graph`
193
282
 
194
283
  ```
195
- # Generated by repomeld v1.1.0
284
+ # Generated by repomeld v1.2.0
196
285
  # Date : 2024-01-01T00:00:00.000Z
197
286
  # Source : /your/project
198
- # Files : 12
199
- # Lines : 843
287
+ # Files : 8
288
+ # Lines : 312
289
+ # Graph : review-graph enabled
290
+ # Author : susheelhbti@gmail.com (open to work)
200
291
 
201
- TABLE OF CONTENTS
202
292
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
203
- 1. src/index.ts
204
- 2. src/utils/helper.ts
205
- ...
293
+ CODE REVIEW GRAPH
206
294
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
207
295
 
208
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
209
- FILE: src/index.ts [42 lines | 1.2 KB | typescript]
210
- โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
211
-
212
- <file contents>
296
+ ```mermaid
297
+ flowchart TD
298
+ ...
213
299
  ```
214
300
 
215
- ---
216
-
217
- ## Output Format (markdown style)
218
-
219
- ````
220
- ## ๐Ÿ“„ src/index.ts [42 lines | 1.2 KB | typescript]
221
-
222
- ```typescript
223
- <file contents>
301
+ TABLE OF CONTENTS
302
+ ...
224
303
  ```
225
- ````
226
304
 
227
- ---
305
+ ### With `--structure-only` (PHP example)
228
306
 
229
- ## Auto-ignored (always skipped regardless of config)
307
+ ```php
308
+ namespace App\Http\Controllers;
309
+ use App\Models\SecurityGuard;
310
+ use App\Models\SecurityRequest;
230
311
 
231
- - `node_modules/`, `.git/`, `dist/`, `build/`, `.next/`, `.nuxt/`, `.cache/`
232
- - `.env`, `.env.local`, `.env.production`
233
- - `.DS_Store`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
234
- - Binary files (images, executables, fonts, etc.)
235
- - The output file itself
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
+ ```
236
323
 
237
324
  ---
238
325
 
239
326
  ## Publish / Update
240
327
 
241
328
  ```bash
242
- npm version patch # 1.0.0 โ†’ 1.0.1
329
+ npm version patch # 1.2.0 โ†’ 1.2.1
243
330
  npm publish
244
331
  ```
245
332
 
@@ -247,10 +334,14 @@ npm publish
247
334
 
248
335
  ## Contributing
249
336
 
250
- Found a popular library that should be in the ignore list? Open a PR and add it to `repomeld.ignore.json`!
337
+ Found a popular library that should be in the ignore list? Open a PR!
251
338
 
252
339
  ---
253
340
 
254
341
  ## License
255
342
 
256
- MIT
343
+ MIT
344
+
345
+ ---
346
+
347
+ > ๐Ÿ’ผ **Looking to hire?** The author [`susheelhbti@gmail.com`](mailto:susheelhbti@gmail.com) is available for freelance and full-time work.
package/bin/cli.js CHANGED
@@ -2,28 +2,18 @@
2
2
 
3
3
  const fs = require("fs");
4
4
  const path = require("path");
5
+ const readline = require("readline");
5
6
  const { program } = require("commander");
6
7
 
7
- const VERSION = "1.0.0";
8
+ const VERSION = "1.2.0";
9
+
10
+ const AUTHOR_NOTE = " ๐Ÿ’ผ Author susheelhbti@gmail.com is open to freelance & full-time work";
8
11
 
9
12
  const DEFAULT_IGNORE = [
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",
26
- "repomeld_output.txt",
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",
27
17
  ];
28
18
 
29
19
  function loadIgnoreConfig() {
@@ -33,9 +23,7 @@ function loadIgnoreConfig() {
33
23
  if (fs.existsSync(loc)) {
34
24
  try {
35
25
  const data = JSON.parse(fs.readFileSync(loc, "utf8"));
36
- if (Array.isArray(data.ignore)) {
37
- return data.ignore;
38
- }
26
+ if (Array.isArray(data.ignore)) return data.ignore;
39
27
  } catch {
40
28
  console.warn(` โš ๏ธ Could not parse ${loc}, using defaults.`);
41
29
  }
@@ -62,31 +50,17 @@ function getLanguage(filePath) {
62
50
 
63
51
  function getAllFiles(dirPath, ignoreList, fileList = []) {
64
52
  let entries;
65
- try {
66
- entries = fs.readdirSync(dirPath, { withFileTypes: true });
67
- } catch {
68
- return fileList;
69
- }
70
-
53
+ try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); }
54
+ catch { return fileList; }
71
55
  for (const entry of entries) {
72
56
  const fullPath = path.join(dirPath, entry.name);
73
57
  const relativePath = path.relative(process.cwd(), fullPath);
74
-
75
- if (
76
- ignoreList.some(
77
- (ig) => entry.name === ig || relativePath.startsWith(ig + path.sep) || relativePath === ig
78
- )
79
- ) {
80
- continue;
81
- }
82
-
83
- if (entry.isDirectory()) {
84
- getAllFiles(fullPath, ignoreList, fileList);
85
- } else if (entry.isFile()) {
86
- fileList.push(fullPath);
87
- }
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);
88
63
  }
89
-
90
64
  return fileList;
91
65
  }
92
66
 
@@ -96,13 +70,9 @@ function isBinaryFile(filePath) {
96
70
  const fd = fs.openSync(filePath, "r");
97
71
  const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
98
72
  fs.closeSync(fd);
99
- for (let i = 0; i < bytesRead; i++) {
100
- if (buffer[i] === 0) return true;
101
- }
73
+ for (let i = 0; i < bytesRead; i++) if (buffer[i] === 0) return true;
102
74
  return false;
103
- } catch {
104
- return true;
105
- }
75
+ } catch { return true; }
106
76
  }
107
77
 
108
78
  function matchesExtensions(filePath, exts) {
@@ -123,147 +93,329 @@ function formatSize(bytes) {
123
93
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
124
94
  }
125
95
 
96
+ // โ”€โ”€โ”€ AUTO-INCREMENT OUTPUT FILENAME โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
97
+
98
+ function resolveOutputPath(cwd, filename) {
99
+ const ext = path.extname(filename);
100
+ const base = path.basename(filename, ext);
101
+ let candidate = path.resolve(cwd, filename);
102
+ let counter = 1;
103
+ while (fs.existsSync(candidate)) {
104
+ candidate = path.resolve(cwd, `${base}_${counter}${ext}`);
105
+ counter++;
106
+ }
107
+ return candidate;
108
+ }
109
+
110
+ // โ”€โ”€โ”€ BANNER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
111
+
126
112
  function printBanner() {
127
113
  console.log(`
128
- โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
129
- โ•‘ repomeld v${VERSION} โ•‘
130
- โ•‘ Meld your repo into one file โ•‘
131
- โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•`);
132
-
133
- console.log("Developer is looking for freelance job susheelhbti@gmail.com");
114
+ โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
115
+ โ•‘ repomeld v${VERSION} โ•‘
116
+ โ•‘ Meld your repo into one file ๐Ÿ”ฅ โ•‘
117
+ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•`);
118
+ console.log(AUTHOR_NOTE);
119
+ console.log();
134
120
  }
135
121
 
136
- function buildHeader(style, relativePath, filePath, lineCount, showMeta) {
122
+ // โ”€โ”€โ”€ HEADER / FOOTER โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
123
+
124
+ function buildHeader(relativePath, filePath, lineCount, showMeta) {
137
125
  const lang = getLanguage(filePath);
138
126
  const size = formatSize(fs.statSync(filePath).size);
139
127
  const meta = showMeta ? ` [${lineCount} lines | ${size}${lang ? " | " + lang : ""}]` : "";
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
148
128
  const divider = "โ”€".repeat(60);
149
129
  return `\n${divider}\n FILE: ${relativePath}${meta}\n${divider}\n\n`;
150
130
  }
151
131
 
152
- function buildFooter(style) {
153
- if (style === "markdown") return "\n```\n";
132
+ function buildFooter() {
154
133
  return "\n";
155
134
  }
156
135
 
136
+ // โ”€โ”€โ”€ TABLE OF CONTENTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
137
+
157
138
  function buildTableOfContents(files, cwd) {
158
139
  let toc = "TABLE OF CONTENTS\n" + "โ•".repeat(60) + "\n";
159
140
  files.forEach((f, i) => {
160
- const rel = path.relative(cwd, f);
161
- toc += ` ${String(i + 1).padStart(3, " ")}. ${rel}\n`;
141
+ toc += ` ${String(i + 1).padStart(3, " ")}. ${path.relative(cwd, f)}\n`;
162
142
  });
163
143
  toc += "โ•".repeat(60) + "\n\n";
164
144
  return toc;
165
145
  }
166
146
 
167
- function repomeld(options) {
168
- printBanner();
147
+ // โ”€โ”€โ”€ FILE TREE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
148
+
149
+ function buildFileTree(files, cwd) {
150
+ const tree = {};
151
+ for (const f of files) {
152
+ const parts = path.relative(cwd, f).split(path.sep);
153
+ let node = tree;
154
+ for (const part of parts) {
155
+ node[part] = node[part] || {};
156
+ node = node[part];
157
+ }
158
+ }
159
+
160
+ function render(node, prefix = "") {
161
+ const keys = Object.keys(node);
162
+ return keys.map((key, i) => {
163
+ const isLast = i === keys.length - 1;
164
+ const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ ";
165
+ const childPfx = isLast ? " " : "โ”‚ ";
166
+ const children = Object.keys(node[key]).length
167
+ ? "\n" + render(node[key], prefix + childPfx)
168
+ : "";
169
+ return prefix + connector + key + children;
170
+ }).join("\n");
171
+ }
172
+
173
+ return "FILE TREE\n" + "โ•".repeat(60) + "\n"
174
+ + render(tree) + "\n"
175
+ + "โ•".repeat(60) + "\n\n";
176
+ }
177
+
178
+ // โ”€โ”€โ”€ STRUCTURE EXTRACTION โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
179
+
180
+ function extractStructure(content, filePath) {
181
+ const lang = getLanguage(filePath);
182
+ const lines = content.split("\n");
183
+ const output = [];
184
+
185
+ if (lang === "php") {
186
+ let insideClass = false;
187
+ let braceDepth = 0;
188
+ let classStartDepth = 0;
189
+ for (const line of lines) {
190
+ const trimmed = line.trim();
191
+ if (trimmed.startsWith("namespace ") || trimmed.startsWith("use ")) { output.push(line); continue; }
192
+ const classMatch = trimmed.match(/^(abstract\s+class|class|interface|trait)\s+(\w+)(\s+extends\s+\w+)?(\s+implements\s+[\w,\s]+)?/);
193
+ if (classMatch) {
194
+ if (output.length && output[output.length - 1] !== "") output.push("");
195
+ output.push(line); insideClass = true; classStartDepth = braceDepth;
196
+ }
197
+ braceDepth += (line.match(/\{/g) || []).length;
198
+ braceDepth -= (line.match(/\}/g) || []).length;
199
+ if (insideClass && braceDepth <= classStartDepth && trimmed === "}") {
200
+ output.push("}"); insideClass = false; output.push(""); continue;
201
+ }
202
+ if (trimmed.startsWith("/**") || trimmed.startsWith("*") || trimmed.startsWith("*/")) {
203
+ if (insideClass) output.push(line); continue;
204
+ }
205
+ if (insideClass) {
206
+ const methodMatch = trimmed.match(/^(public|protected|private|static|abstract|final|\s)*(function\s+\w+\s*\([^)]*\)(\s*:\s*[\w\?\|\\]+)?)/);
207
+ if (methodMatch) output.push(` ${methodMatch[0].trim()} { ... }`);
208
+ if (trimmed.match(/^(public|protected|private|const)\s+/) && trimmed.endsWith(";") && !trimmed.includes("function"))
209
+ output.push(` ${trimmed}`);
210
+ }
211
+ }
212
+ return output.join("\n");
213
+ }
214
+
215
+ if (["javascript", "typescript"].includes(lang)) {
216
+ for (const line of lines) {
217
+ const trimmed = line.trim();
218
+ if (trimmed.match(/^(export\s+)?(abstract\s+)?class\s+\w+/)) { output.push(line); continue; }
219
+ if (trimmed.match(/^(export\s+)?(async\s+)?function\s+\w+\s*\(/)) {
220
+ output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
221
+ }
222
+ if (trimmed.match(/^(export\s+)?(const|let|var)\s+\w+\s*=\s*(async\s+)?\(/)) {
223
+ output.push(`${line.match(/^\s*/)[0]}${trimmed.split("=>")[0].trim()} => { ... }`); continue;
224
+ }
225
+ if (trimmed.match(/^(async\s+)?(\w+)\s*\([^)]*\)\s*(\:\s*[\w<>\[\]]+)?\s*\{/) &&
226
+ !["if", "while", "for", "switch"].some(k => trimmed.startsWith(k))) {
227
+ output.push(`${line.match(/^\s*/)[0]}${trimmed.replace(/\{[\s\S]*$/, "{ ... }")}`); continue;
228
+ }
229
+ if (trimmed.startsWith("import ")) output.push(line);
230
+ if (trimmed === "}") output.push(line);
231
+ }
232
+ return output.join("\n");
233
+ }
234
+
235
+ if (lang === "python") {
236
+ for (const line of lines) {
237
+ const t = line.trim();
238
+ if (t.startsWith("class ") || t.startsWith("def ") || t.startsWith("async def "))
239
+ output.push(line.replace(/:[\s\S]*$/, ":"));
240
+ if (t.startsWith("import ") || t.startsWith("from ")) output.push(line);
241
+ }
242
+ return output.join("\n");
243
+ }
244
+
245
+ return lines.slice(0, 30).join("\n") + "\n... (structure extraction not supported for this language)";
246
+ }
247
+
248
+ // โ”€โ”€โ”€ REVIEW GRAPH โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
249
+
250
+ function buildReviewGraph(files, cwd) {
251
+ const fileFunctions = {};
252
+ const fileImports = {};
253
+
254
+ for (const filePath of files.slice(0, 25)) {
255
+ const lang = getLanguage(filePath);
256
+ if (!["javascript", "typescript", "php", "python"].includes(lang)) continue;
257
+ let content;
258
+ try { content = fs.readFileSync(filePath, "utf8"); } catch { continue; }
259
+ const rel = path.relative(cwd, filePath);
260
+ const nodeId = rel.replace(/[^a-zA-Z0-9]/g, "_");
261
+ fileFunctions[rel] = { nodeId, functions: [] };
262
+ fileImports[rel] = [];
263
+ for (const line of content.split("\n")) {
264
+ const t = line.trim();
265
+ const fnMatch = t.match(/^(export\s+)?(async\s+)?function\s+(\w+)/) ||
266
+ t.match(/^(public|protected|private|static)?\s*(function\s+(\w+))/) ||
267
+ t.match(/^def\s+(\w+)/);
268
+ if (fnMatch) {
269
+ const name = fnMatch[3] || fnMatch[2] || fnMatch[1];
270
+ if (name && name.length > 2 && !["if", "for", "while", "switch"].includes(name))
271
+ fileFunctions[rel].functions.push(name);
272
+ }
273
+ const imp = t.match(/require\(['"]([^'"]+)['"]\)/) ||
274
+ t.match(/^import\s+.*from\s+['"]([^'"]+)['"]/) ||
275
+ t.match(/^use\s+(App\\[^;]+)/);
276
+ if (imp) fileImports[rel].push(imp[1]);
277
+ }
278
+ fileFunctions[rel].functions = [...new Set(fileFunctions[rel].functions)].slice(0, 5);
279
+ }
280
+
281
+ const fileList = Object.keys(fileFunctions);
282
+ if (fileList.length === 0) return null;
283
+
284
+ let mermaid = "```mermaid\nflowchart TD\n %% Code Review Graph โ€” generated by repomeld\n\n";
285
+ for (const rel of fileList) {
286
+ const { nodeId, functions } = fileFunctions[rel];
287
+ const label = path.basename(rel);
288
+ const fnList = functions.length ? `\\n[${functions.join(", ")}]` : "";
289
+ mermaid += ` ${nodeId}["๐Ÿ“„ ${label}${fnList}"]\n`;
290
+ }
291
+ mermaid += "\n";
292
+ let edgeCount = 0;
293
+ for (const rel of fileList) {
294
+ for (const imp of fileImports[rel]) {
295
+ const matched = fileList.find(f => {
296
+ const base = path.basename(f, path.extname(f));
297
+ return imp.endsWith(base) || imp.includes(base);
298
+ });
299
+ if (matched && matched !== rel && edgeCount < 40) {
300
+ mermaid += ` ${fileFunctions[rel].nodeId} --> ${fileFunctions[matched].nodeId}\n`;
301
+ edgeCount++;
302
+ }
303
+ }
304
+ }
305
+ return mermaid + "```\n";
306
+ }
307
+
308
+ // โ”€โ”€โ”€ INTERACTIVE WIZARD โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
169
309
 
310
+ function ask(rl, question) {
311
+ return new Promise((resolve) => rl.question(question, resolve));
312
+ }
313
+
314
+ async function runWizard() {
315
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
316
+
317
+ console.log(" โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
318
+ console.log(" โ”‚ Interactive setup โ€” answer 1 quick question โ”‚");
319
+ console.log(" โ”‚ Press Enter to accept the [default] value โ”‚");
320
+ console.log(" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n");
321
+
322
+ const opts = {};
323
+
324
+ // 1 โ€” Extensions
325
+ console.log(" ๐Ÿ” 1/1 Filter to specific extensions?");
326
+ console.log(" e.g. js ts py (leave blank = all files)");
327
+ const extRaw = await ask(rl, " > ");
328
+ opts.ext = extRaw.trim() ? extRaw.trim().split(/\s+/) : [];
329
+
330
+ // hardcoded defaults โ€” not optional
331
+ opts.output = "repomeld_output.txt";
332
+ opts.style = "banner";
333
+ opts.reviewGraph = true;
334
+ opts.noToc = false;
335
+ opts.dryRun = false;
336
+ opts.structureOnly = false;
337
+
338
+ rl.close();
339
+
340
+ // Summary
341
+ console.log("\n โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”");
342
+ console.log(" โ”‚ Your configuration โ”‚");
343
+ console.log(" โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค");
344
+ console.log(` โ”‚ Extensions : ${(opts.ext.length ? opts.ext.join(", ") : "all").padEnd(33)}โ”‚`);
345
+ console.log(` โ”‚ Style : banner (always) โ”‚`);
346
+ console.log(` โ”‚ Graph : yes (always) โ”‚`);
347
+ console.log(` โ”‚ TOC : yes (always) โ”‚`);
348
+ console.log(" โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n");
349
+
350
+ return opts;
351
+ }
352
+
353
+ // โ”€โ”€โ”€ CORE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
354
+
355
+ function repomeld(options) {
170
356
  const cwd = process.cwd();
171
- const outputFile = path.resolve(cwd, options.output);
357
+
358
+ // Always banner, always review graph, always TOC
359
+ options.style = "banner";
360
+ options.reviewGraph = true;
361
+ options.noToc = false;
362
+
363
+ const outputFile = resolveOutputPath(cwd, options.output || "repomeld_output.txt");
172
364
  const forceInclude = options.forceInclude || [];
173
365
  const rawIgnore = [...DEFAULT_IGNORE, ...IGNORE_FROM_CONFIG, ...(options.ignore || [])];
174
366
  const ignoreList = forceInclude.length
175
- ? rawIgnore.filter((ig) => !forceInclude.some((fi) => ig.includes(fi) || fi.includes(ig)))
367
+ ? rawIgnore.filter(ig => !forceInclude.some(fi => ig.includes(fi) || fi.includes(ig)))
176
368
  : rawIgnore;
177
- const filterExts = options.ext || [];
369
+ const filterExts = options.ext || [];
178
370
  const maxFileSizeBytes = (parseFloat(options.maxSize) || 500) * 1024;
179
- const headerStyle = options.style || "banner";
180
- const showMeta = !options.noMeta;
181
- const showToc = !options.noToc;
182
- const dryRun = options.dryRun || false;
183
- const include = options.include || [];
184
- const exclude = options.exclude || [];
185
- const linesBefore = parseInt(options.linesBefore) || 0;
186
- const linesAfter = parseInt(options.linesAfter) || 0;
187
-
188
- console.log(`\n ๐Ÿ“‚ Source : ${cwd}`);
371
+ const showMeta = options.noMeta !== true;
372
+ const dryRun = options.dryRun || false;
373
+ const include = options.include || [];
374
+ const exclude = options.exclude || [];
375
+ const linesBefore = parseInt(options.linesBefore) || 0;
376
+ const linesAfter = parseInt(options.linesAfter) || 0;
377
+ const structureOnly = options.structureOnly || false;
378
+
379
+ console.log(` ๐Ÿ“‚ Source : ${cwd}`);
189
380
  console.log(` ๐Ÿ“„ Output : ${path.relative(cwd, outputFile)}`);
190
- console.log(` ๐ŸŽจ Style : ${headerStyle}`);
381
+ console.log(` ๐ŸŽจ Style : banner`);
191
382
  if (filterExts.length) console.log(` ๐Ÿ” Filter : .${filterExts.join(", .")}`);
192
- if (forceInclude.length) console.log(` ๐Ÿ“Œ Force : ${forceInclude.join(", ")}`);
193
- if (dryRun) console.log(` ๐Ÿงช Dry run : no file will be written`);
383
+ if (structureOnly) console.log(` ๐Ÿ—๏ธ Mode : structure only`);
384
+ console.log(` ๐Ÿ”— Graph : review graph enabled`);
385
+ if (dryRun) console.log(` ๐Ÿงช Dry run : no file will be written`);
194
386
  console.log();
195
387
 
196
388
  let allFiles = getAllFiles(cwd, ignoreList);
389
+ if (filterExts.length) allFiles = allFiles.filter(f => matchesExtensions(f, filterExts));
390
+ if (include.length) allFiles = allFiles.filter(f => matchesPattern(f, include));
391
+ if (exclude.length) allFiles = allFiles.filter(f => !matchesPattern(f, exclude));
392
+ allFiles = allFiles.filter(f => path.resolve(f) !== outputFile);
197
393
 
198
- // Filter by extension
199
- if (filterExts.length) {
200
- allFiles = allFiles.filter((f) => matchesExtensions(f, filterExts));
201
- }
202
-
203
- // Include pattern filter
204
- if (include.length) {
205
- allFiles = allFiles.filter((f) => matchesPattern(f, include));
206
- }
207
-
208
- // Exclude pattern filter
209
- if (exclude.length) {
210
- allFiles = allFiles.filter((f) => !matchesPattern(f, exclude));
211
- }
212
-
213
- // Remove output file from list
214
- allFiles = allFiles.filter((f) => path.resolve(f) !== outputFile);
215
-
216
- if (allFiles.length === 0) {
217
- console.log(" โš ๏ธ No matching files found.\n");
218
- return;
219
- }
394
+ if (allFiles.length === 0) { console.log(" โš ๏ธ No matching files found.\n"); return; }
220
395
 
221
396
  let combinedContent = "";
222
- let skipped = 0;
223
- let included = 0;
224
- let totalLines = 0;
397
+ let skipped = 0, included = 0, totalLines = 0;
225
398
  const includedFiles = [];
226
399
 
227
400
  for (const filePath of allFiles) {
228
401
  const relativePath = path.relative(cwd, filePath);
229
-
230
- if (isBinaryFile(filePath)) {
231
- console.log(` โญ Binary : ${relativePath}`);
232
- skipped++;
233
- continue;
234
- }
235
-
402
+ if (isBinaryFile(filePath)) { console.log(` โญ Binary : ${relativePath}`); skipped++; continue; }
236
403
  const stat = fs.statSync(filePath);
237
- if (stat.size > maxFileSizeBytes) {
238
- console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`);
239
- skipped++;
240
- continue;
241
- }
242
-
404
+ if (stat.size > maxFileSizeBytes) { console.log(` โญ Too large: ${relativePath} (${formatSize(stat.size)})`); skipped++; continue; }
243
405
  try {
244
406
  let content = fs.readFileSync(filePath, "utf8");
245
-
246
- // Trim leading/trailing blank lines if requested
247
- if (options.trim) {
248
- content = content.trim();
249
- }
250
-
251
- // Slice specific lines
407
+ if (options.trim) content = content.trim();
252
408
  if (linesBefore > 0 || linesAfter > 0) {
253
409
  const lines = content.split("\n");
254
- const start = linesBefore;
255
- const end = linesAfter > 0 ? lines.length - linesAfter : lines.length;
256
- content = lines.slice(start, end).join("\n");
410
+ content = lines.slice(linesBefore, linesAfter > 0 ? lines.length - linesAfter : lines.length).join("\n");
257
411
  }
258
-
412
+ if (structureOnly) content = extractStructure(content, filePath);
259
413
  const lineCount = content.split("\n").length;
260
414
  totalLines += lineCount;
261
415
  includedFiles.push(filePath);
262
-
263
- combinedContent += buildHeader(headerStyle, relativePath, filePath, lineCount, showMeta);
416
+ combinedContent += buildHeader(relativePath, filePath, lineCount, showMeta);
264
417
  combinedContent += content;
265
- combinedContent += buildFooter(headerStyle);
266
-
418
+ combinedContent += buildFooter();
267
419
  console.log(` โœ… ${relativePath}`);
268
420
  included++;
269
421
  } catch (err) {
@@ -272,29 +424,36 @@ function repomeld(options) {
272
424
  }
273
425
  }
274
426
 
275
- // Build final output
427
+ // Build final output: metadata โ†’ review graph โ†’ file tree โ†’ TOC โ†’ file contents
276
428
  let finalOutput = "";
277
-
278
- // Top-level comment
279
- const timestamp = new Date().toISOString();
280
429
  finalOutput += `# Generated by repomeld v${VERSION}\n`;
281
- finalOutput += `# Date : ${timestamp}\n`;
430
+ finalOutput += `# Date : ${new Date().toISOString()}\n`;
282
431
  finalOutput += `# Source : ${cwd}\n`;
283
432
  finalOutput += `# Files : ${included}\n`;
284
- finalOutput += `# Lines : ${totalLines}\n\n`;
285
-
286
- if (showToc) {
287
- finalOutput += buildTableOfContents(includedFiles, cwd);
433
+ finalOutput += `# Lines : ${totalLines}\n`;
434
+ if (structureOnly) finalOutput += `# Mode : structure-only (signatures)\n`;
435
+ finalOutput += `# Graph : review-graph enabled\n`;
436
+ finalOutput += `# Author : susheelhbti@gmail.com (open to work)\n\n`;
437
+
438
+ // Review graph (always)
439
+ const graph = buildReviewGraph(includedFiles, cwd);
440
+ if (graph) {
441
+ finalOutput += "โ•".repeat(60) + "\n CODE REVIEW GRAPH\n" + "โ•".repeat(60) + "\n\n";
442
+ finalOutput += graph + "\n";
288
443
  }
289
444
 
445
+ // File tree (always)
446
+ finalOutput += buildFileTree(includedFiles, cwd);
447
+
448
+ // Table of contents (always)
449
+ finalOutput += buildTableOfContents(includedFiles, cwd);
450
+
451
+ // File contents
290
452
  finalOutput += combinedContent;
291
453
 
292
- if (!dryRun) {
293
- fs.writeFileSync(outputFile, finalOutput, "utf8");
294
- }
454
+ if (!dryRun) fs.writeFileSync(outputFile, finalOutput, "utf8");
295
455
 
296
456
  const outputSize = formatSize(Buffer.byteLength(finalOutput, "utf8"));
297
-
298
457
  console.log(`
299
458
  โœจ repomeld complete!
300
459
  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
@@ -302,41 +461,45 @@ function repomeld(options) {
302
461
  โญ Skipped : ${skipped} files
303
462
  ๐Ÿ“ Lines : ${totalLines}
304
463
  ๐Ÿ’พ Size : ${outputSize}
305
- ๐Ÿ“„ Output : ${options.output}${dryRun ? " (dry run โ€” not written)" : ""}
464
+ ๐Ÿ“„ Output : ${path.relative(cwd, outputFile)}${dryRun ? " (dry run โ€” not written)" : ""}
306
465
  `);
466
+ console.log(AUTHOR_NOTE + "\n");
307
467
  }
308
468
 
309
- // โ”€โ”€โ”€ CLI Definition โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
469
+ // โ”€โ”€โ”€ CLI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
310
470
 
311
471
  program
312
472
  .name("repomeld")
313
473
  .description("Meld your entire repo into a single file โ€” perfect for AI context, code reviews & sharing")
314
474
  .version(VERSION)
315
-
316
- // Output
317
- .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
318
-
319
- // Filtering
320
- .option("-e, --ext <exts...>", "Only include specific extensions e.g. --ext js ts jsx")
321
- .option("--include <patterns...>", "Only include files matching patterns e.g. --include src/")
322
- .option("--exclude <patterns...>", "Exclude files matching patterns e.g. --exclude test spec")
323
- .option("-i, --ignore <names...>", "Extra folders/files to ignore e.g. --ignore dist .next")
324
- .option("--force-include <names...>", "Force-include files ignored by default e.g. --force-include vendor bootstrap jquery")
325
- .option("--max-size <kb>", "Skip files larger than N KB (default 500)","500")
326
-
327
- // Formatting
328
- .option("-s, --style <style>", "Header style: banner | markdown | minimal (default: banner)", "banner")
329
- .option("--no-toc", "Disable table of contents")
330
- .option("--no-meta", "Hide file metadata (lines, size, lang)")
331
- .option("--trim", "Trim leading/trailing whitespace per file")
332
-
333
- // Advanced
334
- .option("--lines-before <n>", "Skip first N lines of each file")
335
- .option("--lines-after <n>", "Skip last N lines of each file")
336
- .option("--dry-run", "Preview what would be included โ€” don't write output")
337
-
338
- .action((options) => {
339
- repomeld(options);
475
+ .option("-o, --output <filename>", "Output file name", "repomeld_output.txt")
476
+ .option("-e, --ext <exts...>", "Only include specific extensions")
477
+ .option("--include <patterns...>", "Only include files matching patterns")
478
+ .option("--exclude <patterns...>", "Exclude files matching patterns")
479
+ .option("-i, --ignore <names...>", "Extra folders/files to ignore")
480
+ .option("--force-include <names...>", "Force-include files ignored by default")
481
+ .option("--max-size <kb>", "Skip files larger than N KB", "500")
482
+ .option("--no-meta", "Hide file metadata")
483
+ .option("--trim", "Trim leading/trailing whitespace per file")
484
+ .option("--lines-before <n>", "Skip first N lines of each file")
485
+ .option("--lines-after <n>", "Skip last N lines of each file")
486
+ .option("--dry-run", "Preview without writing output")
487
+ .option("--structure-only", "Extract signatures only โ€” no body code")
488
+ .option("--wizard", "Interactive step-by-step setup (auto when no flags given)")
489
+ .action(async (options) => {
490
+ printBanner();
491
+
492
+ // Auto-wizard when user runs plain `repomeld` with no flags
493
+ const hasFlags = options.ext || options.include || options.exclude ||
494
+ options.structureOnly || options.output !== "repomeld_output.txt" ||
495
+ options.dryRun;
496
+
497
+ if (options.wizard || !hasFlags) {
498
+ const wizardOpts = await runWizard();
499
+ repomeld({ ...options, ...wizardOpts });
500
+ } else {
501
+ repomeld(options);
502
+ }
340
503
  });
341
504
 
342
505
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repomeld",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
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": {