1+ using System . Diagnostics ;
2+ using System . Reflection ;
3+ using System . Text ;
4+ using TextCopy ;
5+
6+
7+ namespace PreCoding
8+ {
9+ class Program
10+ {
11+ static void Main ( string [ ] args )
12+ {
13+ // Get the path to the running EXE
14+ var metadata = GetExeMetadata ( ) ;
15+ Console . WriteLine ( $ "PreCoding Tool [Version: { metadata . appVersion } ]") ;
16+ Console . WriteLine ( $ "Author: { metadata . companyName } ") ;
17+ Console . WriteLine ( $ "License: GPLv3") ;
18+ Console . WriteLine ( $ "Build: { metadata . lastWriteTime } ") ;
19+ Console . WriteLine ( $ "Ensure your source code files are UTF-8 encoded.\n ") ;
20+
21+ string targetDirectory = Directory . GetCurrentDirectory ( ) ;
22+ //string targetDirectory = @"C:\Users\shawhu\ProjectC\heheng\hehengclient";
23+
24+ bool fileListMode = ( args . Length > 0 && args [ 0 ] . Equals ( "-files" , StringComparison . OrdinalIgnoreCase ) ) ;
25+ bool helpMode = ( args . Length > 0 && args [ 0 ] . Equals ( "-help" , StringComparison . OrdinalIgnoreCase ) ) ;
26+ string [ ] fileList = [ ] ;
27+
28+ if ( helpMode )
29+ {
30+ Console . WriteLine ( "Usages examples:" ) ;
31+ Console . WriteLine ( "1. Specify file search patterns:" ) ;
32+ Console . WriteLine ( "precoding.exe -files file1.cs file2.json ..." ) ;
33+ Console . WriteLine ( ) ;
34+ Console . WriteLine ( "Specify targetDir" ) ;
35+ Console . WriteLine ( "precoding.exe <targetDir>" ) ;
36+ Console . WriteLine ( ) ;
37+ Console . WriteLine ( "it will search the currentDir with default search patterns" ) ;
38+ Console . WriteLine ( "precoding.exe" ) ;
39+ Console . WriteLine ( ) ;
40+ return ;
41+ }
42+ if ( fileListMode )
43+ {
44+ // ADDED: File list mode, get list of files after the -files argument
45+ if ( args . Length < 2 )
46+ {
47+ Console . WriteLine ( "Usage: precoding.exe -files file1.cs file2.json ..." ) ;
48+ return ;
49+ }
50+ fileList = args . Skip ( 1 ) . ToArray ( ) ; // CORRECT: this gets all arguments after -files
51+ }
52+ else if ( args . Length > 0 )
53+ {
54+ targetDirectory = args [ 0 ] ;
55+ }
56+
57+ Console . WriteLine ( "========== File Aggregation Started ==========" ) ;
58+ if ( ! Directory . Exists ( targetDirectory ) )
59+ {
60+ Console . WriteLine ( $ "[ERROR] Target directory does not exist: { targetDirectory } ") ;
61+
62+ return ;
63+ }
64+
65+ // Folders to ignore (case-insensitive)
66+ var ignoredFolderNames = new HashSet < string > ( new [ ] {
67+ "node_modules" ,
68+ "Migrations" ,
69+ "obj" ,
70+ "app-example" ,
71+ "staticdata" ,
72+ "docs"
73+ } , StringComparer . OrdinalIgnoreCase ) ;
74+
75+ // File patterns to search
76+ string [ ] searchPatterns ;
77+ if ( fileListMode )
78+ {
79+ // CHANGED: Use exact filenames as searchPatterns
80+ searchPatterns = fileList ;
81+ }
82+ else
83+ {
84+ searchPatterns = new [ ] {
85+ "*.cs" ,
86+ "*.tsx" ,
87+ "*.ts" ,
88+ "package.json" ,
89+ "*.csproj"
90+ } ;
91+ }
92+
93+ // Printout the criteria
94+ Console . WriteLine ( $ "Mode: { ( fileListMode ? "File List" : "Pattern Search" ) } ") ;
95+ Console . WriteLine ( $ "Search Targets: { string . Join ( ", " , searchPatterns ) } ") ;
96+ Console . WriteLine ( $ "Ignored Folders: { string . Join ( ", " , ignoredFolderNames ) } ") ;
97+
98+ // Find all files, skipping hidden, ignored, and dot-prefixed folders/files
99+ var files = GetFilesSkippingFolders ( targetDirectory , searchPatterns , ignoredFolderNames )
100+ . Distinct ( )
101+ . OrderBy ( f => f )
102+ . ToList ( ) ;
103+
104+ if ( ! files . Any ( ) )
105+ {
106+ Console . WriteLine ( "No matching source files found. Exiting." ) ;
107+ Console . WriteLine ( "========== Aggregation Complete ==============" ) ;
108+
109+ return ;
110+ }
111+
112+ StringBuilder output = new StringBuilder ( ) ;
113+ StringBuilder error = new StringBuilder ( ) ;
114+ string templatePath = Path . Combine ( AppContext . BaseDirectory ?? Directory . GetCurrentDirectory ( ) , "prompt_header.md" ) ;
115+ if ( File . Exists ( templatePath ) )
116+ {
117+ output . AppendLine ( File . ReadAllText ( templatePath ) ) ;
118+ }
119+ else
120+ {
121+ output . AppendLine ( "## Instructions:" ) ;
122+ output . AppendLine ( "1. **Code-First Replies:** Always respond with a code block containing the exact code (method, function, or class) to be replaced or inserted." ) ;
123+ output . AppendLine ( " - For long files, include only the full method/function/class that is being changed." ) ;
124+ output . AppendLine ( "2. **Clear Change Comments:** Clearly mark your changes using `// CHANGED`, `// ADDED`, `// REMOVED`, etc." ) ;
125+ output . AppendLine ( "3. **No Redundant Suggestions:** Double-check my code and **do not suggest fixes for issues already handled**." ) ;
126+ output . AppendLine ( "4. **Prefer Inline Solutions:** Use concise, inline code when possible. Avoid multiple lines for changes that can be made in one." ) ;
127+ output . AppendLine ( "5. **Be Accurate, Direct, and Minimal:**" ) ;
128+ output . AppendLine ( " - Do not add extra features or explanations unless requested." ) ;
129+ output . AppendLine ( " - Provide only code and necessary comments." ) ;
130+ output . AppendLine ( "6. **Reference the Provided Codebase:** Use the code files and their filenames below as context for your responses." ) ;
131+ output . AppendLine ( ) ;
132+ output . AppendLine ( "## Below are all the source code files for reference:" ) ;
133+ }
134+
135+ foreach ( var file in files )
136+ {
137+ try
138+ {
139+ string filetype = GetFileType ( file ) ;
140+ string relativePath = Path . GetRelativePath ( targetDirectory , file ) ;
141+ output . AppendLine ( $ "### --- FILENAME: { relativePath } ---") ;
142+ output . AppendLine ( $ "```{ filetype } ") ;
143+ string content = File . ReadAllText ( file , Encoding . UTF8 ) ;
144+ output . AppendLine ( content ) ;
145+ output . AppendLine ( "```" ) ;
146+ }
147+ catch ( Exception ex )
148+ {
149+ error . AppendLine ( $ "ERROR reading { file } : { ex . Message } ---") ;
150+ error . AppendLine ( ) ;
151+ }
152+ }
153+
154+ // Output file
155+ string outputStr = output . ToString ( ) ;
156+ string outputFile = Path . Combine ( targetDirectory , "AllSourceFiles.txt" ) ;
157+ File . WriteAllText ( outputFile , outputStr , Encoding . UTF8 ) ;
158+
159+ // Get file size in MB
160+ FileInfo fi = new FileInfo ( outputFile ) ;
161+ double sizeInMb = fi . Length / ( 1024.0 * 1024.0 ) ;
162+ Console . WriteLine ( $ "Completed! Aggregated { files . Count } files.") ;
163+ Console . WriteLine ( $ "Output written to: { outputFile } [{ sizeInMb : F2} MB]") ;
164+ if ( sizeInMb <= 1 )
165+ Console . WriteLine ( "Output copied to clipboard." ) ;
166+ else
167+ Console . WriteLine ( "Output too large for clipboard; only written to file." ) ;
168+ Console . WriteLine ( "========== Aggregation Complete ==============" ) ;
169+
170+ // Copy to clipboard
171+ if ( sizeInMb <= 1 ) ClipboardService . SetText ( outputStr ) ;
172+ else Console . WriteLine ( $ "Output file is too big, skipping automatic clipboard copying.") ;
173+
174+ //if error
175+ if ( ! string . IsNullOrWhiteSpace ( error . ToString ( ) ) )
176+ Console . WriteLine ( "[WARN] Some files could not be read:\n " + error . ToString ( ) ) ;
177+
178+
179+ }
180+
181+ static IEnumerable < string > GetFilesSkippingFolders (
182+ string rootPath ,
183+ string [ ] searchPatterns ,
184+ HashSet < string > ignoredFolderNames
185+ )
186+ {
187+ var dirs = new Stack < DirectoryInfo > ( ) ;
188+ dirs . Push ( new DirectoryInfo ( rootPath ) ) ;
189+
190+ while ( dirs . Count > 0 )
191+ {
192+ DirectoryInfo currentDir = dirs . Pop ( ) ;
193+
194+ // Skip hidden directories
195+ if ( ( currentDir . Attributes & FileAttributes . Hidden ) != 0 )
196+ continue ;
197+
198+ // Skip ignored directories by name (e.g., node_modules)
199+ if ( ignoredFolderNames . Contains ( currentDir . Name ) )
200+ continue ;
201+
202+ // Skip directories that start with a dot (.)
203+ if ( currentDir . Name . StartsWith ( "." ) )
204+ continue ;
205+
206+ // Get files for each pattern
207+ foreach ( var pattern in searchPatterns )
208+ {
209+ FileInfo [ ] files = [ ] ;
210+ try
211+ {
212+ files = currentDir . GetFiles ( pattern , SearchOption . TopDirectoryOnly ) ;
213+ }
214+ catch
215+ {
216+ // Could not access this directory, skip
217+ continue ;
218+ }
219+
220+ foreach ( var file in files )
221+ {
222+ // Skip dot-prefixed files
223+ if ( file . Name . StartsWith ( "." ) )
224+ continue ;
225+
226+ yield return file . FullName ;
227+ }
228+ }
229+
230+ // Push subdirectories that are not hidden, not ignored, and not dot-starting
231+ DirectoryInfo [ ] subDirs = [ ] ;
232+ try
233+ {
234+ subDirs = currentDir . GetDirectories ( ) ;
235+ }
236+ catch
237+ {
238+ continue ;
239+ }
240+
241+ foreach ( var subDir in subDirs )
242+ {
243+ if ( ( subDir . Attributes & FileAttributes . Hidden ) == 0 &&
244+ ! ignoredFolderNames . Contains ( subDir . Name ) &&
245+ ! subDir . Name . StartsWith ( "." ) )
246+ {
247+ dirs . Push ( subDir ) ;
248+ }
249+ }
250+ }
251+ }
252+
253+ static string GetFileType ( string file )
254+ {
255+ var extension = Path . GetExtension ( file ) . ToLowerInvariant ( ) ;
256+ var typeMap = new Dictionary < string , string >
257+ { { ".bat" , "bat" } ,
258+ { ".c" , "c" } ,
259+ { ".cc" , "cpp" } ,
260+ { ".cmd" , "bat" } ,
261+ { ".cpp" , "cpp" } ,
262+ { ".cs" , "csharp" } ,
263+ { ".css" , "css" } ,
264+ { ".dart" , "dart" } ,
265+ { ".diff" , "diff" } ,
266+ { ".env" , "env" } ,
267+ { ".go" , "go" } ,
268+ { ".graphql" , "graphql" } ,
269+ { ".gql" , "graphql" } ,
270+ { ".h" , "cpp" } ,
271+ { ".htm" , "html" } ,
272+ { ".html" , "html" } ,
273+ { ".ini" , "ini" } ,
274+ { ".java" , "java" } ,
275+ { ".jl" , "julia" } ,
276+ { ".js" , "javascript" } ,
277+ { ".json" , "json" } ,
278+ { ".kt" , "kotlin" } ,
279+ { ".kts" , "kotlin" } ,
280+ { ".latex" , "latex" } ,
281+ { ".lisp" , "lisp" } ,
282+ { ".lsp" , "lisp" } ,
283+ { ".lua" , "lua" } ,
284+ { ".md" , "markdown" } ,
285+ { ".mjs" , "javascript" } ,
286+ { ".php" , "php" } ,
287+ { ".pl" , "perl" } ,
288+ { ".pm" , "perl" } ,
289+ { ".properties" , "properties" } ,
290+ { ".ps1" , "powershell" } ,
291+ { ".psm1" , "powershell" } ,
292+ { ".py" , "python" } ,
293+ { ".pyw" , "python" } ,
294+ { ".r" , "r" } ,
295+ { ".rb" , "ruby" } ,
296+ { ".rs" , "rust" } ,
297+ { ".scala" , "scala" } ,
298+ { ".sh" , "bash" } ,
299+ { ".sql" , "sql" } ,
300+ { ".swift" , "swift" } ,
301+ { ".tex" , "latex" } ,
302+ { ".toml" , "toml" } ,
303+ { ".ts" , "typescript" } ,
304+ { ".tsx" , "typescript" } ,
305+ { ".txt" , "plaintext" } ,
306+ { ".xml" , "xml" } ,
307+ { ".yml" , "yaml" } ,
308+ { ".yaml" , "yaml" }
309+ } ;
310+
311+ return typeMap . TryGetValue ( extension , out var type ) ? type : "" ;
312+ }
313+
314+ static ( string exePath , string appVersion , string companyName , DateTime lastWriteTime ) GetExeMetadata ( )
315+ {
316+ string exePath = Process . GetCurrentProcess ( ) . MainModule ? . FileName ?? "" ;
317+ DateTime lastWriteTime = DateTime . MinValue ;
318+ string appVersion = "Unknown" ;
319+ string companyName = "Unknown" ;
320+
321+ // Get assembly info from the file itself (EXE)
322+ if ( ! string . IsNullOrEmpty ( exePath ) && File . Exists ( exePath ) )
323+ {
324+ try
325+ {
326+ var assembly = Assembly . LoadFrom ( exePath ) ;
327+ appVersion = assembly . GetCustomAttribute < AssemblyInformationalVersionAttribute > ( ) ? . InformationalVersion ?? "Unknown" ;
328+ companyName = assembly . GetCustomAttribute < AssemblyCompanyAttribute > ( ) ? . Company ?? "Unknown" ;
329+ }
330+ catch
331+ {
332+ // fallback: try FileVersionInfo
333+ var fvi = FileVersionInfo . GetVersionInfo ( exePath ) ;
334+ appVersion = fvi . ProductVersion ?? "Unknown" ;
335+ companyName = fvi . CompanyName ?? "Unknown" ;
336+ }
337+ lastWriteTime = File . GetLastWriteTime ( exePath ) ;
338+ }
339+ return ( exePath , appVersion , companyName , lastWriteTime ) ;
340+ }
341+ }
342+ }
0 commit comments