Skip to content

Commit 141016f

Browse files
committed
feat: Make generate-implementation actually implement code instead of just creating plans
- Updated generate-implementation.mjs to generate and apply actual code diffs - Uses git apply or patch command to apply changes - Falls back to implementation plan if code application fails - Updated workflow to handle both code changes and fallback plans - PRs now contain actual code implementations when successful
1 parent d439524 commit 141016f

2 files changed

Lines changed: 204 additions & 53 deletions

File tree

.github/agent/generate-implementation.mjs

Lines changed: 131 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@ import Anthropic from "@anthropic-ai/sdk";
22
import { readFileSync, writeFileSync, existsSync } from "fs";
33
import { resolve, dirname } from "path";
44
import { fileURLToPath } from "url";
5+
import { execSync } from "child_process";
56

67
const __dirname = dirname(fileURLToPath(import.meta.url));
78

89
/**
9-
* Generate Implementation - Use Claude to analyze codebase and generate implementation
10+
* Generate Implementation - Use Claude to analyze codebase and IMPLEMENT code changes
1011
*
1112
* This script uses Claude to:
1213
* 1. Analyze the existing codebase
13-
* 2. Generate an implementation plan
14-
* 3. Create code changes for the feature request
14+
* 2. Generate actual code changes (diffs)
15+
* 3. Apply the changes to the codebase
1516
*/
1617

1718
const enhancedTask = process.env.ENHANCED_TASK || process.env.TASK || "";
@@ -54,21 +55,22 @@ function getProjectContext() {
5455
const indexJs = readFileSync(resolve(repoRoot, "index.js"), "utf8");
5556
files.push({
5657
path: "index.js",
57-
content: indexJs.substring(0, 5000) // First 5000 chars to avoid token limits
58+
content: indexJs.substring(0, 10000) // More context for main file
5859
});
5960
} catch (e) {
6061
console.warn("[IMPLEMENTATION] Could not read index.js");
6162
}
6263

63-
// Read lib directory structure
64+
// Read lib directory files
6465
try {
6566
const libFiles = [
6667
"lib/slack.js",
6768
"lib/discord.js",
6869
"lib/voting.js",
6970
"lib/command-handlers.js",
7071
"lib/ai-handler.js",
71-
"lib/spotify.js"
72+
"lib/spotify.js",
73+
"lib/add-handlers.js"
7274
];
7375

7476
for (const libFile of libFiles) {
@@ -77,7 +79,7 @@ function getProjectContext() {
7779
const content = readFileSync(fullPath, "utf8");
7880
files.push({
7981
path: libFile,
80-
content: content.substring(0, 3000) // First 3000 chars per file
82+
content: content.substring(0, 5000) // More context per file
8183
});
8284
}
8385
}
@@ -89,11 +91,57 @@ function getProjectContext() {
8991
}
9092

9193
/**
92-
* Generate implementation using Claude
94+
* Apply diff to files
95+
*/
96+
function applyDiff(diff) {
97+
const repoRoot = resolve(__dirname, "../..");
98+
const tempPatchFile = resolve(repoRoot, `.tmp-patch-${issueNumber}.patch`);
99+
100+
try {
101+
// Write diff to temporary patch file
102+
writeFileSync(tempPatchFile, diff, "utf8");
103+
console.log(`[IMPLEMENTATION] Wrote patch to ${tempPatchFile}`);
104+
105+
// Try to apply the patch
106+
try {
107+
execSync(`cd "${repoRoot}" && git apply --ignore-whitespace "${tempPatchFile}"`, {
108+
stdio: 'inherit'
109+
});
110+
console.log(`[IMPLEMENTATION] ✅ Successfully applied patch`);
111+
return true;
112+
} catch (applyError) {
113+
console.warn(`[IMPLEMENTATION] ⚠️ git apply failed, trying patch command...`);
114+
try {
115+
execSync(`cd "${repoRoot}" && patch -p1 < "${tempPatchFile}"`, {
116+
stdio: 'inherit'
117+
});
118+
console.log(`[IMPLEMENTATION] ✅ Successfully applied patch with patch command`);
119+
return true;
120+
} catch (patchError) {
121+
console.error(`[IMPLEMENTATION] ❌ Failed to apply patch`);
122+
console.error(`[IMPLEMENTATION] git apply error: ${applyError.message}`);
123+
console.error(`[IMPLEMENTATION] patch error: ${patchError.message}`);
124+
return false;
125+
}
126+
}
127+
} finally {
128+
// Clean up temp file
129+
try {
130+
if (existsSync(tempPatchFile)) {
131+
execSync(`rm "${tempPatchFile}"`);
132+
}
133+
} catch (e) {
134+
// Ignore cleanup errors
135+
}
136+
}
137+
}
138+
139+
/**
140+
* Generate and apply implementation using Claude
93141
*/
94142
async function generateImplementation() {
95143
try {
96-
console.log(`[IMPLEMENTATION] Generating implementation with ${model}...`);
144+
console.log(`[IMPLEMENTATION] Generating code implementation with ${model}...`);
97145
console.log(`[IMPLEMENTATION] Feature request: ${enhancedTask}`);
98146

99147
const projectFiles = getProjectContext();
@@ -108,7 +156,7 @@ async function generateImplementation() {
108156
109157
The project is called SlackONOS and is a democratic bot where users can vote on songs to play.
110158
111-
Your task is to analyze the feature request and generate a concrete implementation plan with code suggestions.
159+
Your task is to IMPLEMENT the feature request by generating actual code changes in unified diff format.
112160
113161
Project context:
114162
- Node.js application
@@ -118,29 +166,35 @@ Project context:
118166
- Uses AI for natural language commands
119167
- Supports Spotify integration
120168
121-
Output format:
122-
1. **Implementation Plan** - Brief overview of what needs to be changed
123-
2. **Files to Modify/Create** - List specific files
124-
3. **Code Changes** - Provide actual code snippets or full file contents
169+
CRITICAL: You must generate a VALID unified diff that can be applied with git apply or patch command.
170+
171+
DIFF FORMAT REQUIREMENTS:
172+
1. Start each file with "--- a/path/to/file.js" and "+++ b/path/to/file.js" (BOTH lines required)
173+
2. NO "diff --git" line, NO "index" line with hashes
174+
3. Include "@@ -startLine,numLines +startLine,numLines @@" hunk headers
175+
4. Include at least 3 lines of context before and after each change
176+
5. Use "+" prefix for additions, "-" prefix for removals, " " (space) prefix for context
177+
6. Ensure line numbers match actual file contents
178+
7. Output ONLY the diff - no explanations, no markdown code blocks, no text before/after
179+
8. The diff MUST be complete and valid - every file section must have BOTH "--- a/path" AND "+++ b/path" lines
180+
9. NEVER truncate file paths - always write complete paths
181+
10. If creating new files, use "--- /dev/null" and "+++ b/path/to/newfile.js"
125182
126-
Be specific and actionable. Focus on the actual code changes needed.`;
183+
Generate the complete, valid unified diff now.`;
127184

128185
const userPrompt = `Feature Request to Implement:
129186
130187
${enhancedTask}
131188
132189
${contextText}
133190
134-
Please provide:
135-
1. A brief implementation plan
136-
2. List of files to modify or create
137-
3. Actual code changes with clear instructions
191+
Generate a valid unified diff that implements this feature. The diff must be complete and ready to apply with git apply or patch command.
138192
139-
Make it actionable and ready to commit.`;
193+
Output ONLY the diff, no explanations.`;
140194

141195
const response = await anthropic.messages.create({
142196
model: model,
143-
max_tokens: 4096,
197+
max_tokens: 8192, // Increased for larger diffs
144198
messages: [
145199
{
146200
role: "user",
@@ -154,23 +208,66 @@ Make it actionable and ready to commit.`;
154208
]
155209
});
156210

157-
const implementation = response.content[0].text;
211+
const diff = response.content[0].text;
158212

159-
console.log(`[IMPLEMENTATION] Generated implementation plan:`);
160-
console.log(implementation);
213+
console.log(`[IMPLEMENTATION] Generated diff:`);
214+
console.log(diff);
161215

162-
// Save implementation to file for the workflow to use
163-
const outputPath = resolve(__dirname, `../../implementation-${issueNumber}.md`);
164-
writeFileSync(outputPath, implementation, "utf8");
165-
console.log(`[IMPLEMENTATION] Saved to ${outputPath}`);
216+
// Extract diff from potential markdown code blocks
217+
let cleanDiff = diff.trim();
218+
if (cleanDiff.includes('```')) {
219+
const matches = cleanDiff.matchAll(/```(?:diff)?\n([\s\S]*?)```/g);
220+
const extracted = [];
221+
for (const match of matches) {
222+
extracted.push(match[1].trim());
223+
}
224+
if (extracted.length > 0) {
225+
cleanDiff = extracted.reduce((a, b) => a.length > b.length ? a : b);
226+
}
227+
}
166228

167-
// Output markers for workflow parsing
168-
console.log(`\nIMPLEMENTATION_FILE:${outputPath}`);
169-
console.log(`IMPLEMENTATION_START`);
170-
console.log(implementation);
171-
console.log(`IMPLEMENTATION_END`);
229+
// Validate diff format
230+
if (!cleanDiff.includes('--- a/') || !cleanDiff.includes('+++ b/')) {
231+
console.error(`[IMPLEMENTATION] ❌ Generated output is not a valid diff format`);
232+
console.error(`[IMPLEMENTATION] Missing required diff markers (--- a/ or +++ b/)`);
233+
234+
// Save as fallback implementation plan
235+
const outputPath = resolve(__dirname, `../../implementation-${issueNumber}.md`);
236+
writeFileSync(outputPath, `# Implementation Plan\n\n${enhancedTask}\n\n## Generated Output\n\n${diff}`, "utf8");
237+
console.log(`[IMPLEMENTATION] Saved as fallback plan to ${outputPath}`);
238+
console.log(`\nIMPLEMENTATION_FILE:${outputPath}`);
239+
return;
240+
}
172241

173-
return implementation;
242+
// Apply the diff
243+
const applied = applyDiff(cleanDiff);
244+
245+
if (applied) {
246+
console.log(`[IMPLEMENTATION] ✅ Code changes successfully applied!`);
247+
248+
// List changed files
249+
try {
250+
const changedFiles = execSync('git diff --name-only', { encoding: 'utf8' }).trim().split('\n').filter(f => f);
251+
console.log(`[IMPLEMENTATION] Changed files:`);
252+
changedFiles.forEach(f => console.log(`[IMPLEMENTATION] - ${f}`));
253+
254+
// Output marker for workflow
255+
console.log(`\nIMPLEMENTATION_FILE:APPLIED`);
256+
console.log(`IMPLEMENTATION_CHANGED_FILES:${changedFiles.join(',')}`);
257+
} catch (e) {
258+
console.warn(`[IMPLEMENTATION] Could not list changed files: ${e.message}`);
259+
console.log(`\nIMPLEMENTATION_FILE:APPLIED`);
260+
}
261+
} else {
262+
console.error(`[IMPLEMENTATION] ❌ Failed to apply code changes`);
263+
264+
// Save diff as fallback
265+
const outputPath = resolve(__dirname, `../../implementation-${issueNumber}.patch`);
266+
writeFileSync(outputPath, cleanDiff, "utf8");
267+
console.log(`[IMPLEMENTATION] Saved diff to ${outputPath} for manual review`);
268+
console.log(`\nIMPLEMENTATION_FILE:${outputPath}`);
269+
process.exit(1);
270+
}
174271

175272
} catch (error) {
176273
console.error(`[IMPLEMENTATION] Error generating implementation: ${error.message}`);

.github/workflows/feature-request-enhance.yml

Lines changed: 73 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ jobs:
348348
} >> "$GITHUB_OUTPUT"
349349
fi
350350
351-
- name: Generate implementation with Claude
351+
- name: Generate and apply implementation with Claude
352352
id: generate
353353
env:
354354
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -361,14 +361,23 @@ jobs:
361361
OUTPUT=$(node .github/agent/generate-implementation.mjs 2>&1)
362362
echo "$OUTPUT"
363363
364-
# Extract implementation file path
365-
IMPL_FILE=$(echo "$OUTPUT" | grep "IMPLEMENTATION_FILE:" | sed 's/IMPLEMENTATION_FILE://' | tr -d '[:space:]')
366-
echo "implementation_file=$IMPL_FILE" >> $GITHUB_OUTPUT
364+
# Check if code was applied or if we have a fallback file
365+
if echo "$OUTPUT" | grep -q "IMPLEMENTATION_FILE:APPLIED"; then
366+
echo "code_applied=true" >> $GITHUB_OUTPUT
367+
CHANGED_FILES=$(echo "$OUTPUT" | grep "IMPLEMENTATION_CHANGED_FILES:" | sed 's/IMPLEMENTATION_CHANGED_FILES://' | tr -d '[:space:]')
368+
echo "changed_files=$CHANGED_FILES" >> $GITHUB_OUTPUT
369+
else
370+
echo "code_applied=false" >> $GITHUB_OUTPUT
371+
IMPL_FILE=$(echo "$OUTPUT" | grep "IMPLEMENTATION_FILE:" | sed 's/IMPLEMENTATION_FILE://' | tr -d '[:space:]')
372+
echo "implementation_file=$IMPL_FILE" >> $GITHUB_OUTPUT
373+
fi
367374
368375
- name: Create implementation branch and PR
369376
env:
370377
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
371378
ISSUE_NUMBER: ${{ needs.check-label.outputs.issue_number }}
379+
CODE_APPLIED: ${{ steps.generate.outputs.code_applied }}
380+
CHANGED_FILES: ${{ steps.generate.outputs.changed_files }}
372381
IMPL_FILE: ${{ steps.generate.outputs.implementation_file }}
373382
run: |
374383
set -e
@@ -381,38 +390,83 @@ jobs:
381390
BRANCH_NAME="feature/issue-$ISSUE_NUMBER-implementation"
382391
git checkout -b "$BRANCH_NAME"
383392
384-
# Add the implementation file
385-
if [ -f "$IMPL_FILE" ]; then
393+
# Check if code was applied or if we need to add fallback file
394+
if [ "$CODE_APPLIED" = "true" ]; then
395+
# Code was applied - add all changed files
396+
echo "✅ Code changes were applied, staging all changes..."
397+
git add -A
398+
399+
# Get list of changed files for commit message
400+
CHANGED=$(git diff --cached --name-only | tr '\n' ',' | sed 's/,$//')
401+
echo "Changed files: $CHANGED"
402+
403+
# Create commit message
404+
COMMIT_MSG=$(cat <<EOF
405+
Implement feature request for issue #$ISSUE_NUMBER
406+
407+
This is an AI-generated implementation created by Claude.
408+
Please review and test the changes.
409+
410+
Changed files: $CHANGED
411+
Related issue: #$ISSUE_NUMBER
412+
EOF
413+
)
414+
elif [ -f "$IMPL_FILE" ]; then
415+
# Fallback: add the implementation plan file
416+
echo "⚠️ Code application failed, adding implementation plan file..."
386417
git add "$IMPL_FILE"
387-
388-
# Create commit with heredoc to handle multiline
389-
git commit -m "$(cat <<EOF
418+
CHANGED="$IMPL_FILE"
419+
420+
# Create commit message
421+
COMMIT_MSG=$(cat <<EOF
390422
Add implementation plan for issue #$ISSUE_NUMBER
391423
392424
This is an AI-generated implementation plan created by Claude.
393-
Please review and adapt as needed.
425+
Code application failed - please review the plan and implement manually.
394426
395427
Related issue: #$ISSUE_NUMBER
396428
EOF
397-
)"
429+
)
430+
else
431+
echo "❌ No code changes and no implementation file found"
432+
exit 1
433+
fi
434+
435+
git commit -m "$COMMIT_MSG"
436+
437+
# Push branch
438+
git push -u origin "$BRANCH_NAME"
439+
440+
# Create PR body
441+
if [ "$CODE_APPLIED" = "true" ]; then
442+
PR_BODY=$(cat <<EOF
443+
## 🤖 AI-Generated Implementation
444+
445+
This PR contains an **actual code implementation** generated by Claude AI for issue #$ISSUE_NUMBER.
398446
399-
# Push branch
400-
git push -u origin "$BRANCH_NAME"
447+
### ✅ Changes Applied
448+
The following files were modified:
449+
$(echo "$CHANGED_FILES" | tr ',' '\n' | sed 's/^/ - /')
401450
402-
# Create PR body using heredoc
403-
PR_BODY=$(cat <<'EOF'
451+
### 📝 Review Notes
452+
Please review the changes and run tests before merging.
453+
EOF
454+
)
455+
else
456+
PR_BODY=$(cat <<EOF
404457
## 🤖 AI-Generated Implementation Plan
405458
406459
This PR contains an implementation plan generated by Claude AI for issue #$ISSUE_NUMBER.
407460
461+
⚠️ **Note**: Code application failed. This PR contains a plan that needs manual implementation.
462+
408463
### 📋 Implementation Plan
409464
See [implementation-$ISSUE_NUMBER.md](./implementation-$ISSUE_NUMBER.md) for details.
410465
411466
### ⚠️ Important
412-
This is an **AI-generated proposal**. Please:
413-
- Review the implementation plan carefully
414-
- Adapt and modify as needed
415-
- Add actual code changes based on the plan
467+
This is an **AI-generated implementation plan**. Please:
468+
- Review the plan carefully
469+
- Implement the code changes manually
416470
- Test thoroughly before merging
417471
418472
Closes #$ISSUE_NUMBER

0 commit comments

Comments
 (0)