@@ -126,6 +126,35 @@ def is_path_ignored(rel_path: Path, rules: List[Tuple[str, bool]]) -> bool:
126126
127127 return ignored
128128
129+ # --- Project Tree Generator ---
130+
131+ def generate_project_tree (file_paths : List [str ], root_name : str ) -> str :
132+ """Generates a string representation of the project tree from a list of file paths."""
133+ tree_dict : Dict = {}
134+ for path in sorted (file_paths ):
135+ parts = Path (path ).parts
136+ current_level = tree_dict
137+ for part in parts :
138+ if part not in current_level :
139+ current_level [part ] = {}
140+ current_level = current_level [part ]
141+
142+ lines = [f"{ root_name } /" ]
143+
144+ def _generate_lines_recursive (subtree : Dict , prefix : str ):
145+ entries = sorted (subtree .items ())
146+ for i , (name , content ) in enumerate (entries ):
147+ is_last = (i == len (entries ) - 1 )
148+ connector = "└── " if is_last else "├── "
149+ lines .append (f"{ prefix } { connector } { name } " )
150+
151+ if content : # It's a directory, recurse
152+ new_prefix = prefix + (" " if is_last else "│ " )
153+ _generate_lines_recursive (content , new_prefix )
154+
155+ _generate_lines_recursive (tree_dict , "" )
156+ return "\n " .join (lines ) + "\n "
157+
129158# --- Token Estimator ---
130159
131160# Initialize a global tokenizer to avoid reloading
@@ -214,10 +243,6 @@ def main():
214243 files_to_merge : List [Tuple [Path , str , int , str ]] = []
215244 total_files_scanned = 0
216245
217- print (f"\n --- Phase 1: Scanning & Estimating Tokens ---" )
218- print (f"Using rules from: { mergeignore_file .name } " )
219- print (f"Including extensions: { args .extensions } " )
220-
221246 for path in root_dir .rglob ("*" ):
222247 total_files_scanned += 1
223248
@@ -238,7 +263,6 @@ def main():
238263 try :
239264 content = path .read_text (encoding = "utf-8" )
240265 tokens = estimate_tokens (content )
241- print (f" > [Found] { rel_path .as_posix ()} (~{ tokens } tokens)" )
242266 files_to_merge .append ((path , rel_path .as_posix (), tokens , content ))
243267 except Exception as e :
244268 print (f" > [Warning] Skipping { rel_path .as_posix ()} (read error: { e } )" , file = sys .stderr )
@@ -247,7 +271,7 @@ def main():
247271 print (f"\n Scan complete. No matching files found to merge (scanned { total_files_scanned } items)." )
248272 return
249273
250- # 4. Phase 1.5: Top 10 Review
274+ # 4. Phase 1.5: Top 10 Review and Tree Generation
251275 files_to_merge .sort (key = lambda x : x [2 ], reverse = True )
252276 total_tokens = sum (f [2 ] for f in files_to_merge )
253277
@@ -261,43 +285,29 @@ def main():
261285 print (f"Total files to merge: { len (files_to_merge )} " )
262286 print (f"Total estimated tokens: { total_tokens } " )
263287 print ("-" * 70 )
264-
265- # 5. Phase 2: Confirm and Merge
266- proceed = False
267- if args .yes :
268- print ("Auto-confirming with --yes flag." )
269- proceed = True
270- else :
271- try :
272- choice = input (f"> Proceed with merging { len (files_to_merge )} files? (Y/n): " ).strip ().lower ()
273- if choice != 'n' :
274- proceed = True
275- except KeyboardInterrupt :
276- print ("\n Operation cancelled." , file = sys .stderr )
277- sys .exit (0 )
278-
279- if proceed :
280- print (f"\n --- Phase 2: Merging files into { output_file .name } ---" )
281- try :
282- with open (output_file , "w" , encoding = "utf-8" ) as f :
283- f .write (f"# --- flatcode: Project Context Snapshot --- #\n " )
284- f .write (f"# Root: { root_dir } \n " )
285- f .write (f"# Files: { len (files_to_merge )} \n " )
286- f .write (f"# Est. Tokens: { total_tokens } \n " )
287- f .write (f"# --- Start of Context --- #\n \n " )
288-
289- for path , rel_path , tokens , content in files_to_merge :
290- print (f" > Merging: { rel_path } " )
291- f .write (f"--- File: { rel_path } ---\n \n " )
292- f .write (content )
293- f .write (f"\n \n --- End: { rel_path } ---\n \n " )
294-
295- print (f"\n --- Success! ---" )
296- print (f"Project context successfully merged into: { output_file } " )
297- except IOError as e :
298- print (f"\n *** Error writing to output file: { e } ***" , file = sys .stderr )
299- else :
300- print ("Operation cancelled." )
288+
289+ # Generate the project tree string from the final list of files
290+ relative_paths_for_tree = [f [1 ] for f in files_to_merge ]
291+ project_tree_str = generate_project_tree (relative_paths_for_tree , root_dir .name )
292+
293+ # 5. Phase 2: Merge files
294+ try :
295+ with open (output_file , "w" , encoding = "utf-8" ) as f :
296+ f .write (f"# --- flatcode: Project Context Snapshot --- #\n " )
297+ f .write (f"# Root: { root_dir } \n " )
298+ f .write (f"# Files: { len (files_to_merge )} \n " )
299+ f .write (f"# Est. Tokens: { total_tokens } \n " )
300+ f .write (f"# --- Project Tree --- #\n " )
301+ f .write (project_tree_str )
302+ f .write (f"# --- Start of Context --- #\n \n " )
303+
304+ for path , rel_path , tokens , content in files_to_merge :
305+ f .write (f"--- File: { rel_path } ---\n \n " )
306+ f .write (content )
307+ f .write (f"\n \n --- End: { rel_path } ---\n \n " )
308+
309+ except IOError as e :
310+ print (f"\n *** Error writing to output file: { e } ***" , file = sys .stderr )
301311
302312 except KeyboardInterrupt :
303313 print ("\n Operation cancelled by user." , file = sys .stderr )
0 commit comments