Skip to content

Commit 38b0e8a

Browse files
authored
Merge pull request #251 from cloudflare/feat/proper-finalization
Refactor: improve static analysis caching and finalization phase generation rewrite
2 parents 2b3a901 + bbc6b1d commit 38b0e8a

File tree

3 files changed

+106
-111
lines changed

3 files changed

+106
-111
lines changed

worker/agents/core/simpleGeneratorAgent.ts

Lines changed: 35 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
9595
private currentAbortController?: AbortController;
9696
private deepDebugPromise: Promise<{ transcript: string } | { error: string }> | null = null;
9797
private deepDebugConversationId: string | null = null;
98+
99+
private staticAnalysisCache: StaticAnalysisResponse | null = null;
98100

99101
// GitHub token cache (ephemeral, lost on DO eviction)
100102
private githubTokenCache: {
@@ -858,7 +860,6 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
858860
this.createNewIncompletePhase(phaseConcept);
859861
}
860862

861-
let staticAnalysisCache: StaticAnalysisResponse | undefined;
862863
let userContext: UserContext | undefined;
863864

864865
// Store review cycles for later use
@@ -877,13 +878,11 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
877878
executionResults = await this.executePhaseGeneration();
878879
currentDevState = executionResults.currentDevState;
879880
phaseConcept = executionResults.result;
880-
staticAnalysisCache = executionResults.staticAnalysis;
881881
userContext = executionResults.userContext;
882882
break;
883883
case CurrentDevState.PHASE_IMPLEMENTING:
884-
executionResults = await this.executePhaseImplementation(phaseConcept, staticAnalysisCache, userContext);
884+
executionResults = await this.executePhaseImplementation(phaseConcept, userContext);
885885
currentDevState = executionResults.currentDevState;
886-
staticAnalysisCache = executionResults.staticAnalysis;
887886
userContext = undefined;
888887
break;
889888
case CurrentDevState.REVIEWING:
@@ -923,11 +922,11 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
923922
});
924923
}
925924
}
926-
925+
927926
/**
928927
* Execute phase generation state - generate next phase with user suggestions
929928
*/
930-
async executePhaseGeneration(): Promise<PhaseExecutionResult> {
929+
async executePhaseGeneration(isFinal?: boolean): Promise<PhaseExecutionResult> {
931930
this.logger().info("Executing PHASE_GENERATING state");
932931
try {
933932
const currentIssues = await this.fetchAllIssues();
@@ -958,7 +957,7 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
958957
}
959958
}
960959

961-
const nextPhase = await this.generateNextPhase(currentIssues, userContext);
960+
const nextPhase = await this.generateNextPhase(currentIssues, userContext, isFinal);
962961

963962
if (!nextPhase) {
964963
this.logger().info("No more phases to implement, transitioning to FINALIZING");
@@ -976,7 +975,6 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
976975
return {
977976
currentDevState: CurrentDevState.PHASE_IMPLEMENTING,
978977
result: nextPhase,
979-
staticAnalysis: currentIssues.staticAnalysis,
980978
userContext: userContext,
981979
};
982980
} catch (error) {
@@ -993,7 +991,7 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
993991
/**
994992
* Execute phase implementation state - implement current phase
995993
*/
996-
async executePhaseImplementation(phaseConcept?: PhaseConceptType, staticAnalysis?: StaticAnalysisResponse, userContext?: UserContext): Promise<{currentDevState: CurrentDevState, staticAnalysis?: StaticAnalysisResponse}> {
994+
async executePhaseImplementation(phaseConcept?: PhaseConceptType, userContext?: UserContext): Promise<{currentDevState: CurrentDevState, staticAnalysis?: StaticAnalysisResponse}> {
997995
try {
998996
this.logger().info("Executing PHASE_IMPLEMENTING state");
999997

@@ -1014,33 +1012,19 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
10141012
...this.state,
10151013
currentPhase: undefined // reset current phase
10161014
});
1017-
1018-
let currentIssues : AllIssues;
1019-
if (this.state.sandboxInstanceId) {
1020-
if (staticAnalysis) {
1021-
// If have cached static analysis, fetch everything else fresh
1022-
currentIssues = {
1023-
runtimeErrors: await this.fetchRuntimeErrors(true),
1024-
staticAnalysis: staticAnalysis,
1025-
};
1026-
} else {
1027-
currentIssues = await this.fetchAllIssues(true)
1028-
}
1029-
} else {
1030-
currentIssues = {
1031-
runtimeErrors: [],
1032-
staticAnalysis: { success: true, lint: { issues: [] }, typecheck: { issues: [] } },
1033-
}
1034-
}
1015+
1016+
// Prepare issues for implementation
1017+
const currentIssues = await this.fetchAllIssues(true);
1018+
10351019
// Implement the phase with user context (suggestions and images)
10361020
await this.implementPhase(phaseConcept, currentIssues, userContext);
10371021

10381022
this.logger().info(`Phase ${phaseConcept.name} completed, generating next phase`);
10391023

10401024
const phasesCounter = this.decrementPhasesCounter();
10411025

1042-
if ((phaseConcept.lastPhase || phasesCounter <= 0) && this.state.pendingUserInputs.length === 0) return {currentDevState: CurrentDevState.FINALIZING, staticAnalysis: staticAnalysis};
1043-
return {currentDevState: CurrentDevState.PHASE_GENERATING, staticAnalysis: staticAnalysis};
1026+
if ((phaseConcept.lastPhase || phasesCounter <= 0) && this.state.pendingUserInputs.length === 0) return {currentDevState: CurrentDevState.FINALIZING};
1027+
return {currentDevState: CurrentDevState.PHASE_GENERATING};
10441028
} catch (error) {
10451029
this.logger().error("Error implementing phase", error);
10461030
if (error instanceof RateLimitExceededError) {
@@ -1102,19 +1086,13 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
11021086
mvpGenerated: true
11031087
});
11041088

1105-
const phaseConcept: PhaseConceptType = {
1106-
name: "Finalization and Review",
1107-
description: "Full polishing and final review of the application",
1108-
files: [],
1109-
lastPhase: true
1089+
const { result: phaseConcept, userContext } = await this.executePhaseGeneration(true);
1090+
if (!phaseConcept) {
1091+
this.logger().warn("Phase concept not generated, skipping final review");
1092+
return CurrentDevState.REVIEWING;
11101093
}
11111094

1112-
this.createNewIncompletePhase(phaseConcept);
1113-
1114-
const currentIssues = await this.fetchAllIssues(true);
1115-
1116-
// Run final review and cleanup phase
1117-
await this.implementPhase(phaseConcept, currentIssues);
1095+
await this.executePhaseImplementation(phaseConcept, userContext);
11181096

11191097
const numFilesGenerated = this.fileManager.getGeneratedFilePaths().length;
11201098
this.logger().info(`Finalization complete. Generated ${numFilesGenerated}/${this.getTotalFiles()} files.`);
@@ -1179,11 +1157,14 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
11791157
/**
11801158
* Generate next phase with user context (suggestions and images)
11811159
*/
1182-
async generateNextPhase(currentIssues: AllIssues, userContext?: UserContext): Promise<PhaseConceptGenerationSchemaType | undefined> {
1160+
async generateNextPhase(currentIssues: AllIssues, userContext?: UserContext, isFinal?: boolean): Promise<PhaseConceptGenerationSchemaType | undefined> {
11831161
const issues = IssueReport.from(currentIssues);
11841162

11851163
// Build notification message
11861164
let notificationMsg = "Generating next phase";
1165+
if (isFinal) {
1166+
notificationMsg = "Generating final phase";
1167+
}
11871168
if (userContext?.suggestions && userContext.suggestions.length > 0) {
11881169
notificationMsg = `Generating next phase incorporating ${userContext.suggestions.length} user suggestion(s)`;
11891170
}
@@ -1203,6 +1184,7 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
12031184
issues,
12041185
userContext,
12051186
isUserSuggestedPhase: userContext?.suggestions && userContext.suggestions.length > 0 && this.state.mvpGenerated,
1187+
isFinal: isFinal ?? false,
12061188
},
12071189
this.getOperationOptions()
12081190
)
@@ -1495,7 +1477,13 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
14951477
*/
14961478
async runStaticAnalysisCode(files?: string[]): Promise<StaticAnalysisResponse> {
14971479
try {
1480+
// Check if we have cached static analysis
1481+
if (this.staticAnalysisCache) {
1482+
return this.staticAnalysisCache;
1483+
}
1484+
14981485
const analysisResponse = await this.deploymentManager.runStaticAnalysis(files);
1486+
this.staticAnalysisCache = analysisResponse;
14991487

15001488
const { lint, typecheck } = analysisResponse;
15011489
this.broadcast(WebSocketMessageResponses.STATIC_ANALYSIS_RESULTS, {
@@ -1616,6 +1604,9 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
16161604
}
16171605

16181606
async fetchAllIssues(resetIssues: boolean = false): Promise<AllIssues> {
1607+
if (!this.state.sandboxInstanceId) {
1608+
return { runtimeErrors: [], staticAnalysis: { success: false, lint: { issues: [], }, typecheck: { issues: [], } } };
1609+
}
16191610
const [runtimeErrors, staticAnalysis] = await Promise.all([
16201611
this.fetchRuntimeErrors(resetIssues),
16211612
this.runStaticAnalysisCode()
@@ -1850,6 +1841,9 @@ export class SimpleCodeGeneratorAgent extends Agent<Env, CodeGenState> {
18501841
}
18511842

18521843
async deployToSandbox(files: FileOutputType[] = [], redeploy: boolean = false, commitMessage?: string, clearLogs: boolean = false): Promise<PreviewType | null> {
1844+
// Invalidate static analysis cache
1845+
this.staticAnalysisCache = null;
1846+
18531847
// Call deployment manager with callbacks for broadcasting at the right times
18541848
const result = await this.deploymentManager.deployToSandbox(
18551849
files,

worker/agents/operations/PhaseGeneration.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface PhaseGenerationInputs {
1313
issues: IssueReport;
1414
userContext?: UserContext;
1515
isUserSuggestedPhase?: boolean;
16+
isFinal: boolean;
1617
}
1718

1819
const SYSTEM_PROMPT = `<ROLE>
@@ -139,6 +140,69 @@ Adhere to the following guidelines:
139140
140141
{{userSuggestions}}`;
141142

143+
const LAST_PHASE_PROMPT = `Finalization and Review phase.
144+
Goal: Thoroughly review the entire codebase generated in previous phases. Identify and fix any remaining critical issues (runtime errors, logic flaws, rendering bugs) before deployment.
145+
** YOU MUST HALT AFTER THIS PHASE **
146+
147+
<REVIEW FOCUS & METHODOLOGY>
148+
**Your primary goal is to find showstopper bugs and UI/UX problems. Prioritize:**
149+
1. **Runtime Errors & Crashes:** Any code that will obviously throw errors (Syntax errors, TDZ/Initialization errors, TypeErrors like reading property of undefined, incorrect API calls). **Analyze the provided \`errors\` carefully for root causes.**
150+
2. **Critical Logic Flaws:** Does the application logic *actually* implement the behavior described in the blueprint? (e.g., Simulate game moves mentally: Does moving left work? Does scoring update correctly? Are win/loss conditions accurate?).
151+
3. **UI Rendering Failures:** Will the UI render as expected? Check for:
152+
* **Layout Issues:** Misalignment, Incorrect borders/padding/margins etc, overlapping elements, incorrect spacing/padding, broken responsiveness (test mentally against mobile/tablet/desktop descriptions in blueprint).
153+
* **Styling Errors:** Missing or incorrect CSS classes, incorrect framework usage (e.g., wrong Tailwind class).
154+
* **Missing Elements:** Are all UI elements described in the blueprint present?
155+
4. **State Management Bugs:** Does state update correctly? Do UI updates reliably reflect state changes? Are there potential race conditions or infinite update loops?
156+
5. **Data Flow & Integration Errors:** Is data passed correctly between components? Do component interactions work as expected? Are imports valid and do the imported files/functions exist?
157+
6. **Event Handling:** Do buttons, forms, and other interactions trigger the correct logic specified in the blueprint?
158+
7. **Import/Dependency Issues:** Are all imports valid? Are there any missing or incorrectly referenced dependencies? Are they correct for the specific version installed?
159+
8. **Library version issues:** Are you sure the code written is compatible with the installed version of the library? (e.g., Tailwind v3 vs. v4)
160+
9. **Especially lookout for setState inside render or without dependencies**
161+
- Mentally simulate the linting rule \`react-hooks/exhaustive-deps\`.
162+
163+
**Method:**
164+
• Review file-by-file, considering its dependencies and dependents.
165+
• Mentally simulate user flows described in the blueprint.
166+
• Cross-reference implementation against the \`description\`, \`userFlow\`, \`components\`, \`dataFlow\`, and \`implementationDetails\` sections *constantly*.
167+
• Pay *extreme* attention to declaration order within scopes.
168+
• Check for any imports that are not defined, installed or are not in the template.
169+
• Come up with a the most important and urgent issues to fix first. We will run code reviews in multiple iterations, so focus on the most important issues first.
170+
171+
IF there are any runtime errors or linting errors provided, focus on fixing them first and foremost. No need to provide any minor fixes or improvements to the code. Just focus on fixing the errors.
172+
173+
</REVIEW FOCUS & METHODOLOGY>
174+
175+
<ISSUES TO REPORT (Answer these based on your review):>
176+
1. **Functionality Mismatch:** Does the codebase *fail* to deliver any core functionality described in the blueprint? (Yes/No + Specific examples)
177+
2. **Logic Errors:** Are there flaws in the application logic (state transitions, calculations, game rules, etc.) compared to the blueprint? (Yes/No + Specific examples)
178+
3. **Interaction Failures:** Do user interactions (clicks, inputs) behave incorrectly based on blueprint requirements? (Yes/No + Specific examples)
179+
4. **Data Flow Problems:** Is data not flowing correctly between components or managed incorrectly? (Yes/No + Specific examples)
180+
5. **State Management Issues:** Does state management lead to incorrect application behavior or UI? (Yes/No + Specific examples)
181+
6. **UI Rendering Bugs:** Are there specific rendering issues (layout, alignment, spacing, overlap, responsiveness)? (Yes/No + Specific examples of files/components and issues)
182+
7. **Performance Bottlenecks:** Are there obvious performance issues (e.g., inefficient loops, excessive re-renders)? (Yes/No + Specific examples)
183+
8. **UI/UX Quality:** Is the UI significantly different from the blueprint's description or generally poor/unusable (ignoring minor aesthetics)? (Yes/No + Specific examples)
184+
9. **Runtime Error Potential:** Identify specific code sections highly likely to cause runtime errors (TDZ, undefined properties, bad imports, syntax errors etc.). (Yes/No + Specific examples)
185+
10. **Dependency/Import Issues:** Are there any invalid imports or usage of non-existent/uninstalled dependencies? (Yes/No + Specific examples)
186+
187+
If issues pertain to just dependencies not being installed, please only suggest the necessary \`bun add\` commands to install them. Do not suggest file level fixes.
188+
</ISSUES TO REPORT (Answer these based on your review):>
189+
190+
**Regeneration Rules:**
191+
- Only regenerate files with **critical issues** causing runtime errors, significant logic flaws, or major rendering failures.
192+
- **Exception:** Small UI/CSS files *can* be regenerated for styling/alignment fixes if needed.
193+
- Do **not** regenerate for minor formatting or non-critical stylistic preferences.
194+
- Do **not** make major refactors or architectural changes.
195+
196+
<INSTRUCTIONS>
197+
Do not make major changes to the code. Just focus on fixing the critical runtime errors, issues and bugs in isolated and contained ways.
198+
</INSTRUCTIONS>
199+
200+
{{issues}}
201+
202+
{{userSuggestions}}
203+
204+
This phase prepares the code for final deployment.`;
205+
142206
const formatUserSuggestions = (suggestions?: string[] | null): string => {
143207
if (!suggestions || suggestions.length === 0) {
144208
return '';
@@ -173,8 +237,9 @@ ${serialized}`;
173237
return serialized;
174238
};
175239

176-
const userPromptFormatter = (issues: IssueReport, userSuggestions?: string[], isUserSuggestedPhase?: boolean) => {
177-
let prompt = NEXT_PHASE_USER_PROMPT
240+
const userPromptFormatter = (isFinal: Boolean, issues: IssueReport, userSuggestions?: string[], isUserSuggestedPhase?: boolean) => {
241+
let prompt = isFinal ? LAST_PHASE_PROMPT : NEXT_PHASE_USER_PROMPT;
242+
prompt = prompt
178243
.replaceAll('{{issues}}', issuesPromptFormatterWithGuidelines(issues))
179244
.replaceAll('{{userSuggestions}}', formatUserSuggestions(userSuggestions));
180245

@@ -191,7 +256,7 @@ export class PhaseGenerationOperation extends AgentOperation<PhaseGenerationInpu
191256
inputs: PhaseGenerationInputs,
192257
options: OperationOptions
193258
): Promise<PhaseConceptGenerationSchemaType> {
194-
const { issues, userContext, isUserSuggestedPhase } = inputs;
259+
const { issues, userContext, isUserSuggestedPhase, isFinal } = inputs;
195260
const { env, logger, context } = options;
196261
try {
197262
const suggestionsInfo = userContext?.suggestions && userContext.suggestions.length > 0
@@ -201,10 +266,10 @@ export class PhaseGenerationOperation extends AgentOperation<PhaseGenerationInpu
201266
? ` and ${userContext.images.length} image(s)`
202267
: "";
203268

204-
logger.info(`Generating next phase ${suggestionsInfo}${imagesInfo}`);
269+
logger.info(`Generating next phase ${suggestionsInfo}${imagesInfo} (isFinal: ${isFinal})`);
205270

206271
// Create user message with optional images
207-
const userPrompt = userPromptFormatter(issues, userContext?.suggestions, isUserSuggestedPhase);
272+
const userPrompt = userPromptFormatter(isFinal, issues, userContext?.suggestions, isUserSuggestedPhase);
208273
const userMessage = userContext?.images && userContext.images.length > 0
209274
? createMultiModalUserMessage(
210275
userPrompt,

0 commit comments

Comments
 (0)