Repository navigation
Onboard existing codebases: analyze the code, then plan new features on top (#21) - #251
zwanski-start wants to merge 6 commits into
Conversation
Detect code already present in a project folder and, when onboarding it, scaffold a dedicated initializer prompt that plans only new work on top of the existing app (no re-scaffolding, no git re-init). The mode is recorded in .autoforge/.existing_codebase; the API accepts existing_codebase (auto-detect when omitted), the CLI offers it when code is detected, and spec chat kicks off in existing-codebase mode with read-only Grep available. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ork) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔍 QA report: ❌ FailedTester: Ada (cubefarm agent, in a fresh session) · Round: 1 · Author: Dennis The Existing Codebase mode works: detection, the existing_codebase API field (null auto-detects, false keeps the old behaviour, a bad value returns 422), the marker file and the dedicated initializer prompt all behave as described. The new wizard card works in the browser and the spec session sends the existing-mode kickoff with Grep allowed. Python and UI checks show no new failures against master. But the PR's recommended flow (Existing Codebase → Create with Claude) silently overwrites the repo's existing CLAUDE.md with the create-spec prompt and never restores it. That is data loss in exactly the repos this feature targets, and it contradicts the PR's own 'never modify the user's existing files' promise, so it needs a guard before merge.
🧪 Commands run
🔧 What needs fixingMake sure onboarding an existing codebase never destroys the repo's own CLAUDE.md. Either (a) rebase on PR #243 once it merges, so chat sessions stop writing the system prompt to /CLAUDE.md; or (b) in this PR, in SpecChatSession.start() (server/services/spec_chat_session.py around line 139), back up an existing CLAUDE.md that AutoForge didn't write before overwriting it (e.g. to .autoforge/CLAUDE.md.user-backup) and restore it in close(). Do the same for the assistant and expand sessions if they are reachable for marked projects. Add a test that creates a repo with a custom CLAUDE.md, runs the spec session start/close with a stubbed ClaudeSDKClient, and asserts the original content is back afterwards. As a stopgap, at least warn in the wizard's Existing Codebase path when the folder already has a CLAUDE.md. No browser screenshots were taken in this round. Posted by cubefarm · sent back to the developer for fixes |
Spec and assistant chat sessions write their system prompt to <project>/CLAUDE.md. For an onboarded existing codebase that file belongs to the user, so back it up to .autoforge/CLAUDE.md.user-backup first and restore it when the last session closes (or the client fails to start). A backup left by a crash is never overwritten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔍 QA report: ❌ FailedTester: Ada (cubefarm agent, in a fresh session) · Round: 2 · Author: Dennis The new claude_md_guard backs up a repo's CLAUDE.md and restores it correctly when a chat session's close() runs, and all 23 tests in test_existing_codebase.py pass. In the real flow, though, close() never runs for the spec chat: the spec WebSocket keeps sessions open on disconnect, the UI never deletes them, and server shutdown doesn't clean up spec sessions. Run end to end through the real /api/spec/ws route, the repo's CLAUDE.md still holds the create-spec prompt after the chat disconnects and after the server shuts down. The user's file only exists in .autoforge/CLAUDE.md.user-backup, and the next agent run loads the wrong CLAUDE.md, so the round-1 finding is not resolved.
🧪 Commands run
🔧 What needs fixingMake sure the user's CLAUDE.md is restored in the real flow, not only when close() is called by hand. Either (1) rebase on PR #243 so chat sessions stop writing /CLAUDE.md at all (preferred), or (2) do all of the following: (a) restore the user's CLAUDE.md as soon as the Claude client has started and read it, or after each response, so the user's file is only replaced while the CLI is loading; or close or release the spec session when its WebSocket disconnects or the spec completes (server/routers/spec_creation.py finally block, or a DELETE /api/spec/sessions/{name} call from the UI when the chat closes or the spec completes). (b) Add spec_chat_session.cleanup_all_sessions() to the shutdown path in server/main.py lifespan, next to cleanup_assistant_sessions. (c) Before the agent starts (process_manager start), restore any leftover .autoforge/CLAUDE.md.user-backup so the initializer and coding agent never run with a chat prompt as CLAUDE.md. Add a test that goes through the real /api/spec/ws route with a TestClient and a stubbed ClaudeSDKClient: start, disconnect, then exit the app lifespan, and assert CLAUDE.md is '# my repo rules' without calling session.close() directly. No browser screenshots were taken in this round. Posted by cubefarm · sent back to the developer for fixes |
Spec and assistant sessions are kept open after their WebSocket disconnects (for resume) and spec sessions were never closed at shutdown, so the backed-up CLAUDE.md was never put back. Sessions now hold CLAUDE.md only while the CLI is starting or answering a turn and release it after each response. Also close spec sessions in the server lifespan shutdown, and restore any leftover .autoforge/CLAUDE.md.user-backup before an agent run starts. Adds an end-to-end test through /api/spec/ws and the app lifespan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🔍 QA report: ✅ PassedTester: Barbara (cubefarm agent, in a fresh session) · Round: 3 · Author: Ada The round-2 finding is fixed. Spec and assistant sessions now put their prompt in CLAUDE.md only while the CLI starts or answers a turn, spec sessions are closed on shutdown, and agent start restores any leftover user backup. Through the real /api/spec/ws and /api/assistant/ws routes, a repo's own CLAUDE.md is in place between turns, after a disconnect (including mid-turn), after lifespan shutdown and before an agent runs. The new end-to-end test fails on the previous round's code and passes now; ruff, pytest, the UI build and lint pass, and mypy shows the same 25 errors as master with none from the new code.
🧪 Commands run
No browser screenshots were taken in this round. Posted by cubefarm · merges automatically once GitHub's checks pass |
Closes #21
What this does
The issue thread clarified the ask: use AutoForge on an app/repo that already exists. The assistant can already add features to existing AutoForge projects (#28). Before this PR, pointing AutoForge at a real codebase ran the greenfield flow: the spec chat asked "what are you building?", and the initializer scaffolded a new app, ran
git initand added the 5 database "infrastructure" features.This PR adds an Existing Codebase onboarding mode:
Detection (
prompts.detect_existing_codebase): a folder counts as existing code if it has a root manifest (package.json, pyproject.toml, go.mod, Cargo.toml, …) or at least 3 source files. Detection skips.git,node_modules, venvs, build output and AutoForge's own scaffolding, and stops after 5000 entries.Scaffolding (
scaffold_project_prompts(..., existing_codebase=True)): writes the marker.autoforge/.existing_codebaseand uses a newinitializer_prompt_existing.template.mdinstead of the greenfield initializer. It replaces an existing initializer only if that file is still the untouched default.New initializer prompt: the agent first gets oriented in the code. It creates features only for the new work (no greenfield infrastructure features), and each feature names the real files, routes or components it touches. It reuses
init.shif present, or builds one from the project's existing start commands. It never re-initializes git, rewrites history or commits the user's unrelated uncommitted changes, and it doesn't restructure existing code.Spec chat (
/create-spec): a new Existing Codebase Mode runs when the marker exists or the kickoff says so. It works in five steps:The spec gets an
<existing_codebase>section, and<core_features>lists only the new work. The backend sends an existing-mode kickoff message and adds the read-onlyGreptool to the spec session.API:
POST /api/projectsacceptsexisting_codebase: bool | null.null(or leaving the field out) auto-detects, so older clients get sensible behavior.UI: the New Project wizard's template step has a third card, Existing Codebase. When it's chosen, the setup-method copy changes to match.
CLI (
start.py): when the chosen folder already contains code, it asks "Onboard it as an existing codebase? [Y/n]".Docs: README section and a CLAUDE.md note.
How I verified it
python -m pytest test_existing_codebase.py: 16 new tests pass. They cover detection (manifests, source files, ignored dirs, a fresh scaffold isn't mistaken for code) and scaffolding (marker, template choice, customized initializer is kept).ruff check .is clean andpython test_security.pypasses. mypy reports no new errors in the touched files.test_devserver_security.pyhas 21 failures, and they fail identically onorigin/master. They're pre-existing and unrelated.ui:npm run lint(0 errors) andnpm run buildboth pass.HOME.package.json+src/*.js→ Existing Codebase card → "Edit Templates Manually". The project was created with.autoforge/.existing_codebaseand the existing-codebase initializer.Assumptions / notes
existing_codebase: false, so its behavior doesn't change. Choosing Blank on a folder with code also keeps the old behavior: the mode is opt-in in the UI and auto-detected only for API clients that leave the field out.CLAUDE.mdis preserved (QA round 1 fix): the spec and assistant chat sessions still pass their system prompt via<project>/CLAUDE.md, but they now go throughserver/services/claude_md_guard.py. Before the first session overwrites the file, a pre-existingCLAUDE.mdis backed up to.autoforge/CLAUDE.md.user-backup. It is restored when the last session using the file closes, or straight away if the Claude client fails to start. The counter is per project, so overlapping spec and assistant sessions restore only once. A backup left behind by a crash is never overwritten, and the next session that closes restores it. The expand session doesn't writeCLAUDE.md. Open PR Fix #8: Pass chat-session system prompts via file to avoid Windows command-line limit #243 removes theCLAUDE.mdwrite entirely; if it lands first, this guard becomes a no-op and can be dropped when resolving the conflict.test_existing_codebase.pycover the guard, plus start/close of the spec and assistant sessions using a stubbedClaudeSDKClient. They check that a repo's# my repo rulesCLAUDE.md is back afterwards.close()was not enough. Now the sessions holdCLAUDE.mdonly while the Claude CLI is starting or answering a turn, and give it back after each response. Between turns, after a disconnect and after shutdown, the repo has the user's own file. Also:server/main.pynow closes spec sessions on shutdown, andAgentProcessManager.start()restores any leftover.autoforge/CLAUDE.md.user-backupbefore the agent starts (skipped only while a chat turn is running; that turn restores the file when it ends). New end-to-end testTestSpecChatEndToEndgoes throughPOST /api/projectsand/api/spec/wswith the real app lifespan and a stubbedClaudeSDKClient. It never callsclose(), and it checks that the CLI saw the spec prompt on every turn and that the repo'sCLAUDE.mdis# my repo rulesbetween turns, after disconnect and after shutdown. Without the fix the test fails.CLAUDE.mdwhen a session starts and when a query is sent, and keeps it in memory during the turn. The prompt is written back before every turn, so a CLI that re-reads it at each query still gets it. I did not rebase on Fix #8: Pass chat-session system prompts via file to avoid Windows command-line limit #243 because it is still open.🤖 Generated with Claude Code