11import Anthropic from "@anthropic-ai/sdk" ;
2- import { readFileSync , writeFileSync , existsSync } from "fs" ;
3- import { resolve , dirname } from "path" ;
2+ import { readFileSync , writeFileSync , existsSync , readdirSync , statSync } from "fs" ;
3+ import { resolve , dirname , join } from "path" ;
44import { fileURLToPath } from "url" ;
55import { execSync } from "child_process" ;
66
@@ -10,7 +10,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
1010 * Generate Implementation - Use Claude to analyze codebase and IMPLEMENT code changes
1111 *
1212 * This script uses Claude to:
13- * 1. Analyze the existing codebase
13+ * 1. Intelligently identify relevant files based on the task
1414 * 2. Generate actual code changes (diffs)
1515 * 3. Apply the changes to the codebase
1616 */
@@ -32,61 +32,197 @@ if (!anthropicApiKey) {
3232
3333const anthropic = new Anthropic ( { apiKey : anthropicApiKey } ) ;
3434
35+ /**
36+ * Get all files in the repository
37+ */
38+ function getAllFiles ( ) {
39+ const repoRoot = resolve ( __dirname , "../.." ) ;
40+ const files = [ ] ;
41+
42+ function walkDir ( dir , basePath = "" ) {
43+ try {
44+ const entries = readdirSync ( dir , { withFileTypes : true } ) ;
45+ for ( const entry of entries ) {
46+ const fullPath = join ( dir , entry . name ) ;
47+ const relPath = basePath ? join ( basePath , entry . name ) : entry . name ;
48+
49+ // Skip excluded paths
50+ if ( relPath . includes ( 'node_modules/' ) ||
51+ relPath . includes ( '.git/' ) ||
52+ relPath . includes ( 'coverage/' ) ||
53+ relPath . includes ( 'dist/' ) ||
54+ relPath . includes ( 'build/' ) ||
55+ relPath . startsWith ( '.' ) && ! relPath . startsWith ( '.github/' ) ) {
56+ continue ;
57+ }
58+
59+ if ( entry . isDirectory ( ) ) {
60+ walkDir ( fullPath , relPath ) ;
61+ } else if ( entry . isFile ( ) ) {
62+ // Only include code files
63+ const ext = entry . name . split ( '.' ) . pop ( ) ;
64+ if ( [ 'js' , 'mjs' , 'json' , 'html' , 'css' , 'txt' , 'yml' , 'yaml' ] . includes ( ext ) ) {
65+ files . push ( relPath ) ;
66+ }
67+ }
68+ }
69+ } catch ( e ) {
70+ // Skip directories we can't read
71+ }
72+ }
73+
74+ walkDir ( repoRoot ) ;
75+ return files ;
76+ }
77+
78+ /**
79+ * Select relevant files based on task description (keyword-based)
80+ */
81+ function selectRelevantFiles ( task , fileList ) {
82+ const taskLower = task . toLowerCase ( ) ;
83+ const relevantFiles = new Set ( ) ;
84+
85+ // Keyword to file mapping (updated for current codebase structure)
86+ const keywordMap = {
87+ 'spotify' : [ 'lib/spotify.js' , 'lib/command-handlers.js' ] ,
88+ 'discord' : [ 'lib/discord.js' ] ,
89+ 'slack' : [ 'lib/slack.js' ] ,
90+ 'sonos' : [ 'index.js' ] ,
91+ 'vote' : [ 'lib/voting.js' , 'index.js' ] ,
92+ 'gong' : [ 'lib/voting.js' , 'index.js' ] ,
93+ 'admin' : [ 'public/setup/admin.js' , 'public/setup/admin.html' ] ,
94+ 'auth' : [ 'lib/auth-handler.js' , 'lib/webauthn-handler.js' ] ,
95+ 'ai' : [ 'lib/ai-handler.js' ] ,
96+ 'soundcraft' : [ 'lib/soundcraft-handler.js' ] ,
97+ 'help' : [ 'templates/help/' , 'index.js' ] ,
98+ 'web' : [ 'public/setup/' , 'public/' ] ,
99+ 'config' : [ 'index.js' ] ,
100+ 'queue' : [ 'lib/add-handlers.js' , 'index.js' ] ,
101+ 'search' : [ 'lib/spotify.js' , 'lib/command-handlers.js' , 'index.js' ] ,
102+ 'command' : [ 'lib/command-handlers.js' , 'index.js' ] ,
103+ 'feature' : [ 'index.js' , 'lib/command-handlers.js' ] ,
104+ 'alias' : [ 'index.js' , 'lib/command-handlers.js' ] ,
105+ 'github' : [ 'lib/github-app.js' , 'index.js' ]
106+ } ;
107+
108+ // Find relevant files based on keywords
109+ for ( const [ keyword , files ] of Object . entries ( keywordMap ) ) {
110+ if ( taskLower . includes ( keyword ) ) {
111+ for ( const file of files ) {
112+ // Check if file exists in fileList
113+ const matchingFiles = fileList . filter ( f =>
114+ f . includes ( file ) || f . endsWith ( file ) || f === file
115+ ) ;
116+ matchingFiles . forEach ( f => relevantFiles . add ( f ) ) ;
117+ }
118+ }
119+ }
120+
121+ // Also check for direct file mentions
122+ for ( const file of fileList ) {
123+ const fileName = file . split ( '/' ) . pop ( ) ;
124+ if ( taskLower . includes ( fileName . toLowerCase ( ) . replace ( / \. ( j s | m j s | j s o n ) $ / , '' ) ) ) {
125+ relevantFiles . add ( file ) ;
126+ }
127+ }
128+
129+ return Array . from ( relevantFiles ) ;
130+ }
131+
35132/**
36133 * Read relevant project files to give Claude context
37134 */
38- function getProjectContext ( ) {
135+ function getProjectContext ( task ) {
39136 const repoRoot = resolve ( __dirname , "../.." ) ;
40137 const files = [ ] ;
138+ const fileList = getAllFiles ( ) ;
139+
140+ // Priority files (always include)
141+ const priorityFiles = [
142+ 'package.json' ,
143+ 'index.js'
144+ ] ;
145+
146+ // Get task-relevant files
147+ const relevantFiles = selectRelevantFiles ( task , fileList ) ;
148+ console . log ( `[IMPLEMENTATION] Identified ${ relevantFiles . length } relevant files based on task keywords` ) ;
149+ if ( relevantFiles . length > 0 ) {
150+ console . log ( `[IMPLEMENTATION] Relevant files: ${ relevantFiles . join ( ', ' ) } ` ) ;
151+ }
41152
42- // Read package.json for dependencies
43- try {
44- const packageJson = readFileSync ( resolve ( repoRoot , "package.json" ) , "utf8" ) ;
45- files . push ( {
46- path : "package.json" ,
47- content : packageJson
48- } ) ;
49- } catch ( e ) {
50- console . warn ( "[IMPLEMENTATION] Could not read package.json" ) ;
153+ // First pass: Include priority files
154+ for ( const filePath of priorityFiles ) {
155+ try {
156+ const fullPath = resolve ( repoRoot , filePath ) ;
157+ if ( existsSync ( fullPath ) ) {
158+ const content = readFileSync ( fullPath , "utf8" ) ;
159+ // Include full content for priority files
160+ files . push ( {
161+ path : filePath ,
162+ content : filePath === 'index.js' ? content . substring ( 0 , 15000 ) : content
163+ } ) ;
164+ }
165+ } catch ( e ) {
166+ console . warn ( `[IMPLEMENTATION] Could not read ${ filePath } ` ) ;
167+ }
51168 }
52169
53- // Read main index file
54- try {
55- const indexJs = readFileSync ( resolve ( repoRoot , "index.js" ) , "utf8" ) ;
56- files . push ( {
57- path : "index.js" ,
58- content : indexJs . substring ( 0 , 10000 ) // More context for main file
59- } ) ;
60- } catch ( e ) {
61- console . warn ( "[IMPLEMENTATION] Could not read index.js" ) ;
170+ // Second pass: Include task-relevant files
171+ for ( const filePath of relevantFiles ) {
172+ try {
173+ const fullPath = resolve ( repoRoot , filePath ) ;
174+ if ( existsSync ( fullPath ) ) {
175+ const stats = statSync ( fullPath ) ;
176+ // Skip very large files
177+ if ( stats . size > 100000 ) {
178+ console . log ( `[IMPLEMENTATION] Skipping large file: ${ filePath } (${ stats . size } bytes)` ) ;
179+ continue ;
180+ }
181+
182+ const content = readFileSync ( fullPath , "utf8" ) ;
183+ files . push ( {
184+ path : filePath ,
185+ content : content // Include full content for relevant files
186+ } ) ;
187+ }
188+ } catch ( e ) {
189+ console . warn ( `[IMPLEMENTATION] Could not read ${ filePath } ` ) ;
190+ }
62191 }
63192
64- // Read lib directory files
65- try {
66- const libFiles = [
67- "lib/slack.js" ,
68- "lib/discord.js" ,
69- "lib/voting.js" ,
70- "lib/command-handlers.js" ,
71- "lib/ai-handler.js" ,
72- "lib/spotify.js" ,
73- "lib/add-handlers.js"
74- ] ;
75-
76- for ( const libFile of libFiles ) {
77- const fullPath = resolve ( repoRoot , libFile ) ;
193+ // Third pass: Include other lib files if we have space (limit to prevent token overflow)
194+ const maxFiles = 15 ; // Limit total files
195+ const libFiles = [
196+ 'lib/slack.js' ,
197+ 'lib/discord.js' ,
198+ 'lib/voting.js' ,
199+ 'lib/command-handlers.js' ,
200+ 'lib/ai-handler.js' ,
201+ 'lib/spotify.js' ,
202+ 'lib/add-handlers.js'
203+ ] ;
204+
205+ for ( const filePath of libFiles ) {
206+ if ( files . length >= maxFiles ) break ;
207+
208+ // Skip if already included
209+ if ( files . some ( f => f . path === filePath ) ) continue ;
210+
211+ try {
212+ const fullPath = resolve ( repoRoot , filePath ) ;
78213 if ( existsSync ( fullPath ) ) {
79214 const content = readFileSync ( fullPath , "utf8" ) ;
80215 files . push ( {
81- path : libFile ,
82- content : content . substring ( 0 , 5000 ) // More context per file
216+ path : filePath ,
217+ content : content . substring ( 0 , 8000 ) // Truncate non-priority files
83218 } ) ;
84219 }
220+ } catch ( e ) {
221+ // Skip if can't read
85222 }
86- } catch ( e ) {
87- console . warn ( "[IMPLEMENTATION] Could not read lib files" ) ;
88223 }
89224
225+ console . log ( `[IMPLEMENTATION] Including ${ files . length } files in context` ) ;
90226 return files ;
91227}
92228
@@ -144,7 +280,7 @@ async function generateImplementation() {
144280 console . log ( `[IMPLEMENTATION] Generating code implementation with ${ model } ...` ) ;
145281 console . log ( `[IMPLEMENTATION] Feature request: ${ enhancedTask } ` ) ;
146282
147- const projectFiles = getProjectContext ( ) ;
283+ const projectFiles = getProjectContext ( enhancedTask ) ;
148284
149285 // Build context from project files
150286 let contextText = "Here are relevant files from the project:\n\n" ;
0 commit comments