Full Code of Pythagora-io/gpt-pilot for AI

main 53154df1c66b cached
510 files
1.4 MB
359.4k tokens
1255 symbols
1 requests
Download .txt
Showing preview only (1,608K chars total). Download the full file or copy to clipboard to get everything.
Repository: Pythagora-io/gpt-pilot
Branch: main
Commit: 53154df1c66b
Files: 510
Total size: 1.4 MB

Directory structure:
gitextract_kaxuv8xc/

├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── feature-request.yml
│   │   └── howto.yml
│   ├── copyright_template.txt
│   ├── ip_assignment.yml
│   └── workflows/
│       ├── ci.yml
│       └── cloud-staging-build.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── cloud/
│   ├── config-docker.json
│   ├── entrypoint.sh
│   ├── on-event-extension-install.sh
│   ├── posthog.html
│   ├── settings.json
│   └── setup-dependencies.sh
├── core/
│   ├── agents/
│   │   ├── __init__.py
│   │   ├── architect.py
│   │   ├── base.py
│   │   ├── bug_hunter.py
│   │   ├── code_monkey.py
│   │   ├── convo.py
│   │   ├── developer.py
│   │   ├── error_handler.py
│   │   ├── executor.py
│   │   ├── external_docs.py
│   │   ├── frontend.py
│   │   ├── git.py
│   │   ├── human_input.py
│   │   ├── importer.py
│   │   ├── legacy_handler.py
│   │   ├── mixins.py
│   │   ├── orchestrator.py
│   │   ├── problem_solver.py
│   │   ├── response.py
│   │   ├── spec_writer.py
│   │   ├── task_completer.py
│   │   ├── tech_lead.py
│   │   ├── tech_writer.py
│   │   ├── troubleshooter.py
│   │   └── wizard.py
│   ├── cli/
│   │   ├── __init__.py
│   │   ├── helpers.py
│   │   └── main.py
│   ├── config/
│   │   ├── __init__.py
│   │   ├── actions.py
│   │   ├── constants.py
│   │   ├── env_importer.py
│   │   ├── magic_words.py
│   │   ├── user_settings.py
│   │   └── version.py
│   ├── db/
│   │   ├── __init__.py
│   │   ├── alembic.ini
│   │   ├── fix_migrations.py
│   │   ├── migrations/
│   │   │   ├── README
│   │   │   ├── env.py
│   │   │   ├── script.py.mako
│   │   │   └── versions/
│   │   │       ├── 0173e14719aa_move_metadata_from_file_to_file_content_.py
│   │   │       ├── 0173e14719aa_vacuum_database.py
│   │   │       ├── 08d71952ec2f_refactor_specification_template_to_.py
│   │   │       ├── 0a1bb637fa26_initial.py
│   │   │       ├── 3968d770dced_add_project_type_to_project.py
│   │   │       ├── 675268601278_add_chat_messages_and_convos.py
│   │   │       ├── 69e50fdaf067_move_knowledge_base_to_separate_table.py
│   │   │       ├── b760f66138c0_add_docs_column_to_project_states.py
│   │   │       ├── c8905d4ce784_add_original_description_and_template_.py
│   │   │       ├── f352dbe45751_make_relevant_files_nullable.py
│   │   │       ├── f708791b9270_adding_knowledge_base_field_to_.py
│   │   │       └── ff891d366761_add_example_project_to_spec.py
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   ├── branch.py
│   │   │   ├── chat_convo.py
│   │   │   ├── chat_message.py
│   │   │   ├── exec_log.py
│   │   │   ├── file.py
│   │   │   ├── file_content.py
│   │   │   ├── knowledge_base.py
│   │   │   ├── llm_request.py
│   │   │   ├── project.py
│   │   │   ├── project_state.py
│   │   │   ├── specification.py
│   │   │   └── user_input.py
│   │   ├── session.py
│   │   ├── setup.py
│   │   └── v0importer.py
│   ├── disk/
│   │   ├── __init__.py
│   │   ├── ignore.py
│   │   └── vfs.py
│   ├── llm/
│   │   ├── __init__.py
│   │   ├── anthropic_client.py
│   │   ├── azure_client.py
│   │   ├── base.py
│   │   ├── convo.py
│   │   ├── groq_client.py
│   │   ├── openai_client.py
│   │   ├── parser.py
│   │   ├── prompt.py
│   │   ├── relace_client.py
│   │   └── request_log.py
│   ├── log/
│   │   └── __init__.py
│   ├── proc/
│   │   ├── __init__.py
│   │   ├── exec_log.py
│   │   └── process_manager.py
│   ├── prompts/
│   │   ├── architect/
│   │   │   ├── configure_template.prompt
│   │   │   ├── select_templates.prompt
│   │   │   ├── system.prompt
│   │   │   └── technologies.prompt
│   │   ├── bug-hunter/
│   │   │   ├── ask_a_question.prompt
│   │   │   ├── bug_found_or_add_logs.prompt
│   │   │   ├── data_about_logs.prompt
│   │   │   ├── get_bug_reproduction_instructions.prompt
│   │   │   ├── instructions_from_human_hint.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── log_data.prompt
│   │   │   ├── problem_explanation.prompt
│   │   │   ├── system.prompt
│   │   │   └── tell_me_more.prompt
│   │   ├── chat-agent/
│   │   │   ├── chat.prompt
│   │   │   └── system.prompt
│   │   ├── code-monkey/
│   │   │   ├── breakdown.prompt
│   │   │   ├── describe_file.prompt
│   │   │   ├── implement_changes.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── review_changes.prompt
│   │   │   ├── review_feedback.prompt
│   │   │   └── system.prompt
│   │   ├── developer/
│   │   │   ├── breakdown.prompt
│   │   │   ├── filter_files.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── parse_task.prompt
│   │   │   └── system.prompt
│   │   ├── error-handler/
│   │   │   └── debug.prompt
│   │   ├── executor/
│   │   │   └── ran_command.prompt
│   │   ├── external-docs/
│   │   │   ├── create_docs_queries.prompt
│   │   │   ├── select_docset.prompt
│   │   │   └── system.prompt
│   │   ├── frontend/
│   │   │   ├── build_frontend.prompt
│   │   │   ├── create_rag_query.prompt
│   │   │   ├── is_relevant_for_docs_search.prompt
│   │   │   ├── iterate_frontend.prompt
│   │   │   ├── remove_mock.prompt
│   │   │   ├── system.prompt
│   │   │   └── system_relace.prompt
│   │   ├── importer/
│   │   │   ├── analyze_project.prompt
│   │   │   └── get_entrypoints.prompt
│   │   ├── partials/
│   │   │   ├── breakdown_code_instructions.prompt
│   │   │   ├── coding_rules.prompt
│   │   │   ├── doc_snippets.prompt
│   │   │   ├── execution_order.prompt
│   │   │   ├── features_list.prompt
│   │   │   ├── file_naming.prompt
│   │   │   ├── file_size_limit.prompt
│   │   │   ├── files_descriptions.prompt
│   │   │   ├── files_list.prompt
│   │   │   ├── files_list_relevant.prompt
│   │   │   ├── human_intervention_explanation.prompt
│   │   │   ├── project_details.prompt
│   │   │   ├── project_tasks.prompt
│   │   │   ├── relative_paths.prompt
│   │   │   └── user_feedback.prompt
│   │   ├── problem-solver/
│   │   │   ├── get_alternative_solutions.prompt
│   │   │   ├── iteration.prompt
│   │   │   └── system.prompt
│   │   ├── pythagora/
│   │   │   └── commit.prompt
│   │   ├── spec-writer/
│   │   │   ├── add_new_feature.prompt
│   │   │   ├── add_to_specification.prompt
│   │   │   ├── ask_questions.prompt
│   │   │   ├── build_full_specification.prompt
│   │   │   ├── need_auth.prompt
│   │   │   ├── project_name.prompt
│   │   │   ├── prompt_complexity.prompt
│   │   │   ├── review_spec.prompt
│   │   │   └── system.prompt
│   │   ├── tech-lead/
│   │   │   ├── epic_breakdown.prompt
│   │   │   ├── filter_files.prompt
│   │   │   ├── plan.prompt
│   │   │   └── system.prompt
│   │   ├── tech-writer/
│   │   │   ├── create_readme.prompt
│   │   │   └── system.prompt
│   │   └── troubleshooter/
│   │       ├── breakdown.prompt
│   │       ├── bug_report.prompt
│   │       ├── define_user_review_goal.prompt
│   │       ├── filter_files.prompt
│   │       ├── get_route_files.prompt
│   │       ├── get_run_command.prompt
│   │       ├── iteration.prompt
│   │       └── system.prompt
│   ├── state/
│   │   ├── __init__.py
│   │   └── state_manager.py
│   ├── telemetry/
│   │   └── __init__.py
│   ├── templates/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── example_project.py
│   │   ├── info/
│   │   │   ├── javascript_react/
│   │   │   │   └── summary.tpl
│   │   │   ├── node_express_mongoose/
│   │   │   │   └── summary.tpl
│   │   │   ├── react_express/
│   │   │   │   └── summary.tpl
│   │   │   ├── vite_react/
│   │   │   │   └── summary.tpl
│   │   │   └── vite_react_swagger/
│   │   │       └── summary.tpl
│   │   ├── javascript_react.py
│   │   ├── node_express_mongoose.py
│   │   ├── react_express.py
│   │   ├── registry.py
│   │   ├── render.py
│   │   ├── tree/
│   │   │   ├── add_raw_tags.py
│   │   │   ├── javascript_react/
│   │   │   │   ├── .eslintrc.cjs
│   │   │   │   ├── .gitignore
│   │   │   │   ├── index.html
│   │   │   │   ├── package.json
│   │   │   │   ├── public/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── src/
│   │   │   │   │   ├── App.css
│   │   │   │   │   ├── App.jsx
│   │   │   │   │   ├── assets/
│   │   │   │   │   │   └── .gitkeep
│   │   │   │   │   ├── index.css
│   │   │   │   │   └── main.jsx
│   │   │   │   └── vite.config.js
│   │   │   ├── node_express_mongoose/
│   │   │   │   ├── models/
│   │   │   │   │   └── User.js
│   │   │   │   ├── package.json
│   │   │   │   ├── public/
│   │   │   │   │   ├── css/
│   │   │   │   │   │   └── style.css
│   │   │   │   │   └── js/
│   │   │   │   │       └── main.js
│   │   │   │   ├── routes/
│   │   │   │   │   ├── authRoutes.js
│   │   │   │   │   └── middleware/
│   │   │   │   │       └── authMiddleware.js
│   │   │   │   ├── server.js
│   │   │   │   ├── services/
│   │   │   │   │   └── llm.js
│   │   │   │   └── views/
│   │   │   │       ├── index.ejs
│   │   │   │       ├── login.ejs
│   │   │   │       ├── partials/
│   │   │   │       │   ├── _footer.ejs
│   │   │   │       │   ├── _head.ejs
│   │   │   │       │   └── _header.ejs
│   │   │   │       └── register.ejs
│   │   │   ├── react_express/
│   │   │   │   ├── .babelrc
│   │   │   │   ├── .eslintrc.json
│   │   │   │   ├── .gitignore
│   │   │   │   ├── README.md
│   │   │   │   ├── api/
│   │   │   │   │   ├── app.js
│   │   │   │   │   ├── middlewares/
│   │   │   │   │   │   ├── authMiddleware.js
│   │   │   │   │   │   └── errorMiddleware.js
│   │   │   │   │   ├── models/
│   │   │   │   │   │   ├── init.js
│   │   │   │   │   │   └── user.js
│   │   │   │   │   ├── routes/
│   │   │   │   │   │   ├── authRoutes.js
│   │   │   │   │   │   └── index.js
│   │   │   │   │   ├── services/
│   │   │   │   │   │   └── userService.js
│   │   │   │   │   └── utils/
│   │   │   │   │       ├── log.js
│   │   │   │   │       ├── mail.js
│   │   │   │   │       └── password.js
│   │   │   │   ├── components.json
│   │   │   │   ├── index.html
│   │   │   │   ├── jsconfig.json
│   │   │   │   ├── package.json
│   │   │   │   ├── postcss.config.js
│   │   │   │   ├── prisma/
│   │   │   │   │   └── schema.prisma
│   │   │   │   ├── public/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── server.js
│   │   │   │   ├── tailwind.config.js
│   │   │   │   ├── tsconfig.json
│   │   │   │   ├── ui/
│   │   │   │   │   ├── assets/
│   │   │   │   │   │   └── .gitkeep
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── ui/
│   │   │   │   │   │       ├── alert.jsx
│   │   │   │   │   │       ├── button.jsx
│   │   │   │   │   │       ├── card.jsx
│   │   │   │   │   │       ├── input.jsx
│   │   │   │   │   │       └── label.jsx
│   │   │   │   │   ├── index.css
│   │   │   │   │   ├── lib/
│   │   │   │   │   │   └── utils.js
│   │   │   │   │   ├── main.jsx
│   │   │   │   │   └── pages/
│   │   │   │   │       ├── Home.css
│   │   │   │   │       ├── Home.jsx
│   │   │   │   │       ├── Login.jsx
│   │   │   │   │       └── Register.jsx
│   │   │   │   └── vite.config.js
│   │   │   ├── vite_react/
│   │   │   │   ├── .gitignore
│   │   │   │   ├── client/
│   │   │   │   │   ├── components.json
│   │   │   │   │   ├── eslint.config.js
│   │   │   │   │   ├── index.html
│   │   │   │   │   ├── package.json
│   │   │   │   │   ├── postcss.config.js
│   │   │   │   │   ├── src/
│   │   │   │   │   │   ├── App.css
│   │   │   │   │   │   ├── App.tsx
│   │   │   │   │   │   ├── api/
│   │   │   │   │   │   │   ├── api.ts
│   │   │   │   │   │   │   └── auth.ts
│   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   ├── Footer.tsx
│   │   │   │   │   │   │   ├── Header.tsx
│   │   │   │   │   │   │   ├── Layout.tsx
│   │   │   │   │   │   │   ├── ProtectedRoute.tsx
│   │   │   │   │   │   │   └── ui/
│   │   │   │   │   │   │       ├── accordion.tsx
│   │   │   │   │   │   │       ├── alert-dialog.tsx
│   │   │   │   │   │   │       ├── alert.tsx
│   │   │   │   │   │   │       ├── aspect-ratio.tsx
│   │   │   │   │   │   │       ├── avatar.tsx
│   │   │   │   │   │   │       ├── badge.tsx
│   │   │   │   │   │   │       ├── breadcrumb.tsx
│   │   │   │   │   │   │       ├── button.tsx
│   │   │   │   │   │   │       ├── calendar.tsx
│   │   │   │   │   │   │       ├── card.tsx
│   │   │   │   │   │   │       ├── carousel.tsx
│   │   │   │   │   │   │       ├── chart.tsx
│   │   │   │   │   │   │       ├── checkbox.tsx
│   │   │   │   │   │   │       ├── collapsible.tsx
│   │   │   │   │   │   │       ├── command.tsx
│   │   │   │   │   │   │       ├── context-menu.tsx
│   │   │   │   │   │   │       ├── dialog.tsx
│   │   │   │   │   │   │       ├── drawer.tsx
│   │   │   │   │   │   │       ├── dropdown-menu.tsx
│   │   │   │   │   │   │       ├── form.tsx
│   │   │   │   │   │   │       ├── hover-card.tsx
│   │   │   │   │   │   │       ├── input-otp.tsx
│   │   │   │   │   │   │       ├── input.tsx
│   │   │   │   │   │   │       ├── label.tsx
│   │   │   │   │   │   │       ├── menubar.tsx
│   │   │   │   │   │   │       ├── navigation-menu.tsx
│   │   │   │   │   │   │       ├── pagination.tsx
│   │   │   │   │   │   │       ├── popover.tsx
│   │   │   │   │   │   │       ├── progress.tsx
│   │   │   │   │   │   │       ├── radio-group.tsx
│   │   │   │   │   │   │       ├── resizable.tsx
│   │   │   │   │   │   │       ├── scroll-area.tsx
│   │   │   │   │   │   │       ├── select.tsx
│   │   │   │   │   │   │       ├── separator.tsx
│   │   │   │   │   │   │       ├── sheet.tsx
│   │   │   │   │   │   │       ├── sidebar.tsx
│   │   │   │   │   │   │       ├── skeleton.tsx
│   │   │   │   │   │   │       ├── slider.tsx
│   │   │   │   │   │   │       ├── sonner.tsx
│   │   │   │   │   │   │       ├── switch.tsx
│   │   │   │   │   │   │       ├── table.tsx
│   │   │   │   │   │   │       ├── tabs.tsx
│   │   │   │   │   │   │       ├── textarea.tsx
│   │   │   │   │   │   │       ├── theme-provider.tsx
│   │   │   │   │   │   │       ├── theme-toggle.tsx
│   │   │   │   │   │   │       ├── toast.tsx
│   │   │   │   │   │   │       ├── toaster.tsx
│   │   │   │   │   │   │       ├── toggle-group.tsx
│   │   │   │   │   │   │       ├── toggle.tsx
│   │   │   │   │   │   │       └── tooltip.tsx
│   │   │   │   │   │   ├── config/
│   │   │   │   │   │   │   └── constants.ts
│   │   │   │   │   │   ├── contexts/
│   │   │   │   │   │   │   └── AuthContext.tsx
│   │   │   │   │   │   ├── hooks/
│   │   │   │   │   │   │   ├── useMobile.tsx
│   │   │   │   │   │   │   └── useToast.ts
│   │   │   │   │   │   ├── index.css
│   │   │   │   │   │   ├── lib/
│   │   │   │   │   │   │   └── utils.ts
│   │   │   │   │   │   ├── main.tsx
│   │   │   │   │   │   ├── pages/
│   │   │   │   │   │   │   ├── BlankPage.tsx
│   │   │   │   │   │   │   ├── Login.tsx
│   │   │   │   │   │   │   └── Register.tsx
│   │   │   │   │   │   └── vite-env.d.ts
│   │   │   │   │   ├── tailwind.config.js
│   │   │   │   │   ├── tsconfig.app.json
│   │   │   │   │   ├── tsconfig.json
│   │   │   │   │   ├── tsconfig.node.json
│   │   │   │   │   └── vite.config.ts
│   │   │   │   ├── package.json
│   │   │   │   └── server/
│   │   │   │       ├── config/
│   │   │   │       │   └── database.js
│   │   │   │       ├── models/
│   │   │   │       │   ├── User.js
│   │   │   │       │   └── init.js
│   │   │   │       ├── package.json
│   │   │   │       ├── routes/
│   │   │   │       │   ├── authRoutes.js
│   │   │   │       │   ├── index.js
│   │   │   │       │   └── middleware/
│   │   │   │       │       └── auth.js
│   │   │   │       ├── server.js
│   │   │   │       ├── services/
│   │   │   │       │   ├── llmService.js
│   │   │   │       │   └── userService.js
│   │   │   │       └── utils/
│   │   │   │           ├── auth.js
│   │   │   │           └── password.js
│   │   │   └── vite_react_swagger/
│   │   │       ├── .gitignore
│   │   │       ├── client/
│   │   │       │   ├── components.json
│   │   │       │   ├── eslint.config.js
│   │   │       │   ├── index.html
│   │   │       │   ├── package.json
│   │   │       │   ├── postcss.config.js
│   │   │       │   ├── src/
│   │   │       │   │   ├── App.css
│   │   │       │   │   ├── App.tsx
│   │   │       │   │   ├── api/
│   │   │       │   │   │   ├── api.ts
│   │   │       │   │   │   └── auth.ts
│   │   │       │   │   ├── components/
│   │   │       │   │   │   ├── Footer.tsx
│   │   │       │   │   │   ├── Header.tsx
│   │   │       │   │   │   ├── Layout.tsx
│   │   │       │   │   │   ├── ProtectedRoute.tsx
│   │   │       │   │   │   └── ui/
│   │   │       │   │   │       ├── accordion.tsx
│   │   │       │   │   │       ├── alert-dialog.tsx
│   │   │       │   │   │       ├── alert.tsx
│   │   │       │   │   │       ├── aspect-ratio.tsx
│   │   │       │   │   │       ├── avatar.tsx
│   │   │       │   │   │       ├── badge.tsx
│   │   │       │   │   │       ├── breadcrumb.tsx
│   │   │       │   │   │       ├── button.tsx
│   │   │       │   │   │       ├── calendar.tsx
│   │   │       │   │   │       ├── card.tsx
│   │   │       │   │   │       ├── carousel.tsx
│   │   │       │   │   │       ├── chart.tsx
│   │   │       │   │   │       ├── checkbox.tsx
│   │   │       │   │   │       ├── collapsible.tsx
│   │   │       │   │   │       ├── command.tsx
│   │   │       │   │   │       ├── context-menu.tsx
│   │   │       │   │   │       ├── dialog.tsx
│   │   │       │   │   │       ├── drawer.tsx
│   │   │       │   │   │       ├── dropdown-menu.tsx
│   │   │       │   │   │       ├── form.tsx
│   │   │       │   │   │       ├── hover-card.tsx
│   │   │       │   │   │       ├── input-otp.tsx
│   │   │       │   │   │       ├── input.tsx
│   │   │       │   │   │       ├── label.tsx
│   │   │       │   │   │       ├── menubar.tsx
│   │   │       │   │   │       ├── navigation-menu.tsx
│   │   │       │   │   │       ├── pagination.tsx
│   │   │       │   │   │       ├── popover.tsx
│   │   │       │   │   │       ├── progress.tsx
│   │   │       │   │   │       ├── radio-group.tsx
│   │   │       │   │   │       ├── resizable.tsx
│   │   │       │   │   │       ├── scroll-area.tsx
│   │   │       │   │   │       ├── select.tsx
│   │   │       │   │   │       ├── separator.tsx
│   │   │       │   │   │       ├── sheet.tsx
│   │   │       │   │   │       ├── sidebar.tsx
│   │   │       │   │   │       ├── skeleton.tsx
│   │   │       │   │   │       ├── slider.tsx
│   │   │       │   │   │       ├── sonner.tsx
│   │   │       │   │   │       ├── switch.tsx
│   │   │       │   │   │       ├── table.tsx
│   │   │       │   │   │       ├── tabs.tsx
│   │   │       │   │   │       ├── textarea.tsx
│   │   │       │   │   │       ├── theme-provider.tsx
│   │   │       │   │   │       ├── theme-toggle.tsx
│   │   │       │   │   │       ├── toast.tsx
│   │   │       │   │   │       ├── toaster.tsx
│   │   │       │   │   │       ├── toggle-group.tsx
│   │   │       │   │   │       ├── toggle.tsx
│   │   │       │   │   │       └── tooltip.tsx
│   │   │       │   │   ├── config/
│   │   │       │   │   │   └── constants.ts
│   │   │       │   │   ├── contexts/
│   │   │       │   │   │   └── AuthContext.tsx
│   │   │       │   │   ├── hooks/
│   │   │       │   │   │   ├── useMobile.tsx
│   │   │       │   │   │   └── useToast.ts
│   │   │       │   │   ├── index.css
│   │   │       │   │   ├── lib/
│   │   │       │   │   │   └── utils.ts
│   │   │       │   │   ├── main.tsx
│   │   │       │   │   ├── pages/
│   │   │       │   │   │   ├── BlankPage.tsx
│   │   │       │   │   │   ├── Login.tsx
│   │   │       │   │   │   └── Register.tsx
│   │   │       │   │   └── vite-env.d.ts
│   │   │       │   ├── tailwind.config.js
│   │   │       │   ├── tsconfig.app.json
│   │   │       │   ├── tsconfig.json
│   │   │       │   ├── tsconfig.node.json
│   │   │       │   └── vite.config.ts
│   │   │       └── package.json
│   │   ├── vite_react.py
│   │   └── vite_react_swagger.py
│   ├── ui/
│   │   ├── api_server.py
│   │   ├── base.py
│   │   ├── console.py
│   │   ├── ipc_client.py
│   │   └── virtual.py
│   └── utils/
│       ├── __init__.py
│       └── text.py
├── docs/
│   └── TELEMETRY.md
├── example-config.json
├── main.py
├── pyproject.toml
├── requirements.txt
└── tests/
    ├── __init__.py
    ├── agents/
    │   ├── __init__.py
    │   ├── test_base.py
    │   ├── test_convo.py
    │   ├── test_external_docs.py
    │   ├── test_orchestrator.py
    │   └── test_tech_lead.py
    ├── cli/
    │   ├── __init__.py
    │   └── test_cli.py
    ├── config/
    │   ├── __init__.py
    │   ├── test_config.py
    │   ├── test_env_importer.py
    │   ├── test_version.py
    │   └── testconfig.json
    ├── conftest.py
    ├── db/
    │   ├── __init__.py
    │   ├── factories.py
    │   ├── test_branch.py
    │   ├── test_db.py
    │   ├── test_project.py
    │   └── test_project_state.py
    ├── disk/
    │   ├── __init__.py
    │   ├── test_ignore.py
    │   └── test_vfs.py
    ├── integration/
    │   ├── __init__.py
    │   └── llm/
    │       ├── __init__.py
    │       ├── test_anthropic.py
    │       ├── test_groq.py
    │       └── test_openai.py
    ├── llm/
    │   ├── __init__.py
    │   ├── prompts/
    │   │   └── test.txt
    │   ├── test_convo.py
    │   ├── test_openai.py
    │   ├── test_parser.py
    │   └── test_prompt.py
    ├── log/
    │   ├── __init__.py
    │   └── test_log.py
    ├── proc/
    │   ├── __init__.py
    │   └── test_process_manager.py
    ├── state/
    │   ├── __init__.py
    │   └── test_state_manager.py
    ├── telemetry/
    │   └── test_telemetry.py
    ├── templates/
    │   └── test_templates.py
    └── ui/
        ├── __init__.py
        ├── test_console.py
        └── test_ipc_client.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
vsc-dl-x64 export-ignore


================================================
FILE: .github/CODEOWNERS
================================================
# File: CODEOWNERS
* @Pythagora-io/maintainers

# File: .github/ip_assignment.yml
assign_to_owner: true
license: FSL-1.1-MIT

# File: .github/copyright_template.txt
Copyright (c) 2024 Pythagora Technologies Inc. All rights reserved.
This project is licensed under the terms of the FSL-1.1-MIT license.

# File: LICENSE
FSL-1.1-MIT License

Copyright (c) 2024 Pythagora Technologies Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

# Functional Source License, Version 1.1, MIT Future License

## Abbreviation

FSL-1.1-MIT

## Notice

Copyright 2024 Pythagora Technologies, Inc.

## Terms and Conditions

### Licensor ("We")

The party offering the Software under these Terms and Conditions.

### The Software

The "Software" is each version of the software that we make available under
these Terms and Conditions, as indicated by our inclusion of these Terms and
Conditions with the Software.

### License Grant

Subject to your compliance with this License Grant and the Patents,
Redistribution and Trademark clauses below, we hereby grant you the right to
use, copy, modify, create derivative works, publicly perform, publicly display
and redistribute the Software for any Permitted Purpose identified below.

### Permitted Purpose

A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
means making the Software available to others in a commercial product or
service that:

1. substitutes for the Software;

2. substitutes for any other product or service we offer using the Software
   that exists as of the date we make the Software available; or

3. offers the same or substantially similar functionality as the Software.

Permitted Purposes specifically include using the Software:

1. for your internal use and access;

2. for non-commercial education;

3. for non-commercial research; and

4. in connection with professional services that you provide to a licensee
   using the Software in accordance with these Terms and Conditions.

### Patents

To the extent your use for a Permitted Purpose would necessarily infringe our
patents, the license grant above includes a license under our patents. If you
make a claim against any party that the Software infringes or contributes to
the infringement of any patent, then your patent license to the Software ends
immediately.

### Redistribution

The Terms and Conditions apply to all copies, modifications and derivatives of
the Software.

If you redistribute any copies, modifications or derivatives of the Software,
you must include a copy of or a link to these Terms and Conditions and not
remove any copyright notices provided in or with the Software.

### Disclaimer

THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.

IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.

### Trademarks

Except for displaying the License Details and identifying us as the origin of
the Software, you have no right under these Terms and Conditions to use our
trademarks, trade names, service marks or product names.

## Grant of Future License

We hereby irrevocably grant you an additional license to use the Software under
the MIT license that is effective on the second anniversary of the date we make
the Software available. On or after that date, you may use the Software under
the MIT license, in which case the following will apply:

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness towards other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
  overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or
  advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
  address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series
of actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior,  harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within
the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: .github/CONTRIBUTING.md
================================================


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yml
================================================
name: Bug Report
description: File a bug report
title: "[Bug]: "
labels:
  - bug
body:
  - type: markdown
    attributes:
      value: |
        Thank you for taking the time to fill out this bug report!
        
        If you haven't already, please also check our [Frequently Asked Questions](https://github.com/Pythagora-io/gpt-pilot/wiki/Frequently-Asked-Questions) and [currently open issues](https://github.com/Pythagora-io/gpt-pilot/issues/) to see if the problem you have is already mentioned. If so, please comment on the the existing issue instead of creating a new one.
        
        We also have an [active community on Discord](https://discord.gg/HaqXugmxr9). If the issue is more about how to do something with GPT Pilot and not about a bug or problem, consider joining our Discord and discussing there, as it's possible other community members could help you!
  - type: dropdown
    id: client
    attributes:
      label: Version
      description: Which version of GPT Pilot are you using?
      options:
        - VisualStudio Code extension
        - Command-line (Python) version
    validations:
      required: true
  - type: dropdown
    id: os
    attributes:
      label: Operating System
      description: Which operating system are you using?
      options:
        - Windows 10
        - Windows 11
        - MacOS
        - Ubuntu Linux
        - Linux (other)
    validations:
      required: true
  - type: textarea
    id: what-happened
    attributes:
      label: What happened?
      description: Please describe the problem in as much detail as possible. If you have screenshots or screen recordings, please add them - they will help us figure out what's going on.
      placeholder: "When I do [something], [a strange thing or a bug] happens instead of [what I expected]."
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yml
================================================
name: Feature request
description: Suggest a feature or improvement
title: "[Enhancement]: "
labels:
  - enhancement
body:
  - type: markdown
    attributes:
      value: |
        Thank you for taking the time to suggest improvement to GPT Pilot!
        
        If you haven't already, please check our [Frequently Asked Questions](https://github.com/Pythagora-io/gpt-pilot/wiki/Frequently-Asked-Questions) and [currently open issues](https://github.com/Pythagora-io/gpt-pilot/issues/) to see if this is a commonly-asked feature. If so, please comment on the existing issue instead of creating a new one.
        
        We also have an [active community on Discord](https://discord.gg/HaqXugmxr9), so please consider joining, sharing your idea and starting a discussion there.
  - type: dropdown
    id: client
    attributes:
      label: Version
      description: Which version of GPT Pilot does this apply?
      options:
        - VisualStudio Code extension
        - Command-line (Python) version
    validations:
      required: true
  - type: textarea
    id: idea
    attributes:
      label: Suggestion
      description: Please describe your suggestion for the improvement or new feature here.
      placeholder: "It would be cool if GPT Pilot could do [something]."
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/howto.yml
================================================
name: How do I...?
description: Ask for help if you're stuck
title: "[Howto]: "
labels:
  - question
body:
  - type: markdown
    attributes:
      value: |
        Thanks for trying out GPT Pilot!        
        
        First, [check out our Frequently Asked Questions](https://github.com/Pythagora-io/gpt-pilot/wiki/Frequently-Asked-Questions) and our [video tutorials](https://www.youtube.com/watch?v=nlI7a20RPoE&list=PLbi3WiEeXr2wdppQ575zSLbcORou-Lxd1&index=2). There's a high chance your question has already been answered there.

        If not, it's best to ask the question [in our community Discord](https://discord.gg/HaqXugmxr9). We have an active and friendly community and you will likely get an answer quicker than if you post here. You can also check our [open issues](https://github.com/Pythagora-io/gpt-pilot/issues/) and see if what you're asking about was already discussed there.

        BTW: If you're wondering how to use GPT Pilot with a local LLM, please check our tutorial here: [Using GPT Pilot with Local LLMs](https://github.com/Pythagora-io/gpt-pilot/wiki/Using-GPT%E2%80%90Pilot-with-Local-LLMs).
  - type: dropdown
    id: client
    attributes:
      label: Version
      description: Which version of GPT Pilot are you using?
      options:
        - VisualStudio Code extension
        - Command-line (Python) version
    validations:
      required: true
  - type: dropdown
    id: os
    attributes:
      label: Operating System
      description: Which operating system are you using?
      options:
        - Windows 10
        - Windows 11
        - MacOS
        - Ubuntu Linux
        - Linux (other)
    validations:
      required: true
  - type: textarea
    id: question
    attributes:
      label: Your question
      description: Please describe your question in as much detail as possible.
      placeholder: "How can I do [something] with GPT Pilot? I've checked the FAQ, video tutorials, open issues and Discord and there doesn't seem to be an answer to this."
    validations:
      required: true


================================================
FILE: .github/copyright_template.txt
================================================
Copyright (c) 2024 Pythagora Technologies Inc. All rights reserved.
This project is licensed under the terms of the FSL-1.1-MIT license.


================================================
FILE: .github/ip_assignment.yml
================================================
assign_to_owner: true
license: FSL-1.1-MIT


================================================
FILE: .github/workflows/ci.yml
================================================
name: Run unit tests

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main", "rewrite" ]

jobs:
  build:

    runs-on: ${{ matrix.os }}
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.9", "3.12"]
        os: [ubuntu-latest, macos-latest, windows-latest]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install poetry
        poetry install --with=dev
    - name: Lint with ruff
      run: poetry run ruff check --output-format github
    - name: Check code style with ruff
      run: poetry run ruff format --check --diff
    - name: Test with pytest
      run: poetry run pytest


================================================
FILE: .github/workflows/cloud-staging-build.yaml
================================================
name: Staging Cloud Build and Deploy

on:
  workflow_dispatch:
    inputs:
      extension_commit_sha:
        description: "Extension commit SHA (defaults to latest commit on selected branch)"
        required: false
        type: string
        default: ""
      extension_branch:
        description: "Extension branch to use"
        required: false
        type: string
        default: "main"
      core_commit_sha:
        description: "Core commit SHA (defaults to latest commit on selected branch)"
        required: false
        type: string
        default: ""
      core_branch:
        description: "Core branch to use"
        required: false
        type: string
        default: "main"

permissions:
 contents: write

jobs:
  get-versions:
    runs-on: ubuntu-latest
    outputs:
      extension_sha: ${{ steps.get-extension-sha.outputs.extension_sha }}
      core_sha: ${{ steps.get-core-sha.outputs.core_sha }}
      combined_tag: ${{ steps.generate-tag.outputs.combined_tag }}

    steps:
      - name: Checkout Extension
        uses: actions/checkout@v4
        with:
          repository: Pythagora-io/gpt-pilot-vs-code-extension
          token: ${{ secrets.GH_PAT }} 
          path: tmp_extension
          ref: ${{ inputs.extension_commit_sha || inputs.extension_branch }} 
          fetch-depth: 0

      - name: Get extension SHA
        id: get-extension-sha
        run: |
          cd tmp_extension
          SHA=$(git rev-parse HEAD)
          echo "Extension SHA: $SHA"
          echo "extension_sha=$SHA" >> $GITHUB_OUTPUT
          cd ..
    
          
      - name: Checkout Core
        uses: actions/checkout@v4
        with:
          repository: Pythagora-io/pythagora-v1
          token: ${{ secrets.GH_PAT }}
          path: tmp_core
          ref: ${{ inputs.core_commit_sha || inputs.core_branch }}
          fetch-depth: 0
      
      - name: Get core SHA
        id: get-core-sha
        run: |
          cd tmp_core
          SHA=$(git rev-parse HEAD)
          echo "Core SHA: $SHA"
          echo "core_sha=$SHA" >> $GITHUB_OUTPUT
          cd ..
    
      - name: Generate combined image tag
        id: generate-tag
        run: |
          EXT_SHORT=$(echo "${{ steps.get-extension-sha.outputs.extension_sha }}" | cut -c1-7)
          CORE_SHORT=$(echo "${{ steps.get-core-sha.outputs.core_sha }}" | cut -c1-7)
          TAG="ext-${EXT_SHORT}_core-${CORE_SHORT}"
          echo "Combined tag: $TAG"
          echo "combined_tag=$TAG" >> $GITHUB_OUTPUT

  deploy:
    needs: get-versions
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout Extension
        uses: actions/checkout@v4
        with:
          repository: Pythagora-io/gpt-pilot-vs-code-extension
          token: ${{ secrets.GH_PAT }} 
          path: tmp_extension
          ref: ${{ needs.get-versions.outputs.extension_sha }}  # Use SHA instead of version
          fetch-depth: 0
          
      - name: Checkout Core
        uses: actions/checkout@v4
        with:
          repository: Pythagora-io/pythagora-v1
          token: ${{ secrets.GH_PAT }}
          path: tmp_core
          ref: ${{ needs.get-versions.outputs.core_sha }}
          fetch-depth: 0
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.12'  

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Setup extension
        run: |
          cd tmp_extension
          npm install -g @vscode/vsce@3.3.2
          npm run build
          vsce package
          cd ..

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v3
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1 
  
      - name: Login to ECR
        uses: docker/login-action@v3
        with:
          registry: ${{ secrets.ECR_URL }}
          username: ${{ vars.AWS_ACCESS_KEY_ID }}
          password: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      - name: Docker build and push
        run: |
          cd tmp_core
          cp ../tmp_extension/*.vsix ./pythagora-vs-code.vsix
          docker build --platform=linux/amd64 -f Dockerfile -t ${{ secrets.ECR_URL }}/pythagora/workspace:${{ needs.get-versions.outputs.combined_tag }} .
          docker push ${{ secrets.ECR_URL }}/pythagora/workspace:${{ needs.get-versions.outputs.combined_tag }}
          cd ..

      - name: Checkout Workspace Helm
        uses: actions/checkout@v4
        with:
          repository: Pythagora-io/workspace-helm
          token: ${{ secrets.GH_PAT }}
          path: tmp_wh
          ref: 'staging'
          fetch-depth: 0

      - name: Install yq
        run: |
          wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq
          chmod +x /usr/local/bin/yq
  
      - name: Commit and push changes
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          cd tmp_wh
          new_value="${{ needs.get-versions.outputs.combined_tag }}"
          file_path="chart/values.yaml"
          current_value=$(yq '.workspace.image.tag' "$file_path")

          
          if [ "$current_value" = "$new_value" ]; then
          echo "No changes to commit, skipping..."
          else
            git config --global user.name "github-actions[bot]"
            git config --global user.email "github-actions[bot]@users.noreply.github.com"
            yq -i ".workspace.image.tag = \"${new_value}\"" "$file_path"
            git add $file_path
            VERSION=${{ needs.get-versions.outputs.combined_tag }}
            git commit -m "version $VERSION"
            git push origin staging
          fi
          cd ..


================================================
FILE: .gitignore
================================================
__pycache__/
.venv/
venv/
.vscode/
.idea/
htmlcov/
dist/
workspace/
pilot-env/
venv/
data/

.coverage
*.code-workspace
.*_cache
.env
*.pyc
*.db
*.db-shm
*.db-wal
config.json
poetry.lock
.DS_Store
*.log
pythagora-vs-code.vsix


================================================
FILE: .pre-commit-config.yaml
================================================
fail_fast: true
repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    # Ruff version.
    rev: v0.3.5
    hooks:
      # Run the linter.
      - id: ruff
        args: [ --fix ]
      # Run the formatter.
      - id: ruff-format
  - repo: local
    hooks:
      # Check there are no migrations missing
      - id: alembic
        name: alembic
        stages: [commit]
        types: [python]
        entry: alembic -c core/db/alembic.ini check
        language: system
        pass_filenames: false
  - repo: local
    hooks:
      # Run the tests
      - id: pytest
        name: pytest
        stages: [commit]
        types: [python]
        entry: pytest
        language: system
        pass_filenames: false


================================================
FILE: CHANGELOG.md
================================================
#  (2025-08-25)


### Reverts

* Revert "Implemented weekend discount" ([734e0c7](https://github.com/Pythagora-io/pythagora-v1/commit/734e0c726b179a45f235a0fd230a6310c77ae740))





================================================
FILE: Dockerfile
================================================
# Use Ubuntu 22.04 as the base image with multi-arch support
FROM ubuntu:22.04

# Use buildx args for multi-arch support
ARG TARGETPLATFORM
ARG BUILDPLATFORM

# Set defaults for TARGETPLATFORM to ensure it's available in scripts
ENV TARGETPLATFORM=${TARGETPLATFORM:-linux/amd64}

# Copy VSIX file first
COPY pythagora-vs-code.vsix /var/init_data/pythagora-vs-code.vsix

# Install all dependencies
COPY cloud/setup-dependencies.sh /tmp/setup-dependencies.sh
RUN chmod +x /tmp/setup-dependencies.sh && \
    /tmp/setup-dependencies.sh && \
    rm /tmp/setup-dependencies.sh

ENV PYTH_INSTALL_DIR=/pythagora

# Set up work directory
WORKDIR ${PYTH_INSTALL_DIR}/pythagora-core

# Add Python requirements
ADD requirements.txt .

# Create and activate a virtual environment, then install dependencies
RUN python3 -m venv venv && \
    . venv/bin/activate && \
    pip install -r requirements.txt

# Copy application files
ADD main.py .
ADD core core
ADD pyproject.toml .
ADD cloud/config-docker.json config.json

# Set the virtual environment to be automatically activated
ENV VIRTUAL_ENV=${PYTH_INSTALL_DIR}/pythagora-core/venv
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

ENV PYTHAGORA_DATA_DIR=${PYTH_INSTALL_DIR}/pythagora-core/data/
RUN mkdir -p data

# Expose MongoDB and application ports
EXPOSE 27017 8000 8080 5173 3000

# Create a group and user
RUN groupadd -g 1000 devusergroup && \
    useradd -m -u 1000 -g devusergroup -s /bin/bash devuser && \
    echo "devuser:devuser" | chpasswd && \
    adduser devuser sudo && \
    echo "devuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

# Set up entrypoint and VS Code extension
ADD cloud/entrypoint.sh /entrypoint.sh
ADD cloud/on-event-extension-install.sh /var/init_data/on-event-extension-install.sh
ADD cloud/favicon.svg /favicon.svg
ADD cloud/favicon.ico /favicon.ico

# Create necessary directories with proper permissions for code-server
RUN mkdir -p /usr/local/share/code-server/data/User/globalStorage && \
    mkdir -p /usr/local/share/code-server/data/User/History && \
    mkdir -p /usr/local/share/code-server/data/Machine && \
    mkdir -p /usr/local/share/code-server/data/logs

# Add code server settings.json
ADD cloud/settings.json /usr/local/share/code-server/data/Machine/settings.json

RUN chown -R devuser:devusergroup /usr/local/share/code-server && \
    chmod -R 755 /usr/local/share/code-server && \
    # Copy icons
    cp -f /favicon.ico /usr/local/lib/code-server/src/browser/media/favicon.ico && \
    cp -f /favicon.svg /usr/local/lib/code-server/src/browser/media/favicon-dark-support.svg && \
    cp -f /favicon.svg /usr/local/lib/code-server/src/browser/media/favicon.svg

# Configure PostHog analytics integration
RUN sed -i "s|'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' ;|'sha256-/r7rqQ+yrxt57sxLuQ6AMYcy/lUpvAIzHjIJt/OeLWU=' https://us-assets.i.posthog.com ;|g" /usr/local/lib/code-server/lib/vscode/out/server-main.js

COPY cloud/posthog.html /tmp/posthog.html
RUN sed -i '/<head>/r /tmp/posthog.html' /usr/local/lib/code-server/lib/vscode/out/vs/code/browser/workbench/workbench.html && \
    rm /tmp/posthog.html

RUN chmod +x /entrypoint.sh && \
    chmod +x /var/init_data/on-event-extension-install.sh && \
    chown -R devuser:devusergroup /pythagora && \
    chown -R devuser: /var/init_data/

# Create workspace directory
RUN mkdir -p ${PYTH_INSTALL_DIR}/pythagora-core/workspace && \
    chown -R devuser:devusergroup ${PYTH_INSTALL_DIR}/pythagora-core/workspace

# Set up git config
RUN su -c "git config --global user.email 'devuser@pythagora.ai'" devuser && \
    su -c "git config --global user.name 'pythagora'" devuser

# Remove the USER directive to keep root as the running user
ENTRYPOINT ["/entrypoint.sh"]

================================================
FILE: LICENSE
================================================
# Functional Source License, Version 1.1, MIT Future License

## Abbreviation

FSL-1.1-MIT

## Notice

Copyright 2024 Pythagora Technologies, Inc.

## Terms and Conditions

### Licensor ("We")

The party offering the Software under these Terms and Conditions.

### The Software

The "Software" is each version of the software that we make available under
these Terms and Conditions, as indicated by our inclusion of these Terms and
Conditions with the Software.

### License Grant

Subject to your compliance with this License Grant and the Patents,
Redistribution and Trademark clauses below, we hereby grant you the right to
use, copy, modify, create derivative works, publicly perform, publicly display
and redistribute the Software for any Permitted Purpose identified below.

### Permitted Purpose

A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
means making the Software available to others in a commercial product or
service that:

1. substitutes for the Software;

2. substitutes for any other product or service we offer using the Software
   that exists as of the date we make the Software available; or

3. offers the same or substantially similar functionality as the Software.

Permitted Purposes specifically include using the Software:

1. for your internal use and access;

2. for non-commercial education;

3. for non-commercial research; and

4. in connection with professional services that you provide to a licensee
   using the Software in accordance with these Terms and Conditions.

### Patents

To the extent your use for a Permitted Purpose would necessarily infringe our
patents, the license grant above includes a license under our patents. If you
make a claim against any party that the Software infringes or contributes to
the infringement of any patent, then your patent license to the Software ends
immediately.

### Redistribution

The Terms and Conditions apply to all copies, modifications and derivatives of
the Software.

If you redistribute any copies, modifications or derivatives of the Software,
you must include a copy of or a link to these Terms and Conditions and not
remove any copyright notices provided in or with the Software.

### Disclaimer

THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.

IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.

### Trademarks

Except for displaying the License Details and identifying us as the origin of
the Software, you have no right under these Terms and Conditions to use our
trademarks, trade names, service marks or product names.

## Grant of Future License

We hereby irrevocably grant you an additional license to use the Software under
the MIT license that is effective on the second anniversary of the date we make
the Software available. On or after that date, you may use the Software under
the MIT license, in which case the following will apply:

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<div align="center">

# 🧑‍✈️ GPT PILOT 🧑‍✈️

</div>

---

<div align="center">

[![Discord](https://img.shields.io/badge/Discord-Join%20Us-5865F2?style=social&logo=discord)](https://discord.gg/HaqXugmxr9)
[![GitHub Repo stars](https://img.shields.io/github/stars/Pythagora-io/gpt-pilot?style=social)](https://github.com/Pythagora-io/gpt-pilot)
[![Twitter Follow](https://img.shields.io/twitter/follow/PythagoraAI?style=social)](https://x.com/PythagoraAI)

</div>

---

<div align="center">
<a href="https://www.ycombinator.com/" target="_blank"><img src="https://s3.amazonaws.com/assets.pythagora.ai/yc/PNG/Black.png" alt="Pythagora-io%2Fgpt-pilot | Trendshift" style="width: 250px; height: 93px;"/></a>
</div>
<br>
<div align="center">
<a href="https://trendshift.io/repositories/466" target="_blank"><img src="https://trendshift.io/api/badge/repositories/466" alt="Pythagora-io%2Fgpt-pilot | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</div>

<br>
<br>

<div align="center">
   
### GPT Pilot doesn't just generate code, it builds apps!

</div>

<div align="center">

This repo is not being maintained anymore.

# Visit [Pythagora.ai](https://www.pythagora.ai/) for more info

</div>

---
<div align="center">

[![See it in action](https://img.youtube.com/vi/o1nEvwjKziw/0.jpg)]([https://youtu.be/4g-1cPGK0GA](https://www.youtube.com/watch?v=o1nEvwjKziw))

(click to open the video in YouTube) (1:04min)

</div>

---

<div align="center">

<a href="https://marketplace.visualstudio.com/items?itemName=PythagoraTechnologies.pythagora-vs-code" target="_blank"><img src="https://github.com/Pythagora-io/gpt-pilot/assets/10895136/5792143e-77c7-47dd-ad96-6902be1501cd" alt="Pythagora-io%2Fgpt-pilot | Trendshift" style="width: 185px; height: 55px;" width="185" height="55"/></a>

</div>

GPT Pilot is the core technology for the [Pythagora VS Code extension](https://marketplace.visualstudio.com/items?itemName=PythagoraTechnologies.pythagora-vs-code) that aims to provide **the first real AI developer companion**. Not just an autocomplete or a helper for PR messages but rather a real AI developer that can write full features, debug them, talk to you about issues, ask for review, etc.

---

📫 If you would like to get updates on future releases or just get in touch, join our [Discord server](https://discord.gg/HaqXugmxr9) or you [can add your email here](http://eepurl.com/iD6Mpo). 📬

---

<!-- TOC -->
* [🔌 Requirements](#-requirements)
* [🚦How to start using gpt-pilot?](#how-to-start-using-gpt-pilot)
* [🔎 Examples](#-examples)
* [🐳 How to start gpt-pilot in docker?](#-how-to-start-gpt-pilot-in-docker)
* [🧑‍💻️ CLI arguments](#-cli-arguments)
* [🏗 How GPT Pilot works?](#-how-gpt-pilot-works)
* [🕴How's GPT Pilot different from _Smol developer_ and _GPT engineer_?](#hows-gpt-pilot-different-from-smol-developer-and-gpt-engineer)
* [🍻 Contributing](#-contributing)
* [🔗 Connect with us](#-connect-with-us)
* [🌟 Star history](#-star-history)
<!-- TOC -->

---

GPT Pilot aims to research how much LLMs can be utilized to generate fully working, production-ready apps while the developer oversees the implementation.

**The main idea is that AI can write most of the code for an app (maybe 95%), but for the rest, 5%, a developer is and will be needed until we get full AGI**.

If you are interested in our learnings during this project, you can check [our latest blog posts](https://blog.pythagora.ai/2024/02/19/gpt-pilot-what-did-we-learn-in-6-months-of-working-on-a-codegen-pair-programmer/).

---

<br>

<div align="center">

### **[👉 Examples of apps written by GPT Pilot 👈](https://github.com/Pythagora-io/gpt-pilot/wiki/Apps-created-with-GPT-Pilot)**

</div>
<br>

---

# 🔌 Requirements

- **Python 3.9+**

# 🚦How to start using gpt-pilot?
👉 If you are using VS Code as your IDE, the easiest way to start is by downloading [GPT Pilot VS Code extension](https://bit.ly/3IeZxp6). 👈

Otherwise, you can use the CLI tool.

### If you're new to GPT Pilot:

After you have Python and (optionally) PostgreSQL installed, follow these steps:

1. `git clone https://github.com/Pythagora-io/gpt-pilot.git` (clone the repo)
2. `cd gpt-pilot` (go to the repo folder)
3. `python3 -m venv venv` (create a virtual environment)
4. `source venv/bin/activate` (or on Windows `venv\Scripts\activate`) (activate the virtual environment)
5. `pip install -r requirements.txt` (install the dependencies)
6. `cp example-config.json config.json` (create `config.json` file)
7. Set your key and other settings in `config.json` file:
   - LLM Provider (`openai`, `anthropic` or `groq`) key and endpoints (leave `null` for default) (note that Azure and OpenRouter are suppored via the `openai` setting)
   - Your API key (if `null`, will be read from the environment variables)
   - database settings: sqlite is used by default, PostgreSQL should also work
   - optionally update `fs.ignore_paths` and add files or folders which shouldn't be tracked by GPT Pilot in workspace, useful to ignore folders created by compilers
8. `python main.py` (start GPT Pilot)

All generated code will be stored in the folder `workspace` inside the folder named after the app name you enter upon starting the pilot.

# 🔎 [Examples](https://github.com/Pythagora-io/gpt-pilot/wiki/Apps-created-with-GPT-Pilot)

[Click here](https://github.com/Pythagora-io/gpt-pilot/wiki/Apps-created-with-GPT-Pilot) to see all example apps created with GPT Pilot.

### PostgreSQL support

GPT Pilot uses built-in SQLite database by default. If you want to use the PostgreSQL database, you need to additional install `asyncpg` and `psycopg2` packages:

```bash
pip install asyncpg psycopg2
```

Then, you need to update the `config.json` file to set `db.url` to `postgresql+asyncpg://<user>:<password>@<db-host>/<db-name>`.

# 🧑‍💻️ CLI arguments

### List created projects (apps)

```bash
python main.py --list
```

Note: for each project (app), this also lists "branches". Currently we only support having one branch (called "main"), and in the future we plan to add support for multiple project branches.

### Load and continue from the latest step in a project (app)

```bash
python main.py --project <app_id>
```

### Load and continue from a specific step in a project (app)

```bash
python main.py --project <app_id> --step <step>
```

Warning: this will delete all progress after the specified step!

### Delete project (app)

```bash
python main.py --delete <app_id>
```

Delete project with the specified `app_id`. Warning: this cannot be undone!

### Other command-line options

There are several other command-line options that mostly support calling GPT Pilot from our VSCode extension. To see all the available options, use the `--help` flag:

```bash
python main.py --help
```

# 🏗 How GPT Pilot works?
Here are the steps GPT Pilot takes to create an app:

1. You enter the app name and the description.
2. **Product Owner agent** like in real life, does nothing. :)
3. **Specification Writer agent** asks a couple of questions to understand the requirements better if project description is not good enough.
4. **Architect agent** writes up technologies that will be used for the app and checks if all technologies are installed on the machine and installs them if not.
5. **Tech Lead agent** writes up development tasks that the Developer must implement.
6. **Developer agent** takes each task and writes up what needs to be done to implement it. The description is in human-readable form.
7. **Code Monkey agent** takes the Developer's description and the existing file and implements the changes.
8. **Reviewer agent** reviews every step of the task and if something is done wrong Reviewer sends it back to Code Monkey.
9. **Troubleshooter agent** helps you to give good feedback to GPT Pilot when something is wrong.
10. **Debugger agent** hate to see him, but he is your best friend when things go south.
11. **Technical Writer agent** writes documentation for the project.

<br>

# 🕴How's GPT Pilot different from _Smol developer_ and _GPT engineer_?

- **GPT Pilot works with the developer to create a fully working production-ready app** - I don't think AI can (at least in the near future) create apps without a developer being involved. So, **GPT Pilot codes the app step by step** just like a developer would in real life. This way, it can debug issues as they arise throughout the development process. If it gets stuck, you, the developer in charge, can review the code and fix the issue. Other similar tools give you the entire codebase at once - this way, bugs are much harder to fix for AI and for you as a developer.
  <br><br>
- **Works at scale** - GPT Pilot isn't meant to create simple apps but rather so it can work at any scale. It has mechanisms that filter out the code, so in each LLM conversation, it doesn't need to store the entire codebase in context, but it shows the LLM only the relevant code for the current task it's working on. Once an app is finished, you can continue working on it by writing instructions on what feature you want to add.

# 🍻 Contributing
If you are interested in contributing to GPT Pilot, join [our Discord server](https://discord.gg/HaqXugmxr9), check out open [GitHub issues](https://github.com/Pythagora-io/gpt-pilot/issues), and see if anything interests you. We would be happy to get help in resolving any of those. The best place to start is by reviewing blog posts mentioned above to understand how the architecture works before diving into the codebase.

## 🖥 Development
Other than the research, GPT Pilot needs to be debugged to work in different scenarios. For example, we realized that the quality of the code generated is very sensitive to the size of the development task. When the task is too broad, the code has too many bugs that are hard to fix, but when the development task is too narrow, GPT also seems to struggle in getting the task implemented into the existing code.

## 📊 Telemetry
To improve GPT Pilot, we are tracking some events from which you can opt out at any time. You can read more about it [here](./docs/TELEMETRY.md).

# 🔗 Connect with us
🌟 As an open-source tool, it would mean the world to us if you starred the GPT-pilot repo 🌟

💬 Join [the Discord server](https://discord.gg/HaqXugmxr9) to get in touch.


================================================
FILE: cloud/config-docker.json
================================================
{
  "llm": {
    "openai": {
      "base_url": null,
      "api_key": null,
      "connect_timeout": 60.0,
      "read_timeout": 60.0,
      "extra": null
    },
    "anthropic": {
      "base_url": null,
      "api_key": null,
      "connect_timeout": 60.0,
      "read_timeout": 60.0,
      "extra": null
    },
    "relace": {
      "base_url": null,
      "api_key": null,
      "connect_timeout": 60.0,
      "read_timeout": 60.0,
      "extra": null
    }
  },
  "agent": {
    "default": {
      "provider": "openai",
      "model": "gpt-4o-2024-05-13",
      "temperature": 0.5
    },
    "BugHunter.check_logs": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.5
    },
    "CodeMonkey": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.0
    },
    "CodeMonkey.code_review": {
      "provider": "openai",
      "model": "claude-3-5-sonnet-20240620",
      "temperature": 0.0
    },
    "CodeMonkey.implement_changes": {
      "provider": "relace",
      "model": "relace-code-merge",
      "temperature": 0.0
    },
    "CodeMonkey.describe_files": {
      "provider": "openai",
      "model": "gpt-4o-mini-2024-07-18",
      "temperature": 0.0
    },
    "Frontend": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.0
    },
    "get_relevant_files": {
      "provider": "openai",
      "model": "gpt-4o-2024-05-13",
      "temperature": 0.5
    },
    "Developer.parse_task": {
      "provider": "openai",
      "model": "claude-3-5-sonnet-20241022",
      "temperature": 0.0
    },
    "SpecWriter": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.0
    },
    "Developer.breakdown_current_task": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.5
    },
    "TechLead.plan_epic": {
      "provider": "openai",
      "model": "claude-3-5-sonnet-20240620",
      "temperature": 0.5
    },
    "TechLead.epic_breakdown": {
      "provider": "openai",
      "model": "claude-3-5-sonnet-20241022",
      "temperature": 0.5
    },
    "Troubleshooter.generate_bug_report": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.5
    },
    "Troubleshooter.get_run_command": {
      "provider": "openai",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.0
    },
    "Troubleshooter.define_user_review_goal": {
      "provider": "anthropic",
      "model": "claude-sonnet-4-20250514",
      "temperature": 0.0
    }
  },
  "prompt": {
    "paths": [
      "/pythagora/pythagora-core/core/prompts"
    ]
  },
  "log": {
    "level": "DEBUG",
    "format": "%(asctime)s %(levelname)s [%(name)s] %(message)s",
    "output": "data/pythagora.log"
  },
  "db": {
    "url": "sqlite+aiosqlite:///data/database/pythagora.db",
    "debug_sql": false,
    "save_llm_requests": false
  },
  "ui": {
    "type": "plain"
  },
  "fs": {
    "type": "local",
    "workspace_root": "/pythagora/pythagora-core/workspace",
    "ignore_paths": [
      ".git",
      ".gpt-pilot",
      ".idea",
      ".vscode",
      ".next",
      ".DS_Store",
      "__pycache__",
      "site-packages",
      "node_modules",
      "package-lock.json",
      "venv",
      ".venv",
      "dist",
      "build",
      "target",
      "*.min.js",
      "*.min.css",
      "*.svg",
      "*.csv",
      "*.log",
      "go.sum",
      "migration_lock.toml"
    ],
    "ignore_size_threshold": 50000
  }
}


================================================
FILE: cloud/entrypoint.sh
================================================
#!/bin/bash

set -e
# Production instances are slow with date command and stderr
# export PS4='+ $(date "+%Y-%m-%d %H:%M:%S") '
# set -x

echo "TASK: Entrypoint script started"
# export MONGO_DB_DATA=$PYTHAGORA_DATA_DIR/mongodata
# mkdir -p $MONGO_DB_DATA

# # Start MongoDB in the background
# mongod --dbpath "$MONGO_DB_DATA" --bind_ip_all >> $MONGO_DB_DATA/mongo_logs.txt 2>&1 &

# # Loop until MongoDB is running (use pgrep for speed)
# for ((i=0; i<10*5; i++)); do
#   if pgrep -x mongod > /dev/null; then
#     echo "TASK: MongoDB started"
#     break
#   fi
#   sleep 0.2
# done

export DB_DIR=$PYTHAGORA_DATA_DIR/database

chown -R devuser: $PYTHAGORA_DATA_DIR
su -c "mkdir -p $DB_DIR" devuser

# Start the VS Code extension installer/HTTP server script in the background
su -c "cd /var/init_data/ && ./on-event-extension-install.sh" devuser

# Keep container running
echo "FINISH: Entrypoint script finished"
tail -f /dev/null


================================================
FILE: cloud/on-event-extension-install.sh
================================================
#!/bin/bash

set -e

VSCODE_SERVER_PORT=8080

# Create workspace directory and settings
mkdir -p /pythagora/pythagora-core/workspace/.vscode
printf '{\n  "gptPilot.isRemoteWs": true,\n  "gptPilot.useRemoteWs": false,\n  "workbench.colorTheme": "Default Dark+",\n  "remote.autoForwardPorts": false\n}' > /pythagora/pythagora-core/workspace/.vscode/settings.json

# Start code-server and direct to our workspace
echo "Starting code-server..."
code-server --disable-proxy --disable-workspace-trust --config /etc/code-server/config.yaml /pythagora/pythagora-core/workspace &
CODE_SERVER_PID=$!
echo $CODE_SERVER_PID > /tmp/vscode-http-server.pid

# Wait for code-server to open the port (e.g., 8080)
for ((i=0; i<15*2; i++)); do
  if curl -s "http://localhost:$VSCODE_SERVER_PORT/healthz" > /dev/null; then
    echo "TASK: VS Code server started"
    echo "VS Code HTTP server started with PID $CODE_SERVER_PID. Access at http://localhost:$VSCODE_SERVER_PORT"
    break
  fi
  sleep 0.5
done


================================================
FILE: cloud/posthog.html
================================================
<script>
    !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init Re Os As Pe Ms Fs capture Ve calculateEventProperties js register register_once register_for_session unregister unregister_for_session qs getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey canRenderSurveyAsync identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty zs Ls createPersonProfile Us Rs Bs opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing Ds debug I Ns getPageViewId captureTraceFeedback captureTraceMetric".split(" "),n=0;n<o.length;n++)g(u,o[n]);e._i.push([i,s,a])},e.__SV=1)}(document,window.posthog||[]);
    posthog.init('phc_CPMrohXQJiasSs2PCwK3SItm1mUT6F69dcLo1TMZOz8', {
        api_host: 'https://us.i.posthog.com',
        defaults: '2025-05-24',
        person_profiles: 'identified_only',
    })
</script>

================================================
FILE: cloud/settings.json
================================================
{
    "workbench.startupEditor": "none",
    "workbench.statusBar.visible": false,
    "workbench.editor.showTabs": "none",
    "chat.commandCenter.enabled": false,
    "window.commandCenter": false,
    "workbench.colorCustomizations": {
         "commandCenter.background": "#0B0912",
         "activityBar.foreground":   "#F7F8F8",
         "activityBar.background": "#0B0912",
         "commandCenter.foreground": "#F7F8F8",
         "commandCenter.activeBackground": "#0B0912",
         "titleBar.activeBackground": "#0B0912",
         "titleBar.inactiveBackground": "#0B0912",
         "sideBarSectionHeader.background": "#0B0912",
         "sideBarSectionHeader.foreground": "#F7F8F8",
         "editorGroupHeader.tabsBackground": "#0B0912",
         "tab.activeBackground":   "#0B0912",
         "tab.inactiveBackground": "#0B0912",
         "tab.activeForeground":   "#F7F8F8",
         "tab.inactiveForeground": "#F7F8F8",
         "editorGroup.emptyBackground": "#0B0912",
         "editor.background": "#0B0912",
         "sideBar.background": "#0B0912",
         "editorGroup.border": "#0B0912"
    }
}


================================================
FILE: cloud/setup-dependencies.sh
================================================
#!/bin/bash
set -e

# Set environment variables
export DEBIAN_FRONTEND=noninteractive
export TZ=Etc/UTC

# IMPORTANT: Create a dummy update-ca-certificates script that does nothing
# This completely bypasses the problematic ca-certificates update process
echo "Creating dummy update-ca-certificates script"
if [ -f /usr/sbin/update-ca-certificates ]; then
    mv /usr/sbin/update-ca-certificates /usr/sbin/update-ca-certificates.orig
fi
cat > /usr/sbin/update-ca-certificates << 'EOF'
#!/bin/sh
echo "Dummy update-ca-certificates called, doing nothing"
exit 0
EOF
chmod +x /usr/sbin/update-ca-certificates

# Update package list and install prerequisites
echo "Installing basic packages"
apt-get update && apt-get install -y --no-install-recommends \
    software-properties-common \
    build-essential \
    curl \
    git \
    gnupg \
    tzdata \
    inotify-tools \
    vim \
    nano \
    lsof \
    procps \
    ca-certificates
echo "Basic packages installed successfully"
rm -rf /var/lib/apt/lists/*

# Install Python 3.12
echo "Adding Python 3.12 PPA"
add-apt-repository ppa:deadsnakes/ppa -y && apt-get update
echo "Installing Python 3.12"
apt-get install -y --no-install-recommends \
    python3.12 \
    python3.12-venv \
    python3.12-dev \
    python3-pip
echo "Python 3.12 installed successfully"
rm -rf /var/lib/apt/lists/*

# Set Python 3.12 as default
echo "Setting Python 3.12 as default"
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.12 1
update-alternatives --install /usr/bin/python python /usr/bin/python3 1
python --version

# Install Node.js
echo "Installing Node.js"
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get install -y nodejs
node --version && npm --version

# Install MongoDB based on platform architecture
echo "Installing MongoDB"
case "$TARGETPLATFORM" in
    "linux/amd64")
        MONGO_ARCH="amd64"
        ;;
    "linux/arm64"|"linux/arm64/v8")
        MONGO_ARCH="arm64"
        ;;
    *)
        echo "Using default architecture amd64 for MongoDB"
        MONGO_ARCH="amd64"
        ;;
esac

curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-archive-keyring.gpg
echo "deb [arch=$MONGO_ARCH signed-by=/usr/share/keyrings/mongodb-archive-keyring.gpg] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/6.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-6.0.list
apt-get update && apt-get install -y mongodb-org
echo "MongoDB installed successfully"
rm -rf /var/lib/apt/lists/*

# Install code-server
echo "Installing code-server"
VERSION="4.97.2"
case "$TARGETPLATFORM" in
    "linux/amd64")
        PLATFORM="amd64"
        ;;
    "linux/arm64"|"linux/arm64/v8")
        PLATFORM="arm64"
        ;;
    *)
        echo "Using default platform amd64 for code-server"
        PLATFORM="amd64"
        ;;
esac

DOWNLOAD_URL="https://github.com/coder/code-server/releases/download/v${VERSION}/code-server-${VERSION}-linux-${PLATFORM}.tar.gz"
echo "Downloading code-server from $DOWNLOAD_URL"
curl -L "$DOWNLOAD_URL" -o /tmp/code-server.tar.gz

# Create directories and install
mkdir -p /usr/local/lib/code-server
mkdir -p /usr/local/bin
mkdir -p /usr/local/share/code-server/extensions
mkdir -p /usr/local/share/code-server/data
mkdir -p /etc/code-server

# Install code-server
tar -xzf /tmp/code-server.tar.gz -C /usr/local/lib/code-server --strip-components=1
ln -s /usr/local/lib/code-server/bin/code-server /usr/local/bin/code-server
rm /tmp/code-server.tar.gz
echo "code-server installed successfully"

# Create default config
cat > /etc/code-server/config.yaml << EOF
bind-addr: 0.0.0.0:8080
auth: none
extensions-dir: /usr/local/share/code-server/extensions
user-data-dir: /usr/local/share/code-server/data
EOF

# Pre-install extension
echo "Installing VS Code extension..."
code-server --config /etc/code-server/config.yaml --install-extension /var/init_data/pythagora-vs-code.vsix || {
    echo "Extension installation failed but continuing build process..."
}

# Restore original update-ca-certificates if it exists
if [ -f /usr/sbin/update-ca-certificates.orig ]; then
    mv /usr/sbin/update-ca-certificates.orig /usr/sbin/update-ca-certificates
    echo "Restored original update-ca-certificates"
fi

echo "Setup completed successfully"

================================================
FILE: core/agents/__init__.py
================================================


================================================
FILE: core/agents/architect.py
================================================
import json
from enum import Enum
from typing import Any, Optional

from pydantic import BaseModel, Field

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse
from core.db.models import Specification
from core.llm.parser import JSONParser
from core.log import get_logger
from core.telemetry import telemetry
from core.templates.base import BaseProjectTemplate, NoOptions
from core.templates.example_project import EXAMPLE_PROJECTS
from core.templates.registry import (
    PROJECT_TEMPLATES,
    ProjectTemplateEnum,
)

ARCHITECTURE_STEP_NAME = "Project architecture"
WARN_SYSTEM_DEPS = ["docker", "kubernetes", "microservices"]
WARN_FRAMEWORKS = ["next.js", "vue", "vue.js", "svelte", "angular"]
WARN_FRAMEWORKS_URL = "https://github.com/Pythagora-io/gpt-pilot/wiki/Using-GPT-Pilot-with-frontend-frameworks"

log = get_logger(__name__)


class AppType(str, Enum):
    WEB = "web-app"
    API = "api-service"
    MOBILE = "mobile-app"
    DESKTOP = "desktop-app"
    CLI = "cli-tool"


# FIXME: all the response pydantic models should be strict (see config._StrictModel), also check if we
#  can disallow adding custom Python attributes to the model
class SystemDependency(BaseModel):
    name: str = Field(
        None,
        description="Name of the system dependency, for example Node.js or Python.",
    )
    description: str = Field(
        None,
        description="One-line description of the dependency.",
    )
    test: str = Field(
        None,
        description="Command line to test whether the dependency is available on the system.",
    )
    required_locally: bool = Field(
        None,
        description="Whether this dependency must be installed locally (as opposed to connecting to cloud or other server)",
    )


class PackageDependency(BaseModel):
    name: str = Field(
        None,
        description="Name of the package dependency, for example Express or React.",
    )
    description: str = Field(
        None,
        description="One-line description of the dependency.",
    )


class Architecture(BaseModel):
    app_type: AppType = Field(
        AppType.WEB,
        description="Type of the app to build.",
    )
    system_dependencies: list[SystemDependency] = Field(
        None,
        description="List of system dependencies required to build and run the app.",
    )
    package_dependencies: list[PackageDependency] = Field(
        None,
        description="List of framework/language-specific packages used by the app.",
    )


class TemplateSelection(BaseModel):
    architecture: str = Field(
        None,
        description="General description of the app architecture.",
    )
    template: Optional[ProjectTemplateEnum] = Field(
        None,
        description="Project template to use for the app, or null if no template is a good fit.",
    )


class Architect(BaseAgent):
    agent_type = "architect"
    display_name = "Architect"

    async def run(self) -> AgentResponse:
        spec = self.current_state.specification.clone()

        if spec.example_project:
            self.prepare_example_project(spec)
        else:
            await self.plan_architecture(spec)

        await self.check_system_dependencies(spec)

        self.next_state.specification = spec
        telemetry.set("templates", spec.templates)
        self.next_state.action = ARCHITECTURE_STEP_NAME
        await self.ui.send_back_logs(
            [
                {
                    "title": "Setting up backend",
                    "project_state_id": "be_0",
                    "disallow_reload": True,
                    "labels": ["E2 / T2", "Backend setup", "done"],
                }
            ]
        )
        return AgentResponse.done(self)

    async def select_templates(self, spec: Specification) -> tuple[str, dict[ProjectTemplateEnum, Any]]:
        """
        Select project template(s) to use based on the project description.

        Although the Pythagora database models support multiple projects, this
        function will choose at most one project template, as we currently don't
        have templates that could be used together in a single project.

        :param spec: Project specification.
        :return: Dictionary of selected project templates.
        """
        await self.send_message("Selecting starter templates ...")

        llm = self.get_llm()
        convo = (
            AgentConvo(self)
            .template(
                "select_templates",
                templates=PROJECT_TEMPLATES,
            )
            .require_schema(TemplateSelection)
        )
        tpl: TemplateSelection = await llm(convo, parser=JSONParser(TemplateSelection))
        templates = {}
        # if tpl.template:
        #     answer = await self.ask_question(
        #         f"Do you want to use the '{tpl.template.name}' template?",
        #         buttons={"yes": "Yes", "no": "No"},
        #         default="yes",
        #         buttons_only=True,
        #         hint="Project templates are here to speed up start of your app development and save tokens and time.\n"
        #         "Choose 'Yes' to use suggested template for your app.\n"
        #         "If you choose 'No', project will be created from scratch.",
        #     )
        #
        #     if answer.button == "no":
        #         return tpl.architecture, templates
        #
        #     template_class = PROJECT_TEMPLATES.get(tpl.template)
        #     if template_class:
        #         options = await self.configure_template(spec, template_class)
        #         templates[tpl.template] = template_class(
        #             options,
        #             self.state_manager,
        #             self.process_manager,
        #         )

        return tpl.architecture, templates

    async def plan_architecture(self, spec: Specification):
        await self.send_message("Planning project architecture ...")
        architecture_description, templates = await self.select_templates(spec)

        await self.send_message("Picking technologies to use ...")

        llm = self.get_llm(stream_output=True)
        convo = (
            AgentConvo(self)
            .template(
                "technologies",
                templates=templates,
                architecture=architecture_description,
            )
            .require_schema(Architecture)
        )
        arch: Architecture = await llm(convo, parser=JSONParser(Architecture))

        await self.check_compatibility(arch)

        spec.architecture = architecture_description
        spec.templates = {t.name: t.options_dict for t in templates.values()}
        spec.system_dependencies = [d.model_dump() for d in arch.system_dependencies]
        spec.package_dependencies = [d.model_dump() for d in arch.package_dependencies]
        telemetry.set("architecture", json.loads(arch.model_dump_json()))

    async def check_compatibility(self, arch: Architecture) -> bool:
        warn_system_deps = [dep.name for dep in arch.system_dependencies if dep.name.lower() in WARN_SYSTEM_DEPS]
        warn_package_deps = [dep.name for dep in arch.package_dependencies if dep.name.lower() in WARN_FRAMEWORKS]

        if warn_system_deps:
            await self.ask_question(
                f"Warning: Pythagora doesn't officially support {', '.join(warn_system_deps)}. "
                f"You can try to use {'it' if len(warn_system_deps) == 1 else 'them'}, but you may run into problems.",
                buttons={"continue": "Continue"},
                buttons_only=True,
                default="continue",
            )

        if warn_package_deps:
            await self.ask_question(
                f"Warning: Pythagora works best with vanilla JavaScript. "
                f"You can try try to use {', '.join(warn_package_deps)}, but you may run into problems. "
                f"Visit {WARN_FRAMEWORKS_URL} for more information.",
                buttons={"continue": "Continue"},
                buttons_only=True,
                default="continue",
            )

        # TODO: add "cancel" option to the above buttons; if pressed, Architect should
        # return AgentResponse.revise_spec()
        # that SpecWriter should catch and allow the user to reword the initial spec.
        return True

    def prepare_example_project(self, spec: Specification):
        log.debug(f"Setting architecture for example project: {spec.example_project}")
        arch = EXAMPLE_PROJECTS[spec.example_project]["architecture"]

        spec.architecture = arch["architecture"]
        spec.system_dependencies = arch["system_dependencies"]
        spec.package_dependencies = arch["package_dependencies"]
        spec.templates = arch["templates"]
        telemetry.set("templates", spec.templates)

    async def check_system_dependencies(self, spec: Specification):
        """
        Check whether the required system dependencies are installed.

        This also stores the app architecture telemetry data, including the
        information about whether each system dependency is installed.

        :param spec: Project specification.
        """
        deps = spec.system_dependencies

        for dep in deps:
            await self.send_message(f"Checking if {dep['name']} is available ...")
            status_code, _, _ = await self.process_manager.run_command(dep["test"])
            dep["installed"] = bool(status_code == 0)
            if status_code != 0:
                if dep["required_locally"]:
                    remedy = "Please install it before proceeding with your app."
                else:
                    remedy = "If you would like to use it locally, please install it before proceeding."
                await self.send_message(f"❌ {dep['name']} is not available. {remedy}")
                await self.ask_question(
                    f"Have you installed {dep['name']}?",
                    buttons={"continue": f"I've installed {dep['name']}"},
                    buttons_only=True,
                    default="continue",
                )

            else:
                await self.send_message(f"✅ {dep['name']} is available.")

    async def configure_template(self, spec: Specification, template_class: BaseProjectTemplate) -> BaseModel:
        """
        Ask the LLM to configure the template options.

        Based on the project description, the LLM should pick the options that
        make the most sense. If template has no options, the method is a no-op
        and returns an empty options model.

        :param spec: Project specification.
        :param template_class: Template that needs to be configured.
        :return: Configured options model.
        """
        if template_class.options_class is NoOptions:
            # If template has no options, no need to ask LLM for anything
            return NoOptions()

        llm = self.get_llm(stream_output=True)
        convo = (
            AgentConvo(self)
            .template(
                "configure_template",
                project_description=spec.description,
                project_template=template_class,
            )
            .require_schema(template_class.options_class)
        )
        return await llm(convo, parser=JSONParser(template_class.options_class))


================================================
FILE: core/agents/base.py
================================================
from typing import Any, Callable, Optional

from core.agents.response import AgentResponse
from core.config import get_config
from core.db.models import ProjectState
from core.llm.base import BaseLLMClient, LLMError
from core.log import get_logger
from core.proc.process_manager import ProcessManager
from core.state.state_manager import StateManager
from core.ui.base import AgentSource, UIBase, UserInput, pythagora_source

log = get_logger(__name__)


class BaseAgent:
    """
    Base class for agents.
    """

    agent_type: str
    display_name: str

    def __init__(
        self,
        state_manager: StateManager,
        ui: UIBase,
        *,
        step: Optional[Any] = None,
        prev_response: Optional["AgentResponse"] = None,
        process_manager: Optional["ProcessManager"] = None,
        data: Optional[Any] = None,
        args: Optional[Any] = None,
    ):
        """
        Create a new agent.
        """
        self.ui_source = AgentSource(self.display_name, self.agent_type)
        self.ui = ui
        self.state_manager = state_manager
        self.process_manager = process_manager
        self.prev_response = prev_response
        self.step = step
        self.data = data
        self.args = args

    @property
    def current_state(self) -> ProjectState:
        """Current state of the project (read-only)."""
        return self.state_manager.current_state

    @property
    def next_state(self) -> ProjectState:
        """Next state of the project (write-only)."""
        return self.state_manager.next_state

    async def send_message(self, message: str, extra_info: Optional[dict] = None):
        """
        Send a message to the user.

        Convenience method, uses `UIBase.send_message()` to send the message,
        setting the correct source and project state ID.

        :param message: Message to send.
        :param extra_info: Extra information to indicate special functionality in extension
        """
        await self.ui.send_message(
            message + "\n", source=self.ui_source, project_state_id=str(self.current_state.id), extra_info=extra_info
        )

    async def ask_question(
        self,
        question: str,
        *,
        buttons: Optional[dict[str, str]] = None,
        default: Optional[str] = None,
        buttons_only: bool = False,
        allow_empty: bool = False,
        full_screen: Optional[bool] = False,
        hint: Optional[str] = None,
        verbose: bool = True,
        initial_text: Optional[str] = None,
        extra_info: Optional[dict] = None,
        placeholder: Optional[str] = None,
    ) -> UserInput:
        """
        Ask a question to the user and return the response.

        Convenience method, uses `UIBase.ask_question()` to
        ask the question, setting the correct source and project state ID, and
        logging the question/response.

        :param question: Question to ask.
        :param buttons: Buttons to display with the question.
        :param default: Default button to select.
        :param buttons_only: Only display buttons, no text input.
        :param allow_empty: Allow empty input.
        :param full_screen: Show question full screen in extension.
        :param hint: Text to display in a popup as a hint to the question.
        :param verbose: Whether to log the question and response.
        :param initial_text: Initial text input.
        :param extra_info: Extra information to indicate special functionality in extension.
        :param placeholder: Placeholder text for the input field.
        :return: User response.
        """
        response = await self.ui.ask_question(
            question,
            buttons=buttons,
            default=default,
            buttons_only=buttons_only,
            allow_empty=allow_empty,
            full_screen=full_screen,
            hint=hint,
            verbose=verbose,
            initial_text=initial_text,
            source=self.ui_source,
            project_state_id=str(self.current_state.id) if self.current_state.prev_state_id is not None else None,
            extra_info=extra_info,
            placeholder=placeholder,
        )
        # Store the access token in the state manager
        if hasattr(response, "access_token") and response.access_token:
            self.state_manager.update_access_token(response.access_token)
        await self.state_manager.log_user_input(question, response)
        return response

    async def stream_handler(self, content: str):
        """
        Handle streamed response from the LLM.

        Serves as a callback to `AgentBase.llm()` so it can stream the responses to the UI.

        :param content: Response content.
        """
        route = getattr(self, "_current_route", None)
        await self.ui.send_stream_chunk(
            content, source=self.ui_source, project_state_id=str(self.current_state.id), route=route
        )

        if content is None:
            await self.ui.send_message("", source=self.ui_source, project_state_id=str(self.current_state.id))

    async def error_handler(self, error: LLMError, message: Optional[str] = None) -> bool:
        """
        Handle error responses from the LLM.

        :param error: The exception that was thrown the the LLM client.
        :param message: Optional message to show.
        :return: Whether the request should be retried.
        """

        if error == LLMError.KEY_EXPIRED:
            await self.ui.send_key_expired(message)
            answer = await self.ask_question(
                "Would you like to retry the last step?",
                buttons={"yes": "Yes", "no": "No"},
                buttons_only=True,
            )
            if answer.button == "yes":
                return True
        elif error == LLMError.GENERIC_API_ERROR:
            await self.stream_handler(message)
            answer = await self.ui.ask_question(
                "Would you like to retry the failed request?",
                buttons={"yes": "Yes", "no": "No"},
                buttons_only=True,
                source=pythagora_source,
            )
            if answer.button == "yes":
                return True
        elif error == LLMError.RATE_LIMITED:
            await self.stream_handler(message)

        return False

    def get_llm(self, name=None, stream_output=False, route=None) -> Callable:
        """
        Get a new instance of the agent-specific LLM client.

        The client initializes the UI stream handler and stores the
        request/response to the current state's log. The agent name
        can be overridden in case the agent needs to use a different
        model configuration.

        :param name: Name of the agent for configuration (default: class name).
        :param stream_output: Whether to enable streaming output.
        :param route: Route information for message routing.
        :return: LLM client for the agent.
        """

        if name is None:
            name = self.__class__.__name__

        config = get_config()

        llm_config = config.llm_for_agent(name)
        client_class = BaseLLMClient.for_provider(llm_config.provider)
        stream_handler = self.stream_handler if stream_output else None
        llm_client = client_class(
            llm_config,
            stream_handler=stream_handler,
            error_handler=self.error_handler,
            ui=self.ui,
            state_manager=self.state_manager,
        )

        async def client(convo, **kwargs) -> Any:
            """
            Agent-specific LLM client.

            For details on optional arguments to pass to the LLM client,
            see `pythagora.llm.openai_client.OpenAIClient()`.
            """
            # Set the route for this LLM request
            self._current_route = route
            try:
                response, request_log = await llm_client(convo, **kwargs)
                await self.state_manager.log_llm_request(request_log, agent=self)
                return response
            finally:
                # Clear the route after the request
                self._current_route = None

        return client

    async def run() -> AgentResponse:
        """
        Run the agent.

        :return: Response from the agent.
        """
        raise NotImplementedError()


================================================
FILE: core/agents/bug_hunter.py
================================================
import asyncio
import json
from enum import Enum

from pydantic import BaseModel, Field

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.mixins import ChatWithBreakdownMixin, TestSteps
from core.agents.response import AgentResponse
from core.config import CHECK_LOGS_AGENT_NAME, magic_words
from core.config.actions import (
    BH_ADDITIONAL_FEEDBACK,
    BH_HUMAN_TEST_AGAIN,
    BH_IS_BUG_FIXED,
    BH_START_BUG_HUNT,
    BH_START_USER_TEST,
    BH_STARTING_PAIR_PROGRAMMING,
    BH_WAIT_BUG_REP_INSTRUCTIONS,
)
from core.config.constants import CONVO_ITERATIONS_LIMIT
from core.db.models.project_state import IterationStatus
from core.llm.parser import JSONParser
from core.log import get_logger
from core.telemetry import telemetry
from core.ui.base import ProjectStage, pythagora_source

log = get_logger(__name__)


class HuntConclusionType(str, Enum):
    ADD_LOGS = magic_words.ADD_LOGS
    PROBLEM_IDENTIFIED = magic_words.PROBLEM_IDENTIFIED


class HuntConclusionOptions(BaseModel):
    conclusion: HuntConclusionType = Field(
        description=f"If more logs are needed to identify the problem, respond with '{magic_words.ADD_LOGS}'. If the problem is identified, respond with '{magic_words.PROBLEM_IDENTIFIED}'."
    )


class ImportantLog(BaseModel):
    logCode: str = Field(description="Actual line of code that prints the log.")
    shouldBeDifferent: bool = Field(
        description="Whether the current output should be different from the expected output."
    )
    filePath: str = Field(description="Path to the file in which the log exists.")
    currentOutput: str = Field(description="Current output of the log.")
    expectedOutput: str = Field(description="Expected output of the log.")
    explanation: str = Field(description="A brief explanation of the log.")


class ImportantLogsForDebugging(BaseModel):
    logs: list[ImportantLog] = Field(description="Important logs that will help the human debug the current bug.")


class BugHunter(ChatWithBreakdownMixin, BaseAgent):
    agent_type = "bug-hunter"
    display_name = "Bug Hunter"

    async def run(self) -> AgentResponse:
        current_iteration = self.current_state.current_iteration

        if "bug_reproduction_description" not in current_iteration:
            if not self.state_manager.async_tasks:
                self.state_manager.async_tasks = []
                self.state_manager.async_tasks.append(asyncio.create_task(self.get_bug_reproduction_instructions()))

        if current_iteration["status"] == IterationStatus.HUNTING_FOR_BUG:
            # TODO determine how to find a bug (eg. check in db, ask user a question, etc.)
            return await self.check_logs()
        elif current_iteration["status"] == IterationStatus.AWAITING_USER_TEST:
            await self.ui.send_bug_hunter_status("close_status", 0)
            return await self.ask_user_to_test(False, True)
        elif current_iteration["status"] == IterationStatus.AWAITING_BUG_REPRODUCTION:
            await self.ui.send_bug_hunter_status("close_status", 0)
            return await self.ask_user_to_test(True, False)
        elif current_iteration["status"] == IterationStatus.START_PAIR_PROGRAMMING:
            await self.ui.send_bug_hunter_status("close_status", 0)
            return await self.start_pair_programming()

    async def get_bug_reproduction_instructions(self):
        await self.send_message("Finding a way to reproduce the bug ...")
        await self.ui.set_important_stream()
        llm = self.get_llm()
        convo = (
            AgentConvo(self)
            .template(
                "get_bug_reproduction_instructions",
                current_task=self.current_state.current_task,
                user_feedback=self.current_state.current_iteration["user_feedback"],
                user_feedback_qa=self.current_state.current_iteration["user_feedback_qa"],
                docs=self.current_state.docs,
                next_solution_to_try=None,
            )
            .require_schema(TestSteps)
        )
        bug_reproduction_instructions: TestSteps = await llm(convo, parser=JSONParser(TestSteps), temperature=0)
        self.next_state.current_iteration["bug_reproduction_description"] = json.dumps(
            [test.dict() for test in bug_reproduction_instructions.steps]
        )

    async def check_logs(self, logs_message: str = None):
        self.next_state.action = BH_START_BUG_HUNT.format(
            self.current_state.tasks.index(self.current_state.current_task) + 1
        )
        llm = self.get_llm(CHECK_LOGS_AGENT_NAME, stream_output=True)
        convo = self.generate_iteration_convo_so_far()
        await self.ui.start_breakdown_stream()
        human_readable_instructions = await llm(convo, temperature=0.5)

        convo.assistant(human_readable_instructions)

        human_readable_instructions = await self.chat_with_breakdown(convo, human_readable_instructions)

        convo = (
            AgentConvo(self)
            .template(
                "bug_found_or_add_logs",
                hunt_conclusion=human_readable_instructions,
            )
            .require_schema(HuntConclusionOptions)
        )
        llm = self.get_llm()
        hunt_conclusion = await llm(convo, parser=JSONParser(HuntConclusionOptions), temperature=0)

        bug_hunting_cycles = self.current_state.current_iteration.get("bug_hunting_cycles")
        num_bug_hunting_cycles = len(bug_hunting_cycles) if bug_hunting_cycles else 0
        if hunt_conclusion.conclusion == magic_words.PROBLEM_IDENTIFIED:
            # if no need for logs, implement iteration same as before
            self.set_data_for_next_hunting_cycle(human_readable_instructions, IterationStatus.AWAITING_BUG_FIX)
            await self.send_message("Found the bug. I'm attempting to fix it ...")
            await self.ui.send_bug_hunter_status("fixing_bug", num_bug_hunting_cycles)
        else:
            # if logs are needed, add logging steps
            self.set_data_for_next_hunting_cycle(human_readable_instructions, IterationStatus.AWAITING_LOGGING)
            await self.send_message("Adding more logs to identify the bug ...")
            await self.ui.send_bug_hunter_status("adding_logs", num_bug_hunting_cycles)

        self.next_state.flag_iterations_as_modified()
        await self.async_task_finish()
        return AgentResponse.done(self)

    async def ask_user_to_test(self, awaiting_bug_reproduction: bool = False, awaiting_user_test: bool = False):
        if awaiting_user_test:
            self.next_state.action = BH_START_USER_TEST.format(
                self.current_state.tasks.index(self.current_state.current_task) + 1
            )
        elif awaiting_bug_reproduction:
            self.next_state.action = BH_WAIT_BUG_REP_INSTRUCTIONS.format(
                self.current_state.tasks.index(self.current_state.current_task) + 1
            )

        await self.async_task_finish()

        test_instructions = self.current_state.current_iteration["bug_reproduction_description"]
        await self.ui.send_message(
            "Start the app and test it by following these instructions:\n\n", source=pythagora_source
        )
        await self.ui.send_test_instructions(test_instructions, project_state_id=str(self.current_state.id))

        if self.current_state.run_command:
            await self.ui.send_run_command(self.current_state.run_command)

        user_feedback = await self.ask_question(
            BH_HUMAN_TEST_AGAIN,
            buttons={"done": "I am done testing"},
            buttons_only=True,
            default="continue",
            extra_info={"restart_app": True},
            hint="Instructions for testing:\n\n" + test_instructions,
        )

        if awaiting_user_test:
            self.next_state.current_iteration["bug_hunting_cycles"][-1]["fix_attempted"] = True

        if awaiting_user_test and not user_feedback.text:
            buttons = {"yes": "Yes, the issue is fixed", "no": "No", "start_pair_programming": "Start Pair Programming"}
            user_feedback = await self.ask_question(
                BH_IS_BUG_FIXED,
                buttons=buttons,
                default="yes",
                buttons_only=True,
                hint="Instructions for testing:\n\n" + test_instructions,
            )
            # self.next_state.current_iteration["bug_hunting_cycles"][-1]["fix_attempted"] = True

            if user_feedback.button == "yes":
                self.next_state.complete_iteration()
                return AgentResponse.done(self)
            elif user_feedback.button == "start_pair_programming":
                self.next_state.current_iteration["status"] = IterationStatus.START_PAIR_PROGRAMMING
                self.next_state.flag_iterations_as_modified()
                return AgentResponse.done(self)
            else:
                awaiting_bug_reproduction = True

        if awaiting_bug_reproduction and not user_feedback.text:
            buttons = {
                "done": "Bug is fixed",
                "continue": "Continue without feedback",  # DO NOT CHANGE THIS TEXT without changing it in the extension (it is hardcoded)
                "start_pair_programming": "Start Pair Programming",
            }
            await self.ui.send_project_stage(
                {
                    "stage": ProjectStage.ADDITIONAL_FEEDBACK,
                }
            )
            user_feedback = await self.ask_question(
                BH_ADDITIONAL_FEEDBACK,
                buttons=buttons,
                default="continue",
                extra_info={"collect_logs": True},
                hint="Instructions for testing:\n\n" + test_instructions,
            )

            if user_feedback.button == "done":
                self.next_state.complete_iteration()
                return AgentResponse.done(self)
            elif user_feedback.button == "start_pair_programming":
                self.next_state.current_iteration["status"] = IterationStatus.START_PAIR_PROGRAMMING
                self.next_state.flag_iterations_as_modified()
                return AgentResponse.done(self)

        # TODO select only the logs that are new (with PYTHAGORA_DEBUGGING_LOG)
        self.next_state.current_iteration["bug_hunting_cycles"][-1]["backend_logs"] = None
        self.next_state.current_iteration["bug_hunting_cycles"][-1]["frontend_logs"] = None
        self.next_state.current_iteration["bug_hunting_cycles"][-1]["user_feedback"] = user_feedback.text
        self.next_state.current_iteration["status"] = IterationStatus.HUNTING_FOR_BUG
        self.next_state.current_iteration["attempts"] += 1
        self.next_state.flag_iterations_as_modified()

        await self.ui.send_project_stage(
            {
                "bug_fix_attempt": self.next_state.current_iteration["attempts"],
            }
        )

        return AgentResponse.done(self)

    async def start_pair_programming(self):
        self.next_state.action = BH_STARTING_PAIR_PROGRAMMING.format(
            self.current_state.tasks.index(self.current_state.current_task) + 1
        )
        llm = self.get_llm(stream_output=True)
        convo = self.generate_iteration_convo_so_far(True)
        if len(convo.messages) > 1:
            convo.remove_last_x_messages(1)
        convo = convo.template("problem_explanation")
        await self.ui.set_important_stream()
        initial_explanation = await llm(convo, temperature=0.5)

        llm = self.get_llm()
        convo = convo.template("data_about_logs").require_schema(ImportantLogsForDebugging)
        data_about_logs = await llm(convo, parser=JSONParser(ImportantLogsForDebugging), temperature=0.5)

        await self.ui.send_data_about_logs(
            {
                "logs": [
                    {
                        "currentLog": d.currentOutput,
                        "expectedLog": d.expectedOutput,
                        "explanation": d.explanation,
                        "filePath": d.filePath,
                        "logCode": d.logCode,
                        "shouldBeDifferent": d.shouldBeDifferent,
                    }
                    for d in data_about_logs.logs
                ]
            }
        )

        await self.async_task_finish()

        while True:
            self.next_state.current_iteration["initial_explanation"] = initial_explanation
            next_step = await self.ask_question(
                "What do you want to do?",
                buttons={
                    "question": "I have a question",
                    "done": "I fixed the bug myself",
                    "tell_me_more": "Tell me more about the bug",
                    "solution_hint": "I think I know where the problem is",
                    "other": "Other",
                },
                buttons_only=True,
                default="continue",
                hint="Instructions for testing:\n\n"
                + self.current_state.current_iteration["bug_reproduction_description"],
            )

            await telemetry.trace_code_event(
                "pair-programming",
                {
                    "button": next_step.button,
                    "num_tasks": len(self.current_state.tasks),
                    "num_epics": len(self.current_state.epics),
                    "num_iterations": len(self.current_state.iterations),
                    "app_id": str(self.state_manager.project.id),
                    "app_name": self.state_manager.project.name,
                    "folder_name": self.state_manager.project.folder_name,
                },
            )

            # TODO: remove when Leon checks
            convo.remove_last_x_messages(2)

            if len(convo.messages) > CONVO_ITERATIONS_LIMIT:
                convo.slice(1, CONVO_ITERATIONS_LIMIT)

            # TODO: in the future improve with a separate conversation that parses the user info and goes into an appropriate if statement
            if next_step.button == "done":
                self.next_state.complete_iteration()
                break
            elif next_step.button == "question":
                user_response = await self.ask_question("Oh, cool, what would you like to know?")
                convo = convo.template("ask_a_question", question=user_response.text)
                await self.ui.set_important_stream()
                llm_answer = await llm(convo, temperature=0.5)
                await self.send_message(llm_answer)
            elif next_step.button == "tell_me_more":
                convo.template("tell_me_more")
                await self.ui.set_important_stream()
                response = await llm(convo, temperature=0.5)
                await self.send_message(response)
            elif next_step.button == "other":
                # this is the same as "question" - we want to keep an option for users to click to understand if we're missing something with other options
                user_response = await self.ask_question("Let me know what you think ...")
                convo = convo.template("ask_a_question", question=user_response.text)
                await self.ui.set_important_stream()
                llm_answer = await llm(convo, temperature=0.5)
                await self.send_message(llm_answer)
            elif next_step.button == "solution_hint":
                human_hint_label = "Amazing! How do you think we can solve this bug?"
                while True:
                    human_hint = await self.ask_question(human_hint_label)
                    convo = convo.template("instructions_from_human_hint", human_hint=human_hint.text)
                    await self.ui.set_important_stream()
                    llm = self.get_llm(CHECK_LOGS_AGENT_NAME, stream_output=True)
                    human_readable_instructions = await llm(convo, temperature=0.5)
                    human_approval = await self.ask_question(
                        "Can I implement this solution?", buttons={"yes": "Yes", "no": "No"}, buttons_only=True
                    )
                    llm = self.get_llm(stream_output=True)
                    if human_approval.button == "yes":
                        self.set_data_for_next_hunting_cycle(
                            human_readable_instructions, IterationStatus.AWAITING_BUG_FIX
                        )
                        self.next_state.flag_iterations_as_modified()
                        break
                    else:
                        human_hint_label = "Oh, my bad, what did I misunderstand?"
                break
            elif next_step.button == "tell_me_more":
                convo.template("tell_me_more")
                await self.ui.set_important_stream()
                response = await llm(convo, temperature=0.5)
                await self.send_message(response)
                continue

        return AgentResponse.done(self)

    def generate_iteration_convo_so_far(self, omit_last_cycle=False):
        convo = AgentConvo(self).template(
            "iteration",
            current_task=self.current_state.current_task,
            user_feedback=self.current_state.current_iteration["user_feedback"],
            user_feedback_qa=self.current_state.current_iteration["user_feedback_qa"],
            docs=self.current_state.docs,
            magic_words=magic_words,
            next_solution_to_try=None,
            test_instructions=json.loads(self.current_state.current_task.get("test_instructions") or "[]"),
        )

        hunting_cycles = self.current_state.current_iteration.get("bug_hunting_cycles", [])[
            0 : (-1 if omit_last_cycle else None)
        ]

        for hunting_cycle in hunting_cycles:
            convo = convo.assistant(hunting_cycle["human_readable_instructions"]).template(
                "log_data",
                backend_logs=hunting_cycle.get("backend_logs"),
                frontend_logs=hunting_cycle.get("frontend_logs"),
                fix_attempted=hunting_cycle.get("fix_attempted"),
                user_feedback=hunting_cycle.get("user_feedback"),
            )

        if len(convo.messages) > CONVO_ITERATIONS_LIMIT:
            convo.slice(1, CONVO_ITERATIONS_LIMIT)

        return convo

    async def async_task_finish(self):
        if self.state_manager.async_tasks:
            if not self.state_manager.async_tasks[-1].done():
                await self.send_message("Waiting for the bug reproduction instructions...")
                await self.state_manager.async_tasks[-1]
            self.state_manager.async_tasks = []

    def set_data_for_next_hunting_cycle(self, human_readable_instructions, new_status):
        self.next_state.current_iteration["description"] = human_readable_instructions
        self.next_state.current_iteration["bug_hunting_cycles"] += [
            {
                "human_readable_instructions": human_readable_instructions,
                "fix_attempted": any(
                    c["fix_attempted"] for c in self.current_state.current_iteration["bug_hunting_cycles"]
                ),
            }
        ]

        self.next_state.current_iteration["status"] = new_status


================================================
FILE: core/agents/code_monkey.py
================================================
import asyncio
import re
from enum import Enum
from typing import Optional

from pydantic import BaseModel, Field

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.mixins import FileDiffMixin
from core.agents.response import AgentResponse, ResponseType
from core.config import CODE_MONKEY_AGENT_NAME, DESCRIBE_FILES_AGENT_NAME, IMPLEMENT_CHANGES_AGENT_NAME
from core.config.actions import CM_UPDATE_FILES
from core.db.models import File
from core.llm.convo import Convo
from core.llm.parser import JSONParser, OptionalCodeBlockParser
from core.log import get_logger

log = get_logger(__name__)


# Constant for indicating missing new line at the end of a file in a unified diff
NO_EOL = "\\ No newline at end of file"

# Regular expression pattern for matching hunk headers
PATCH_HEADER_PATTERN = re.compile(r"^@@ -(\d+),?(\d+)? \+(\d+),?(\d+)? @@")

# Maximum number of attempts to ask for review if it can't be parsed
MAX_REVIEW_RETRIES = 2

# Maximum number of code implementation attempts after which we accept the changes unconditionally
MAX_CODING_ATTEMPTS = 3


class Decision(str, Enum):
    APPLY = "apply"
    IGNORE = "ignore"
    REWORK = "rework"


class Hunk(BaseModel):
    number: int = Field(description="Index of the hunk in the diff. Starts from 1.")
    reason: str = Field(description="Reason for applying or ignoring this hunk, or for asking for it to be reworked.")
    decision: Decision = Field(description="Whether to apply this hunk, rework, or ignore it.")


class ReviewChanges(BaseModel):
    hunks: list[Hunk]
    review_notes: str = Field(description="Additional review notes (optional, can be empty).")


class FileDescription(BaseModel):
    summary: str = Field(
        description="Detailed description summarized what the file is about, and what the major classes, functions, elements or other functionality is implemented."
    )
    references: list[str] = Field(
        description="List of references the file imports or includes (only files local to the project), where each element specifies the project-relative path of the referenced file, including the file extension."
    )


def extract_code_blocks(content):
    # Use regex to find all <pythagoracode> blocks with file attribute and their content
    code_blocks = re.findall(r'<pythagoracode\s+file="(.*?)">(.*?)</pythagoracode>', content, re.DOTALL)
    # Convert matches into a list of dictionaries
    return [{"file_name": file_name, "file_content": file_content} for file_name, file_content in code_blocks]


class CodeMonkey(FileDiffMixin, BaseAgent):
    agent_type = "code-monkey"
    display_name = "Code Monkey"

    async def run(self) -> AgentResponse:
        if self.prev_response and self.prev_response.type == ResponseType.DESCRIBE_FILES:
            return await self.describe_files()
        else:
            data = await self.implement_changes()
            if not data:
                return AgentResponse.done(self)
            return await self.accept_changes(data["path"], data["old_content"], data["new_content"])

    async def implement_changes(self, data: Optional[dict] = None) -> dict:
        file_name = self.step["save_file"]["path"]

        current_file = await self.state_manager.get_file_by_path(file_name)
        file_content = current_file.content.content if current_file else ""

        if data is not None:
            attempt = data["attempt"] + 1
            feedback = data["feedback"]
            log.debug(f"Fixing file {file_name} after review feedback: {feedback} ({attempt}. attempt)")
            await self.ui.send_file_status(file_name, "reworking", source=self.ui_source)
        else:
            log.debug(f"Implementing file {file_name}")
            if data is None:
                await self.ui.send_file_status(
                    file_name, "updating" if file_content else "creating", source=self.ui_source
                )
            else:
                await self.ui.send_file_status(file_name, "reworking", source=self.ui_source)
            self.next_state.action = CM_UPDATE_FILES
            feedback = None

        iterations = self.current_state.iterations
        user_feedback = None
        user_feedback_qa = None

        if iterations:
            last_iteration = iterations[-1]
            instructions = last_iteration.get("description")
            user_feedback = last_iteration.get("user_feedback")
            user_feedback_qa = last_iteration.get("user_feedback_qa")
        else:
            instructions = self.current_state.current_task["instructions"]

        blocks = extract_code_blocks(instructions)
        response = None

        if blocks and self.state_manager.get_access_token():
            try:
                # Try Relace first
                block = next((item for item in blocks if item["file_name"] == file_name), None)
                if block:
                    llm = self.get_llm(IMPLEMENT_CHANGES_AGENT_NAME)
                    convo = Convo().user(
                        {
                            "initialCode": file_content,
                            "editSnippet": block["file_content"],
                        }
                    )
                    response = await llm(convo, temperature=0, parser=OptionalCodeBlockParser())
            except Exception:
                response = None

        # Fall back to OpenAI if Relace wasn't used or returned empty response
        if not response or response is None:
            llm = self.get_llm(CODE_MONKEY_AGENT_NAME)
            convo = AgentConvo(self).template(
                "implement_changes",
                file_name=file_name,
                file_content=file_content,
                instructions=instructions,
                user_feedback=user_feedback,
                user_feedback_qa=user_feedback_qa,
            )
            if feedback:
                convo.assistant(f"```\n{data['new_content']}\n```\n").template(
                    "review_feedback",
                    content=data["approved_content"],
                    original_content=file_content,
                    rework_feedback=feedback,
                )
            response = await llm(convo, temperature=0, parser=OptionalCodeBlockParser())

        return {
            "path": file_name,
            "old_content": file_content,
            "new_content": response,
        }

    async def describe_files(self) -> AgentResponse:
        tasks = []
        to_describe = {
            file.path: file.content.content
            for file in self.current_state.files
            if not file.content.meta.get("description")
        }

        for file in self.next_state.files:
            content = to_describe.get(file.path)
            if content is None:
                continue

            if content == "":
                file.content.meta = {
                    **file.content.meta,
                    "description": "Empty file",
                    "references": [],
                }
                continue

            tasks.append(self.describe_file(file, content))

        await asyncio.gather(*tasks)
        return AgentResponse.done(self)

    async def describe_file(self, file: File, content: str):
        """
        Describes a file by sending it to the LLM agent and then updating the file's metadata in the database.
        """
        llm = self.get_llm(DESCRIBE_FILES_AGENT_NAME)
        log.debug(f"Describing file {file.path}")
        convo = (
            AgentConvo(self)
            .template(
                "describe_file",
                path=file.path,
                content=content,
            )
            .require_schema(FileDescription)
        )
        llm_response: FileDescription = await llm(convo, parser=JSONParser(spec=FileDescription))

        file.content.meta = {
            **file.content.meta,
            "description": llm_response.summary,
            "references": llm_response.references,
        }

    # ------------------------------
    # CODE REVIEW
    # ------------------------------

    async def accept_changes(self, file_path: str, old_content: str, new_content: str) -> AgentResponse:
        await self.ui.send_file_status(file_path, "done", source=self.ui_source)

        n_new_lines, n_del_lines = self.get_line_changes(old_content, new_content)
        await self.ui.generate_diff(
            file_path, old_content, new_content, n_new_lines, n_del_lines, source=self.ui_source
        )

        await self.state_manager.save_file(file_path, new_content)
        self.step["save_file"]["content"] = new_content
        self.next_state.complete_step("save_file")

        input_required = self.state_manager.get_input_required(new_content, file_path)
        if input_required:
            return AgentResponse.input_required(
                self,
                [{"file": file_path, "line": line} for line in input_required],
            )
        else:
            return AgentResponse.done(self)


================================================
FILE: core/agents/convo.py
================================================
import json
import sys
from copy import deepcopy
from typing import TYPE_CHECKING, Optional

import jsonref
from pydantic import BaseModel

from core.config import get_config
from core.llm.convo import Convo
from core.llm.prompt import JinjaFileTemplate
from core.log import get_logger

if TYPE_CHECKING:
    from core.agents.response import BaseAgent

log = get_logger(__name__)


class AgentConvo(Convo):
    prompt_loader: Optional[JinjaFileTemplate] = None

    def __init__(self, agent: "BaseAgent"):
        self.agent_instance = agent

        super().__init__()
        try:
            system_message = self.render("system")
            self.system(system_message)
        except ValueError as err:
            log.warning(f"Agent {agent.__class__.__name__} has no system prompt: {err}")

    @classmethod
    def _init_templates(cls):
        if cls.prompt_loader is not None:
            return

        config = get_config()
        cls.prompt_loader = JinjaFileTemplate(config.prompt.paths)

    def _get_default_template_vars(self) -> dict:
        if sys.platform == "win32":
            os = "Windows"
        elif sys.platform == "darwin":
            os = "macOS"
        else:
            os = "Linux"

        return {
            "state": self.agent_instance.current_state,
            "os": os,
        }

    @staticmethod
    def _serialize_prompt_context(context: dict) -> dict:
        """
        Convert data to JSON serializable format

        This is done by replacing non-serializable values with
        their string representations. This is useful for logging.
        """
        return json.loads(json.dumps(context, default=lambda o: str(o)))

    def render(self, name: str, **kwargs) -> str:
        self._init_templates()

        kwargs.update(self._get_default_template_vars())

        # Jinja uses "/" even in Windows
        template_name = f"{self.agent_instance.agent_type}/{name}.prompt"
        log.debug(f"Loading template {template_name}")
        return self.prompt_loader(template_name, **kwargs)

    def template(self, template_name: str, **kwargs) -> "AgentConvo":
        message = self.render(template_name, **kwargs)
        self.user(message)
        self.prompt_log.append(
            {
                "template": f"{self.agent_instance.agent_type}/{template_name}",
                "context": self._serialize_prompt_context(kwargs),
            }
        )
        return self

    def fork(self) -> "AgentConvo":
        child = AgentConvo(self.agent_instance)
        child.messages = deepcopy(self.messages)
        child.prompt_log = deepcopy(self.prompt_log)
        return child

    def trim(self, trim_index: int, trim_count: int) -> "AgentConvo":
        """
        Trim the conversation starting from the given index by 1 message.
        :param trim_index:
        :return:
        """
        self.messages = self.messages[:trim_index] + self.messages[trim_index + trim_count :]
        return self

    def slice(self, slice_index: int, slice_count: int) -> "AgentConvo":
        """
        Create a new conversation containing messages from slice_index to the end, excluding slice_count messages.

        :param slice_index: Starting index to slice from
        :param slice_count: Number of messages to exclude from the end
        :return: self for method chaining
        """
        end_index = max(slice_index, len(self.messages) - slice_count)
        self.messages = self.messages[:slice_index] + self.messages[end_index:]
        return self

    def require_schema(self, model: BaseModel) -> "AgentConvo":
        def remove_defs(d):
            if isinstance(d, dict):
                return {k: remove_defs(v) for k, v in d.items() if k != "$defs"}
            elif isinstance(d, list):
                return [remove_defs(v) for v in d]
            else:
                return d

        # We want to make the schema as simple as possible to avoid confusing the LLM,
        # so we remove (dereference) all the refs we can and show the "final" schema version.
        schema_txt = json.dumps(remove_defs(jsonref.loads(json.dumps(model.model_json_schema()))))
        self.user(
            f"IMPORTANT: Your response MUST conform to this JSON schema:\n```\n{schema_txt}\n```."
            f"YOU MUST NEVER add any additional fields to your response, and NEVER add additional preamble like 'Here is your JSON'."
        )
        return self

    def remove_last_x_messages(self, x: int) -> "AgentConvo":
        """
        Remove the last `x` messages from the conversation.
        """
        self.messages = self.messages[:-x]
        return self


================================================
FILE: core/agents/developer.py
================================================
import json
from enum import Enum
from typing import Annotated, Literal, Union
from uuid import UUID, uuid4

from pydantic import BaseModel, Field

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.mixins import ChatWithBreakdownMixin, RelevantFilesMixin
from core.agents.response import AgentResponse
from core.cli.helpers import get_epic_task_number
from core.config import PARSE_TASK_AGENT_NAME, TASK_BREAKDOWN_AGENT_NAME
from core.config.actions import (
    DEV_EXECUTE_TASK,
    DEV_TASK_BREAKDOWN,
    DEV_TASK_REVIEW_FEEDBACK,
    DEV_TASK_START,
    DEV_TROUBLESHOOT,
    DEV_WAIT_TEST,
)
from core.db.models.project_state import IterationStatus, TaskStatus
from core.db.models.specification import Complexity
from core.llm.parser import JSONParser
from core.log import get_logger
from core.telemetry import telemetry
from core.ui.base import ProjectStage, pythagora_source

log = get_logger(__name__)


class StepType(str, Enum):
    COMMAND = "command"
    SAVE_FILE = "save_file"
    HUMAN_INTERVENTION = "human_intervention"
    UTILITY_FUNCTION = "utility_function"


class CommandOptions(BaseModel):
    command: str = Field(description="Command to run")
    timeout: int = Field(description="Timeout in seconds")
    success_message: str = ""


class SaveFileOptions(BaseModel):
    path: str


class SaveFileStep(BaseModel):
    type: Literal[StepType.SAVE_FILE] = StepType.SAVE_FILE
    save_file: SaveFileOptions


class CommandStep(BaseModel):
    type: Literal[StepType.COMMAND] = StepType.COMMAND
    command: CommandOptions


class HumanInterventionStep(BaseModel):
    type: Literal[StepType.HUMAN_INTERVENTION] = StepType.HUMAN_INTERVENTION
    human_intervention_description: str


class UtilityFunction(BaseModel):
    type: Literal[StepType.UTILITY_FUNCTION] = StepType.UTILITY_FUNCTION
    file: str
    function_name: str
    description: str
    return_value: str
    input_value: str
    status: Literal["mocked", "implemented"]


Step = Annotated[
    Union[SaveFileStep, CommandStep, HumanInterventionStep, UtilityFunction],
    Field(discriminator="type"),
]


class TaskSteps(BaseModel):
    steps: list[Step]


def has_correct_num_of_tags(response: str) -> bool:
    """
    Checks if the response has the correct number of opening and closing tags.
    """
    return response.count("<pythagoracode file") == response.count("</pythagoracode>")


class Developer(ChatWithBreakdownMixin, RelevantFilesMixin, BaseAgent):
    agent_type = "developer"
    display_name = "Developer"

    async def run(self) -> AgentResponse:
        if self.current_state.current_step and self.current_state.current_step.get("type") == "utility_function":
            return await self.update_knowledge_base()

        if not self.current_state.unfinished_tasks:
            log.warning("No unfinished tasks found, nothing to do (why am I called? is this a bug?)")
            return AgentResponse.done(self)

        if self.current_state.unfinished_iterations:
            return await self.breakdown_current_iteration()

        # By default, we want to ask the user if they want to run the task,
        # except in certain cases (such as they've just edited it).
        # The check for docs is here to prevent us from asking the user whether we should
        # run the task twice - we'll only ask if we haven't yet checked for docs.
        if not self.current_state.current_task.get("run_always", False) and self.current_state.docs is None:
            if not await self.ask_to_execute_task():
                return AgentResponse.done(self)

        if self.current_state.docs is None and self.current_state.specification.complexity != Complexity.SIMPLE:
            # We check for external docs here, to make sure we only fetch the docs
            # if the task is actually being done.
            return AgentResponse.external_docs_required(self)

        return await self.breakdown_current_task()

    async def breakdown_current_iteration(self) -> AgentResponse:
        """
        Breaks down current iteration or task review into steps.

        :return: AgentResponse.done(self) when the breakdown is done
        """
        current_task = self.current_state.current_task

        if self.current_state.current_iteration["status"] in (
            IterationStatus.AWAITING_BUG_FIX,
            IterationStatus.AWAITING_LOGGING,
        ):
            iteration = self.current_state.current_iteration

            description = iteration["bug_hunting_cycles"][-1]["human_readable_instructions"]
            user_feedback = iteration["user_feedback"]
            source = "bug_hunt"
            n_tasks = len(self.next_state.iterations)
            log.debug(f"Breaking down the logging cycle {description}")
        else:
            iteration = self.current_state.current_iteration
            if iteration is None:
                log.error("Iteration breakdown called but there's no current iteration or task review, possible bug?")
                return AgentResponse.done(self)

            description = iteration["description"]
            user_feedback = iteration["user_feedback"]
            source = "troubleshooting"
            n_tasks = len(self.next_state.iterations)
            log.debug(f"Breaking down the iteration {description}")

        if self.current_state.files and self.current_state.relevant_files is None:
            await self.get_relevant_files_parallel(user_feedback, description)

        await self.ui.send_task_progress(
            n_tasks,  # iterations and reviews can be created only one at a time, so we are always on last one
            n_tasks,
            current_task["description"],
            source,
            "in-progress",
            self.current_state.get_source_index(source),
            self.current_state.tasks,
        )
        llm = self.get_llm(PARSE_TASK_AGENT_NAME)

        # FIXME: In case of iteration, parse_task depends on the context (files, tasks, etc) set there.
        #  Ideally this prompt would be self-contained.

        convo = (
            AgentConvo(self).template("parse_task", implementation_instructions=description).require_schema(TaskSteps)
        )
        response: TaskSteps = await llm(convo, parser=JSONParser(TaskSteps), temperature=0)

        self.set_next_steps(response, source)

        if iteration:
            if "status" not in iteration or (
                iteration["status"] in (IterationStatus.AWAITING_USER_TEST, IterationStatus.AWAITING_BUG_REPRODUCTION)
            ):
                # This is just a support for old iterations that don't have status
                self.next_state.complete_iteration()
                self.next_state.action = DEV_TROUBLESHOOT.format(len(self.current_state.iterations))
            elif iteration["status"] == IterationStatus.IMPLEMENT_SOLUTION:
                # If the user requested a change, then, we'll implement it and go straight back to testing
                self.next_state.complete_iteration()
                self.next_state.action = DEV_TROUBLESHOOT.format(len(self.current_state.iterations))
            elif iteration["status"] == IterationStatus.AWAITING_BUG_FIX:
                # If bug fixing is done, ask user to test again
                self.next_state.action = DEV_WAIT_TEST
                self.next_state.current_iteration["status"] = IterationStatus.AWAITING_USER_TEST
            elif iteration["status"] == IterationStatus.AWAITING_LOGGING:
                # If logging is done, ask user to reproduce the bug
                self.next_state.current_iteration["status"] = IterationStatus.AWAITING_BUG_REPRODUCTION
        else:
            self.next_state.action = DEV_TASK_REVIEW_FEEDBACK

        current_task_index = self.current_state.tasks.index(current_task)
        self.next_state.tasks[current_task_index] = {
            **current_task,
        }
        self.next_state.flag_tasks_as_modified()
        return AgentResponse.done(self)

    async def breakdown_current_task(self) -> AgentResponse:
        current_task = self.current_state.current_task
        current_task_index = self.current_state.tasks.index(current_task)
        self.next_state.action = DEV_TASK_BREAKDOWN.format(current_task_index + 1)

        source = self.current_state.current_epic.get("source", "app")
        await self.ui.send_task_progress(
            self.current_state.tasks.index(current_task) + 1,
            len(self.current_state.tasks),
            current_task["description"],
            source,
            "in-progress",
            self.current_state.get_source_index(source),
            self.current_state.tasks,
        )

        log.debug(f"Breaking down the current task: {current_task['description']}")

        log.debug(f"Current state files: {len(self.current_state.files)}, relevant {self.current_state.relevant_files}")
        # Check which files are relevant to the current task
        await self.get_relevant_files_parallel()

        current_task_index = self.current_state.tasks.index(current_task)

        await self.send_message("### Thinking about how to implement this task ...")

        await self.ui.start_breakdown_stream()
        await self.ui.set_important_stream()
        related_api_endpoints = current_task.get("related_api_endpoints", [])
        llm = self.get_llm(TASK_BREAKDOWN_AGENT_NAME, stream_output=True)
        # TODO: Temp fix for old projects
        if not (
            related_api_endpoints
            and len(related_api_endpoints) > 0
            and all(isinstance(api, dict) and "endpoint" in api for api in related_api_endpoints)
        ):
            related_api_endpoints = []

        redo_task_user_feedback = None

        if (
            self.next_state
            and self.next_state.current_task
            and self.next_state.current_task.get("redo_human_instructions", None) is not None
        ):
            redo_task_user_feedback = self.next_state.current_task["redo_human_instructions"]

        convo = AgentConvo(self).template(
            "breakdown",
            task=current_task,
            iteration=None,
            current_task_index=current_task_index,
            docs=self.current_state.docs,
            related_api_endpoints=related_api_endpoints,
            redo_task_user_feedback=redo_task_user_feedback,
        )

        response: str = await llm(convo)

        convo.assistant(response)

        max_retries = 2
        retry_count = 0

        while retry_count < max_retries:
            if has_correct_num_of_tags(response):
                break

            convo.user(
                "Ok, now think carefully about your previous response. If the response ends by mentioning something about continuing with the implementation, continue but don't implement any files that have already been implemented. If your last response finishes with an incomplete file, implement that file and any other that needs implementation. Finally, if your last response doesn't end by mentioning continuing and if there isn't an unfinished file implementation, respond only with `DONE` and with nothing else."
            )
            continue_response: str = await llm(convo)

            last_open_tag_index = response.rfind("<pythagoracode file")
            response = response[:last_open_tag_index] + continue_response

            convo.assistant(response)

            retry_count += 1

        response = await self.chat_with_breakdown(convo, response)

        self.next_state.tasks[current_task_index] = {
            **current_task,
            "instructions": response,
        }
        self.next_state.flag_tasks_as_modified()

        llm = self.get_llm(PARSE_TASK_AGENT_NAME)

        convo = AgentConvo(self).template("parse_task", implementation_instructions=response).require_schema(TaskSteps)

        response: TaskSteps = await llm(convo, parser=JSONParser(TaskSteps), temperature=0)

        # There might be state leftovers from previous tasks that we need to clean here
        self.next_state.modified_files = {}
        self.set_next_steps(response, source)
        self.next_state.current_task["status"] = TaskStatus.IN_PROGRESS
        self.next_state.action = DEV_TASK_START.format(current_task_index + 1)
        await telemetry.trace_code_event(
            "task-start",
            {
                "task_index": current_task_index + 1,
                "num_tasks": len(self.current_state.tasks),
                "num_epics": len(self.current_state.epics),
            },
        )
        return AgentResponse.done(self)

    def set_next_steps(self, response: TaskSteps, source: str):
        # For logging/debugging purposes, we don't want to remove the finished steps
        # until we're done with the task.
        unique_steps = self.remove_duplicate_steps({**response.model_dump()})
        finished_steps = [step for step in self.current_state.steps if step["completed"]]
        self.next_state.steps = finished_steps + [
            {
                "id": uuid4().hex,
                "completed": False,
                "source": source,
                "iteration_index": len(self.current_state.iterations),
                **step,
            }
            for step in unique_steps["steps"]
        ]
        log.debug(f"Next steps: {self.next_state.unfinished_steps}")

    def remove_duplicate_steps(self, data):
        unique_steps = []

        # Process steps attribute
        for step in data["steps"]:
            if isinstance(step, SaveFileStep) and any(
                s["type"] == "save_file" and s["save_file"]["path"] == step["save_file"]["path"] for s in unique_steps
            ):
                continue
            unique_steps.append(step)

        # Update steps attribute
        data["steps"] = unique_steps

        # Use the serializable_steps for JSON dumping
        data["original_response"] = json.dumps(unique_steps, indent=2)

        return data

    async def ask_to_execute_task(self) -> bool:
        """
        Asks the user to approve, skip or edit the current task.

        If task is edited, the method returns False so that the changes are saved. The
        Orchestrator will rerun the agent on the next iteration.

        :return: True if the task should be executed as is, False if the task is skipped or edited
        """
        buttons = {"yes": "Yes", "edit": "Edit Task"}
        if len(self.current_state.tasks) > 1:
            buttons["skip"] = "Skip Task"

        description = self.current_state.current_task["description"]
        epic_index, task_index = get_epic_task_number(self.current_state, self.current_state.current_task)

        await self.ui.send_project_stage(
            {
                "stage": ProjectStage.STARTING_TASK,
                "task_index": task_index,
            }
        )

        # find latest finished task, send back logs for it being finished
        tasks_done = [task for task in self.current_state.tasks if task not in self.current_state.unfinished_tasks]
        previous_task = tasks_done[-1] if tasks_done else None
        if previous_task:
            e_i, t_i = get_epic_task_number(self.current_state, previous_task)
            task_convo = await self.state_manager.get_task_conversation_project_states(
                UUID(previous_task["id"]), first_last_only=True
            )
            await self.ui.send_back_logs(
                [
                    {
                        "title": previous_task["description"],
                        "project_state_id": str(task_convo[0].id) if task_convo else "be_0",
                        "start_id": str(task_convo[0].id) if task_convo else "be_0",
                        "end_id": str(task_convo[-1].prev_state_id) if task_convo else "be_0",
                        "labels": [f"E{e_i} / T{t_i}", "Backend", "done"],
                    }
                ]
            )
            await self.ui.send_front_logs_headers(
                str(task_convo[0].id) if task_convo else "be_0",
                [f"E{e_i} / T{t_i}", "Backend", "done"],
                previous_task["description"],
                self.current_state.current_task.get("id"),
            )

        await self.ui.send_front_logs_headers(
            str(self.current_state.id),
            [f"E{epic_index} / T{task_index}", "Backend", "working"],
            description,
            self.current_state.current_task.get("id"),
        )

        await self.ui.send_back_logs(
            [
                {
                    "title": description,
                    "project_state_id": str(self.current_state.id),
                    "labels": [f"E{epic_index} / T{task_index}", "working"],
                }
            ]
        )
        await self.ui.clear_main_logs()
        await self.send_message(f"Starting task #{task_index} with the description:\n\n## {description}")
        if self.current_state.run_command:
            await self.ui.send_run_command(self.current_state.run_command)

        if self.next_state.current_task.get("redo_human_instructions", None) is not None:
            await self.send_message(f"Additional feedback: {self.next_state.current_task['redo_human_instructions']}")
            return True

        if self.current_state.current_task.get("quick_implementation", False):
            return True

        if self.current_state.current_task.get("user_added_subsequently", False):
            return True

        if self.current_state.current_task.get("hardcoded", False):
            return True

        if self.current_state.current_task and self.current_state.current_task.get("hardcoded", False):
            await self.ui.send_message(
                "Ok, great, you're now starting to build the backend and the first task is to test how the authentication works. You can now register and login. Your data will be saved into the database.",
                source=pythagora_source,
            )

        user_response = await self.ask_question(
            DEV_EXECUTE_TASK,
            buttons=buttons,
            default="yes",
            buttons_only=True,
            hint=description,
        )
        if user_response.button == "yes":
            # Execute the task as is
            return True

        if user_response.cancelled or user_response.button == "skip":
            log.info(f"Skipping task: {description}")
            self.next_state.current_task["instructions"] = "(skipped on user request)"
            self.next_state.set_current_task_status(TaskStatus.SKIPPED)
            await self.send_message("Skipping task...")
            # We're done here, and will pick up the next task (if any) on the next run
            return False

        user_response = await self.ask_question(
            "Edit the task description:",
            buttons={
                # FIXME: must be lowercase becase VSCode doesn't recognize it otherwise. Needs a fix in the extension
                "continue": "continue",
                "cancel": "Cancel",
            },
            default="continue",
            initial_text=description,
        )
        if user_response.button == "cancel" or user_response.cancelled:
            # User hasn't edited the task, so we can execute it immediately as is
            return await self.ask_to_execute_task()

        self.next_state.current_task["description"] = user_response.text
        self.next_state.current_task["run_always"] = True
        self.next_state.relevant_files = None
        log.info(f"Task description updated to: {user_response.text}")
        # Orchestrator will rerun us with the new task description
        return False

    async def update_knowledge_base(self):
        """
        Update the knowledge base with the current task and steps.
        """
        await self.state_manager.update_utility_functions(self.current_state.current_step)
        self.next_state.complete_step("utility_function")
        return AgentResponse.done(self)


================================================
FILE: core/agents/error_handler.py
================================================
from uuid import uuid4

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse
from core.db.models.project_state import IterationStatus
from core.log import get_logger

log = get_logger(__name__)


class ErrorHandler(BaseAgent):
    """
    Error handler agent.

    Error handler is responsible for handling errors returned by other agents. If it's possible
    to recover from the error, it should do it (which may include updating the "next" state) and
    return DONE. Otherwise it should return EXIT to tell Orchestrator to quit the application.
    """

    agent_type = "error-handler"
    display_name = "Error Handler"

    async def run(self) -> AgentResponse:
        from core.agents.executor import Executor
        from core.agents.spec_writer import SpecWriter

        error = self.prev_response
        if error is None:
            log.warning("ErrorHandler called without a previous error", stack_info=True)
            return AgentResponse.done(self)

        log.error(
            f"Agent {error.agent.display_name} returned error response: {error.type}",
            extra={"data": error.data},
        )

        if isinstance(error.agent, SpecWriter):
            # If SpecWriter wasn't able to get the project description, there's nothing for
            # us to do.
            return AgentResponse.exit(self)

        if isinstance(error.agent, Executor):
            return await self.handle_command_error(
                error.data.get("message", "Unknown error"), error.data.get("details", {})
            )

        log.error(
            f"Unhandled error response from agent {error.agent.display_name}",
            extra={"data": error.data},
        )
        return AgentResponse.exit(self)

    async def handle_command_error(self, message: str, details: dict) -> AgentResponse:
        """
        Handle an error returned by Executor agent.

        Error message must be the analyis of the command execution, and the details must contain:
        * cmd - command that was executed
        * timeout - timeout for the command if any (or None if no timeout was used)
        * status_code - exit code for the command (or None if the command timed out)
        * stdout - standard output of the command
        * stderr - standard error of the command

        :return: AgentResponse
        """
        cmd = details.get("cmd")
        timeout = details.get("timeout")
        status_code = details.get("status_code")
        stdout = details.get("stdout", "")
        stderr = details.get("stderr", "")

        if not message:
            raise ValueError("No error message provided in command error response")
        if not cmd:
            raise ValueError("No command provided in command error response details")

        confirm = await self.ask_question(
            "Can I debug why this command failed?",
            buttons={"yes": "Yes", "no": "No"},
            default="yes",
            buttons_only=True,
        )
        if confirm.cancelled or confirm.button == "no":
            log.info("Skipping command error debug (requested by user)")
            return AgentResponse.done(self)

        llm = self.get_llm(stream_output=True)
        convo = AgentConvo(self).template(
            "debug",
            task_steps=self.current_state.steps,
            current_task=self.current_state.current_task,
            # FIXME: can this break?
            step_index=self.current_state.steps.index(self.current_state.current_step),
            cmd=cmd,
            timeout=timeout,
            stdout=stdout,
            stderr=stderr,
            status_code=status_code,
            # fixme: everything above copypasted from Executor
            analysis=message,
        )
        llm_response: str = await llm(convo)

        # TODO: duplicate from Troubleshooter, maybe extract to a ProjectState method?
        self.next_state.iterations = self.current_state.iterations + [
            {
                "id": uuid4().hex,
                "user_feedback": f"Error running command: {cmd}",
                "user_feedback_qa": None,
                "description": llm_response,
                "alternative_solutions": [],
                "attempts": 1,
                "status": IterationStatus.IMPLEMENT_SOLUTION,
                "bug_hunting_cycles": [],
            }
        ]
        # TODO: maybe have ProjectState.finished_steps as well? would make the debug/ran_command prompts nicer too
        self.next_state.steps = [s for s in self.current_state.steps if s.get("completed") is True]
        # No need to call complete_step() here as we've just removed the steps so that Developer can break down the iteration
        return AgentResponse.done(self)


================================================
FILE: core/agents/executor.py
================================================
from datetime import datetime, timezone
from typing import Optional

from pydantic import BaseModel, Field

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse
from core.config.actions import EX_RUN_COMMAND, EX_SKIP_COMMAND, RUN_COMMAND
from core.llm.parser import JSONParser
from core.log import get_logger
from core.proc.exec_log import ExecLog
from core.proc.process_manager import ProcessManager
from core.state.state_manager import StateManager
from core.ui.base import AgentSource, UIBase, UISource

log = get_logger(__name__)

CMD_OUTPUT_SOURCE_NAME = "Command output"
CMD_OUTPUT_SOURCE_TYPE = "cli-output"


class CommandResult(BaseModel):
    """
    Analysis of the command run and decision on the next steps.
    """

    analysis: str = Field(
        description="Analysis of the command output (stdout, stderr) and exit code, in context of the current task"
    )
    success: bool = Field(
        description="True if the command should be treated as successful and the task should continue, false if the command unexpectedly failed and we should debug the issue"
    )


class Executor(BaseAgent):
    agent_type = "executor"
    display_name = "Executor"

    def __init__(
        self,
        state_manager: StateManager,
        ui: UIBase,
    ):
        """
        Create a new Executor agent
        """
        self.ui_source = AgentSource(self.display_name, self.agent_type)
        self.cmd_ui_source = UISource(CMD_OUTPUT_SOURCE_NAME, CMD_OUTPUT_SOURCE_TYPE)

        self.ui = ui
        self.state_manager = state_manager
        self.process_manager = ProcessManager(
            root_dir=state_manager.get_full_project_root(),
            output_handler=self.output_handler,
            exit_handler=self.exit_handler,
        )

    def for_step(self, step):
        # FIXME: not needed, refactor to use self.current_state.current_step
        # in general, passing current step is not needed
        self.step = step
        return self

    async def output_handler(self, out, err):
        await self.ui.send_stream_chunk(out, source=self.cmd_ui_source)
        await self.ui.send_stream_chunk(err, source=self.cmd_ui_source)

    async def exit_handler(self, process):
        pass

    async def run(self) -> AgentResponse:
        if not self.step:
            raise ValueError("No current step set (probably an Orchestrator bug)")

        options = self.step["command"]
        cmd = options["command"]
        cmd_name = cmd[:30] + "..." if len(cmd) > 33 else cmd
        timeout = options.get("timeout")

        if timeout:
            q = f"{RUN_COMMAND} {cmd} with {timeout}s timeout?"
        else:
            q = f"{RUN_COMMAND} {cmd}?"

        confirm = await self.ask_question(
            q,
            buttons={"yes": "Yes", "no": "No"},
            default="yes",
            buttons_only=False,
            initial_text=cmd,
            extra_info={"remove_button": "yes"},
        )
        if confirm.button == "no":
            log.info(f"Skipping command execution of `{cmd}` (requested by user)")
            await self.send_message(f"Skipping command {cmd}")
            self.complete()
            self.next_state.action = EX_SKIP_COMMAND.format(cmd_name)
            return AgentResponse.done(self)

        if confirm.button != "yes":
            cmd = confirm.text

        started_at = datetime.now(timezone.utc)

        log.info(f"Running command `{cmd}` with timeout {timeout}s")
        status_code, stdout, stderr = await self.process_manager.run_command(cmd, timeout=timeout)

        llm_response = await self.check_command_output(cmd, timeout, stdout, stderr, status_code)

        duration = (datetime.now(timezone.utc) - started_at).total_seconds()

        self.complete()
        self.next_state.action = EX_RUN_COMMAND.format(cmd_name)

        exec_log = ExecLog(
            started_at=started_at,
            duration=duration,
            cmd=cmd,
            cwd=".",
            env={},
            timeout=timeout,
            status_code=status_code,
            stdout=stdout,
            stderr=stderr,
            analysis=llm_response.analysis,
            success=llm_response.success,
        )
        await self.state_manager.log_command_run(exec_log)

        # FIXME: ErrorHandler isn't debugged with BugHunter - we should move all commands to run before testing and debug them with BugHunter
        if True or llm_response.success:
            return AgentResponse.done(self)

        return AgentResponse.error(
            self,
            llm_response.analysis,
            {
                "cmd": cmd,
                "timeout": timeout,
                "stdout": stdout,
                "stderr": stderr,
                "status_code": status_code,
            },
        )

    async def check_command_output(
        self, cmd: str, timeout: Optional[int], stdout: str, stderr: str, status_code: int
    ) -> CommandResult:
        llm = self.get_llm()
        convo = (
            AgentConvo(self)
            .template(
                "ran_command",
                task_steps=self.current_state.steps,
                current_task=self.current_state.current_task,
                # FIXME: can step ever happen *not* to be in current steps?
                step_index=self.current_state.steps.index(self.step),
                cmd=cmd,
                timeout=timeout,
                stdout=stdout,
                stderr=stderr,
                status_code=status_code,
            )
            .require_schema(CommandResult)
        )
        return await llm(convo, parser=JSONParser(spec=CommandResult), temperature=0)

    def complete(self):
        """
        Mark the step as complete.

        Note that this marks the step complete in the next state. If there's an error,
        the state won't get committed and the error handler will have access to the
        current state, where this step is still unfinished.

        This is intentional, so that the error handler can decide what to do with the
        information we give it.
        """
        self.step = None
        self.next_state.complete_step("command")


================================================
FILE: core/agents/external_docs.py
================================================
import asyncio
from urllib.parse import urljoin

import httpx
from pydantic import BaseModel

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse
from core.config import EXTERNAL_DOCUMENTATION_API
from core.llm.parser import JSONParser
from core.log import get_logger
from core.telemetry import telemetry

log = get_logger(__name__)


class DocQueries(BaseModel):
    queries: list[str]


class SelectedDocsets(BaseModel):
    docsets: list[str]


class ExternalDocumentation(BaseAgent):
    """Agent in charge of collecting and storing additional documentation.

    Docs are per task and are stores in the `docs` variable in the project state.
    This agent ensures documentation is collected only once per task.

    Agent does 2 LLM interactions:
        1. Ask the LLM to select useful documentation from a predefined list.
        2. Ask the LLM to come up with a query to use to fetch the actual documentation snippets.

    Agent does 2 calls to our documentation API:
        1. Fetch all the available docsets. `docset` is a collection of documentation snippets
           for a single topic, eg. VueJS API Reference docs.
        2. Fetch the documentation snippets for given queries.

    """

    agent_type = "external-docs"
    display_name = "Documentation"

    async def run(self) -> AgentResponse:
        await self._store_docs([], [])
        return AgentResponse.done(self)

        if self.current_state.specification.example_project:
            log.debug("Example project detected, no documentation selected.")
            available_docsets = []
        else:
            available_docsets = await self._get_available_docsets()

        selected_docsets = await self._select_docsets(available_docsets)
        await telemetry.trace_code_event("docsets_used", selected_docsets)

        if not selected_docsets:
            log.info("No documentation selected for this task.")
            await self._store_docs([], available_docsets)
            return AgentResponse.done(self)

        log.info(f"Selected {len(selected_docsets)} docsets for this task.")
        queries = await self._create_queries(selected_docsets)
        doc_snippets = await self._fetch_snippets(queries)
        await telemetry.trace_code_event("doc_snippets", {"num_stored": len(doc_snippets)})

        await self._store_docs(doc_snippets, available_docsets)
        return AgentResponse.done(self)

    async def _get_available_docsets(self) -> list[tuple]:
        url = urljoin(EXTERNAL_DOCUMENTATION_API, "docsets")
        client = httpx.Client(transport=httpx.HTTPTransport(retries=3))
        try:
            resp = client.get(url)
        except httpx.HTTPError:
            # In case of any errors, we'll proceed without the documentation
            log.warning("Failed to fetch available docsets due to an error.", exc_info=True)
            return []

        log.debug(f"Fetched {len(resp.json())} docsets.")
        return resp.json()

    async def _select_docsets(self, available_docsets: list[tuple]) -> dict[str, str]:
        """From a list of available docsets, select the relevant ones."""

        if not available_docsets:
            return {}

        llm = self.get_llm(stream_output=True)
        convo = (
            AgentConvo(self)
            .template(
                "select_docset",
                current_task=self.current_state.current_task,
                available_docsets=available_docsets,
            )
            .require_schema(SelectedDocsets)
        )
        await self.send_message("Determining if external documentation is needed for the next task...")
        llm_response: SelectedDocsets = await llm(convo, parser=JSONParser(spec=SelectedDocsets))
        available_docsets = dict(available_docsets)
        return {k: available_docsets[k] for k in llm_response.docsets if k in available_docsets}

    async def _create_queries(self, docsets: dict[str, str]) -> dict[str, list[str]]:
        """Return queries we have to make to the docs API.

        Key is the docset_key and value is the list of queries for that docset.

        """
        queries = {}
        await self.send_message("Getting relevant documentation for the following topics:")
        for k, short_desc in docsets.items():
            llm = self.get_llm(stream_output=True)
            convo = (
                AgentConvo(self)
                .template(
                    "create_docs_queries",
                    short_description=short_desc,
                    current_task=self.current_state.current_task,
                )
                .require_schema(DocQueries)
            )
            llm_response: DocQueries = await llm(convo, parser=JSONParser(spec=DocQueries))
            if llm_response.queries:
                queries[k] = llm_response.queries

        return queries

    async def _fetch_snippets(self, queries: dict[str, list[str]]) -> list[tuple]:
        """Query the docs API and fetch the documentation snippets.

        Returns a list of tuples: (docset_key, snippets).

        """
        url = urljoin(EXTERNAL_DOCUMENTATION_API, "query")
        snippets: list[tuple] = []
        async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport(retries=3)) as client:
            reqs = []
            ordered_keys = []
            for docset_key, qs in queries.items():
                reqs.append(client.get(url, params={"q": qs, "doc_key": docset_key, "num_results": 3}))
                ordered_keys.append(docset_key)

            try:
                results = await asyncio.gather(*reqs)
            except httpx.HTTPError:
                log.warning("Failed to fetch documentation snippets", exc_info=True)

        for k, res in zip(ordered_keys, results):
            json_snippets = res.json()
            log.debug(f"Fetched {len(json_snippets)} snippets from {k}")
            if len(json_snippets):
                snippets.append((k, res.json()))
        return snippets

    async def _store_docs(self, snippets: list[tuple], available_docsets: list[tuple]):
        """Store the snippets into current task data.

        Documentation snippets are stored as a list of dictionaries:
        {"key": docset-key, "desc": documentation-description, "snippets": list-of-snippets}

        :param snippets: List of tuples: (docset_key, snippets)
        :param available_docsets: List of available docsets from the API.
        """

        docsets_dict = dict(available_docsets)
        docs = []
        for docset_key, snip in snippets:
            docs.append({"key": docset_key, "desc": docsets_dict[docset_key], "snippets": snip})

        self.next_state.docs = docs


================================================
FILE: core/agents/frontend.py
================================================
import asyncio
import json
import os
import sys
from urllib.parse import urljoin

import httpx

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.git import GitMixin
from core.agents.mixins import FileDiffMixin
from core.agents.response import AgentResponse
from core.cli.helpers import capture_exception
from core.config import FRONTEND_AGENT_NAME, IMPLEMENT_CHANGES_AGENT_NAME, PYTHAGORA_API
from core.config.actions import (
    FE_CHANGE_REQ,
    FE_CONTINUE,
    FE_DONE_WITH_UI,
    FE_ITERATION,
    FE_ITERATION_DONE,
    FE_START,
)
from core.llm.convo import Convo
from core.llm.parser import DescriptiveCodeBlockParser, OptionalCodeBlockParser
from core.log import get_logger
from core.telemetry import telemetry
from core.ui.base import ProjectStage

log = get_logger(__name__)


def has_correct_num_of_backticks(response: str) -> bool:
    """
    Checks if the response has the correct number of backticks.
    """
    return response.count("```") % 2 == 0 and response.count("```") > 0


class Frontend(FileDiffMixin, GitMixin, BaseAgent):
    agent_type = "frontend"
    display_name = "Frontend"

    async def run(self) -> AgentResponse:
        if not self.current_state.epics[-1]["messages"]:
            finished = await self.start_frontend()
        elif self.next_state.epics[-1].get("file_paths_to_remove_mock"):
            finished = await self.remove_mock()
            if finished is None:
                return AgentResponse.exit(self)
        elif not self.next_state.epics[-1].get("fe_iteration_done"):
            finished = await self.continue_frontend()
        else:
            await self.set_app_details()
            finished = await self.iterate_frontend()
            if finished is None:
                return AgentResponse.exit(self)

        return await self.end_frontend_iteration(finished)

    async def start_frontend(self):
        """
        Starts the frontend of the app.
        """
        self.state_manager.fe_auto_debug = True
        await self.ui.clear_main_logs()
        await self.ui.send_front_logs_headers(str(self.next_state.id), ["E2 / T1", "working"], "Building frontend")
        await self.ui.send_back_logs(
            [
                {
                    "title": "Building frontend",
                    "project_state_id": str(self.next_state.id),
                    "labels": ["E2 / T1", "Frontend", "working"],
                }
            ]
        )
        self.next_state.action = FE_START
        await self.send_message("## Building the frontend\n\nThis may take a couple of minutes.")
        await self.ui.send_project_stage({"stage": ProjectStage.FRONTEND_STARTED})

        await self.ui.set_important_stream(False)
        llm = self.get_llm(FRONTEND_AGENT_NAME, stream_output=True)
        convo = AgentConvo(self).template(
            "build_frontend",
            summary=self.state_manager.template["template"].get_summary()
            if self.state_manager.template is not None
            else self.current_state.specification.template_summary,
            description=self.next_state.epics[-1]["description"],
            user_feedback=None,
            first_time_build=True,
        )
        response = await llm(convo, parser=DescriptiveCodeBlockParser())
        response_blocks = response.blocks
        convo.assistant(response.original_response)

        # Await the template task if it's not done yet
        if self.state_manager.async_tasks:
            if not self.state_manager.async_tasks[-1].done():
                await self.state_manager.async_tasks[-1]
            self.state_manager.async_tasks = []

        await self.process_response(response_blocks)

        self.next_state.epics[-1]["messages"] = convo.messages
        self.next_state.epics[-1]["fe_iteration_done"] = (
            "done" in response.original_response[-20:].lower().strip() or len(convo.messages) > 11
        )
        self.next_state.flag_epics_as_modified()

        return False

    async def continue_frontend(self):
        """
        Continues building the frontend of the app after the initial user input.
        """
        self.state_manager.fe_auto_debug = True
        self.next_state.action = FE_CONTINUE
        await self.ui.send_project_stage({"stage": ProjectStage.CONTINUE_FRONTEND})
        await self.send_message("### Continuing to build UI... This may take a couple of minutes")

        llm = self.get_llm(FRONTEND_AGENT_NAME, stream_output=True)
        convo = AgentConvo(self)
        convo.messages = self.current_state.epics[-1]["messages"]
        convo.user(
            "Ok, now think carefully about your previous response. If the response ends by mentioning something about continuing with the implementation, continue but don't implement any files that have already been implemented. If your last response finishes with an incomplete file, implement that file and any other that needs implementation. Finally, if your last response doesn't end by mentioning continuing and if there isn't an unfinished file implementation, respond only with `DONE` and with nothing else."
        )

        response = await llm(convo, parser=DescriptiveCodeBlockParser())
        response_blocks = response.blocks
        convo.assistant(response.original_response)

        use_relace = self.current_state.epics[-1].get("use_relace", False)
        await self.process_response(response_blocks, relace=use_relace)

        if self.next_state.epics[-1].get("manual_iteration", False):
            self.next_state.epics[-1]["fe_iteration_done"] = (
                has_correct_num_of_backticks(response.original_response)
                or self.current_state.epics[-1].get("retry_count", 0) >= 2
            )
            self.next_state.epics[-1]["retry_count"] = self.current_state.epics[-1].get("retry_count", 0) + 1
        else:
            self.next_state.epics[-1]["fe_iteration_done"] = (
                "done" in response.original_response[-20:].lower().strip() or len(convo.messages) > 15
            )

        self.next_state.epics[-1]["messages"] = convo.messages
        self.next_state.flag_epics_as_modified()

        return False

    async def iterate_frontend(self):
        """
        Iterates over the frontend.

        :return: True if the frontend is fully built, False otherwise.
        """
        self.next_state.epics[-1]["auto_debug_attempts"] = 0
        self.next_state.epics[-1]["retry_count"] = 0
        user_input = await self.try_auto_debug()

        frontend_only = self.current_state.branch.project.project_type == "swagger"
        self.next_state.action = FE_ITERATION
        # update the pages in the knowledge base
        await self.state_manager.update_implemented_pages_and_apis()

        await self.ui.send_project_stage({"stage": ProjectStage.ITERATE_FRONTEND, "iteration_index": 1})

        if user_input:
            await self.send_message("Errors detected, fixing...")
        else:
            answer = await self.ask_question(
                "Do you want to change anything or report a bug?" if frontend_only else FE_CHANGE_REQ,
                buttons={"yes": "I'm done building the UI"} if not frontend_only else None,
                default="yes",
                extra_info={"restart_app": True, "collect_logs": True},
                placeholder='For example, "I don\'t see anything when I open http://localhost:5173/" or "Nothing happens when I click on the NEW PROJECT button"',
            )

            if answer.button == "yes":
                answer = await self.ask_question(
                    FE_DONE_WITH_UI,
                    buttons={
                        "yes": "Yes, let's build the backend",
                        "no": "No, continue working on the UI",
                    },
                    buttons_only=True,
                    default="yes",
                )

                if answer.button == "yes":
                    fe_states = await self.state_manager.get_fe_states()
                    first_fe_state_id = fe_states[0].id if fe_states else None
                    last_fe_state_id = fe_states[-1].id if fe_states else None

                    await self.ui.clear_main_logs()
                    await self.ui.send_front_logs_headers(
                        str(first_fe_state_id) if first_fe_state_id else "fe_0",
                        ["E2 / T1", "done"],
                        "Building frontend",
                    )
                    await self.ui.send_back_logs(
                        [
                            {
                                "title": "Building frontend",
                                "project_state_id": str(first_fe_state_id) if first_fe_state_id else "fe_0",
                                "start_id": str(first_fe_state_id) if first_fe_state_id else "fe_0",
                                "end_id": str(last_fe_state_id) if last_fe_state_id else "fe_0",
                                "labels": ["E2 / T1", "Frontend", "done"],
                            }
                        ]
                    )
                    await self.ui.send_back_logs(
                        [
                            {
                                "title": "Setting up backend",
                                "disallow_reload": True,
                                "project_state_id": "be_0",
                                "labels": ["E2 / T2", "Backend setup", "working"],
                            }
                        ]
                    )
                    await self.ui.send_front_logs_headers("", ["E2 / T2", "working"], "Setting up backend")
                    return True
                elif answer.button == "no":
                    return False

            if answer.text:
                user_input = answer.text
                await self.send_message("Implementing the changes you suggested...")

        llm = self.get_llm(FRONTEND_AGENT_NAME)

        relevant_api_documentation = None

        if frontend_only:
            convo = AgentConvo(self).template(
                "is_relevant_for_docs_search",
                user_feedback=user_input,
            )

            response = await llm(convo)
            if str(response).lower() == "yes":
                error = None
                for attempt in range(3):
                    try:
                        url = urljoin(PYTHAGORA_API, "rag/search")
                        async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport()) as client:
                            resp = await client.post(
                                url,
                                json={"text": user_input, "project_id": str(self.state_manager.project.id)},
                                headers={"Authorization": f"Bearer {self.state_manager.get_access_token()}"},
                            )

                            if resp.status_code in [200]:
                                relevant_api_documentation = "\n".join(item["content"] for item in resp.json())
                                break
                            elif resp.status_code in [401, 403]:
                                access_token = await self.ui.send_token_expired()
                                self.state_manager.update_access_token(access_token)
                            else:
                                try:
                                    error = resp.json()["error"]
                                except Exception as e:
                                    error = e
                                log.warning(f"Failed to fetch from RAG service: {error}")
                                await self.send_message(
                                    f"Couldn't find any relevant API documentation. Retrying... \nError: {error}"
                                )

                    except Exception as e:
                        error = e
                        capture_exception(e)
                        log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True)
                if error:
                    await self.send_message(f"Please try reloading the project. \nError: {error}")
                    return None

        llm = self.get_llm(FRONTEND_AGENT_NAME, stream_output=True)

        # try relace first
        convo = AgentConvo(self).template(
            "iterate_frontend",
            description=self.current_state.epics[-1]["description"],
            user_feedback=user_input,
            relevant_api_documentation=relevant_api_documentation,
            first_time_build=False,
        )

        # replace system prompt because of relace
        convo.messages[0]["content"] = AgentConvo(self).render("system_relace")

        response = await llm(convo, parser=DescriptiveCodeBlockParser())

        relace_finished = await self.process_response(response.blocks, relace=True)

        if not relace_finished:
            log.debug("Relace didn't finish, reverting to build_frontend")
            convo = AgentConvo(self).template(
                "build_frontend",
                description=self.current_state.epics[-1]["description"],
                user_feedback=user_input,
                relevant_api_documentation=relevant_api_documentation,
                first_time_build=False,
            )

            response = await llm(convo, parser=DescriptiveCodeBlockParser())

            await self.process_response(response.blocks)

        convo.assistant(response.original_response)

        self.next_state.epics[-1]["messages"] = convo.messages
        self.next_state.epics[-1]["use_relace"] = relace_finished
        self.next_state.epics[-1]["fe_iteration_done"] = has_correct_num_of_backticks(response.original_response)
        self.next_state.epics[-1]["manual_iteration"] = True
        self.next_state.flag_epics_as_modified()

        return False

    async def end_frontend_iteration(self, finished: bool) -> AgentResponse:
        """
        Ends the frontend iteration.

        :param finished: Whether the frontend is fully built.
        :return: AgentResponse.done(self)
        """
        if finished:
            # TODO Add question if user app is fully finished
            self.next_state.action = FE_ITERATION_DONE

            self.next_state.complete_epic()
            await telemetry.trace_code_event(
                "frontend-finished",
                {
                    "description": self.current_state.epics[-1]["description"],
                    "messages": self.current_state.epics[-1]["messages"],
                },
            )

            if self.state_manager.git_available and self.state_manager.git_used:
                await self.git_commit(commit_message="Frontend finished")

            inputs = []
            for file in self.current_state.files:
                if not file.content:
                    continue
                input_required = self.state_manager.get_input_required(file.content.content, file.path)
                if input_required:
                    inputs += [{"file": file.path, "line": line} for line in input_required]

            if inputs:
                return AgentResponse.input_required(self, inputs)

        return AgentResponse.done(self)

    async def process_response(self, response_blocks: list, removed_mock: bool = False, relace: bool = False) -> bool:
        """
        Processes the response blocks from the LLM.

        :param response_blocks: The response blocks from the LLM.
        :return: AgentResponse.done(self)
        """
        for block in response_blocks:
            description = block.description.strip()
            content = block.content.strip()

            # Split description into lines and check the last line for file path
            description_lines = description.split("\n")
            last_line = description_lines[-1].strip()

            if "file:" in last_line:
                # Extract file path from the last line - get everything after "file:"
                file_path = last_line[last_line.index("file:") + 5 :].strip()
                file_path = file_path.strip("\"'`")
                # Skip empty file paths
                if file_path.strip() == "":
                    continue
                new_content = content
                old_content = self.current_state.get_file_content_by_path(file_path)

                if relace:
                    llm = self.get_llm(IMPLEMENT_CHANGES_AGENT_NAME)
                    convo = Convo().user(
                        {
                            "initialCode": old_content,
                            "editSnippet": new_content,
                        }
                    )

                    new_content = await llm(convo, temperature=0, parser=OptionalCodeBlockParser())

                    if not new_content or new_content == ("", 0, 0):
                        return False

                n_new_lines, n_del_lines = self.get_line_changes(old_content, new_content)
                await self.ui.send_file_status(file_path, "done", source=self.ui_source)
                await self.ui.generate_diff(
                    file_path, old_content, new_content, n_new_lines, n_del_lines, source=self.ui_source
                )
                if not removed_mock and self.current_state.branch.project.project_type == "swagger":
                    if "client/src/api" in file_path:
                        if not self.next_state.epics[-1].get("file_paths_to_remove_mock"):
                            self.next_state.epics[-1]["file_paths_to_remove_mock"] = []
                        self.next_state.epics[-1]["file_paths_to_remove_mock"].append(file_path)

                await self.state_manager.save_file(file_path, new_content)

            elif "command:" in last_line:
                # Split multiple commands and execute them sequentially
                commands = content.strip().split("\n")
                for command in commands:
                    command = command.strip()
                    if command:
                        # Add "cd client" prefix if not already present
                        if not command.startswith("cd "):
                            command = f"cd client && {command}"
                        if "run start" in command or "run dev" in command:
                            continue

                        # if command is cd client && some_command client/ -> won't work, we need to remove client/ after &&
                        prefix, cmd_part = command.split("&&", 1)
                        cmd_part = cmd_part.strip().replace("client/", "")
                        command = f"{prefix} && {cmd_part}"

                        # check if cmd_part contains npm run something, if that something is not in scripts, then skip it
                        if "npm run" in cmd_part:
                            npm_script = cmd_part.split("npm run")[1].strip()

                            absolute_path = os.path.join(
                                self.state_manager.get_full_project_root(),
                                os.path.join(
                                    "client" if "client" in prefix else "server" if "server" in prefix else "",
                                    "package.json",
                                ),
                            )
                            with open(absolute_path, "r") as file:
                                package_json = json.load(file)
                                if npm_script not in package_json.get("scripts", {}):
                                    log.warning(
                                        f"Skipping command: {command} as npm script {npm_script} not found, command is {command}"
                                    )
                                    continue

                        await self.send_message(f"Running command: `{command}`...")
                        await self.process_manager.run_command(command)
            else:
                log.info(f"Unknown block description: {description}")

        return True

    async def remove_mock(self):
        """
        Remove mock API from the backend and replace it with api endpoints defined in the external documentation
        """
        new_file_paths = self.current_state.epics[-1]["file_paths_to_remove_mock"]
        llm = self.get_llm(FRONTEND_AGENT_NAME)

        for file_path in new_file_paths:
            old_content = self.current_state.get_file_content_by_path(file_path)

            convo = AgentConvo(self).template("create_rag_query", file_content=old_content)
            topics = await llm(convo)

            if topics != "None":
                error = None
                for attempt in range(3):
                    try:
                        url = urljoin(PYTHAGORA_API, "rag/search")
                        async with httpx.AsyncClient(transport=httpx.AsyncHTTPTransport()) as client:
                            resp = await client.post(
                                url,
                                json={"text": topics, "project_id": str(self.state_manager.project.id)},
                                headers={"Authorization": f"Bearer {self.state_manager.get_access_token()}"},
                            )
                            if resp.status_code == 200:
                                resp_json = resp.json()
                                relevant_api_documentation = "\n".join(item["content"] for item in resp_json)

                                referencing_files = await self.state_manager.get_referencing_files(
                                    self.current_state, file_path
                                )

                                convo = AgentConvo(self).template(
                                    "remove_mock",
                                    relevant_api_documentation=relevant_api_documentation,
                                    file_content=old_content,
                                    file_path=file_path,
                                    referencing_files=referencing_files,
                                    lines=len(old_content.splitlines()),
                                )

                                response = await llm(convo, parser=DescriptiveCodeBlockParser())
                                response_blocks = response.blocks
                                convo.assistant(response.original_response)
                                await self.process_response(response_blocks, removed_mock=True)
                                self.next_state.epics[-1]["file_paths_to_remove_mock"].remove(file_path)
                                break
                            elif resp.status_code in [401, 403]:
                                access_token = await self.ui.send_token_expired()
                                self.state_manager.update_access_token(access_token)
                            else:
                                try:
                                    error = resp.json()["error"]
                                except Exception as e:
                                    error = e
                                log.warning(f"Failed to fetch from RAG service: {error}")
                                await self.send_message(
                                    f"I couldn't find any relevant API documentation. Retrying... \nError: {error}"
                                )
                    except Exception as e:
                        capture_exception(e)
                        log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True)
                if error:
                    await self.send_message(f"Please try reloading the project. \nError: {error}")
                    return None

        return False

    async def set_app_details(self):
        """
        Sets the app details.
        """
        command = "npm run start"
        app_link = "http://localhost:5173"

        self.next_state.run_command = command
        # todo store app link and send whenever we are sending run_command
        # self.next_state.app_link = app_link
        await self.ui.send_run_command(command)
        await self.ui.send_app_link(app_link)

    async def kill_app(self):
        is_win = sys.platform.lower().startswith("win")
        # TODO make ports configurable
        # kill frontend - both swagger and node
        if is_win:
            await self.process_manager.run_command(
                """for /f "tokens=5" %a in ('netstat -ano ^| findstr :5173 ^| findstr LISTENING') do taskkill /F /PID %a""",
                show_output=False,
            )
        else:
            await self.process_manager.run_command("lsof -ti:5173 | xargs -r kill", show_output=False)

        # if node project, kill backend as well
        if self.state_manager.project.project_type == "node":
            if is_win:
                await self.process_manager.run_command(
                    """for /f "tokens=5" %a in ('netstat -ano ^| findstr :3000 ^| findstr LISTENING') do taskkill /F /PID %a""",
                    show_output=False,
                )
            else:
                await self.process_manager.run_command("lsof -ti:3000 | xargs -r kill", show_output=False)

    async def try_auto_debug(self) -> str:
        if not self.state_manager.fe_auto_debug:
            self.state_manager.fe_auto_debug = True
            return ""
        if self.next_state.epics[-1].get("auto_debug_attempts", 0) >= 3:
            return ""

        count = 3

        try:
            await self.send_message(
                f"### Auto-debugging the frontend #{self.next_state.epics[-1]['auto_debug_attempts']+1}"
            )
            self.next_state.epics[-1]["auto_debug_attempts"] = (
                self.current_state.epics[-1].get("auto_debug_attempts", 0) + 1
            )
            # kill app
            await self.kill_app()

            npm_proc = await self.process_manager.start_process("npm run start &", show_output=False)

            while True:
                if count == 3:
                    await asyncio.sleep(5)
                else:
                    await asyncio.sleep(2)
                diff_stdout, diff_stderr = await npm_proc.read_output()
                if (diff_stdout == "" and diff_stderr == "") or count <= 0:
                    break
                count -= 1

            await self.process_manager.run_command("curl http://localhost:5173", show_output=False)
            await asyncio.sleep(1)

            diff_stdout, diff_stderr = await npm_proc.read_output()

            # kill app again
            await self.kill_app()

            if diff_stdout or diff_stderr:
                await self.send_message(f"### Auto-debugging found an error: \n{diff_stdout}\n{diff_stderr}")
                log.debug(f"Auto-debugging output:\n{diff_stdout}\n{diff_stderr}")
                return f"I got an error. Here are the logs:\n{diff_stdout}\n{diff_stderr}"
        except Exception as e:
            capture_exception(e)
            log.error(f"Error during auto-debugging: {e}", exc_info=True)

        await self.send_message("### All good, no errors found.")
        return ""


================================================
FILE: core/agents/git.py
================================================
import os
from typing import Optional

from core.agents.convo import AgentConvo
from core.config.magic_words import GITIGNORE_CONTENT
from core.ui.base import pythagora_source


class GitMixin:
    """
    Mixin class for git commands
    """

    async def check_git_installed(self) -> bool:
        """Check if git is installed on the system."""
        status_code, _, _ = await self.process_manager.run_command("git --version", show_output=False)
        git_available = status_code == 0
        self.state_manager.git_available = git_available
        return git_available

    async def is_git_initialized(self) -> bool:
        """Check if git is initialized in the workspace."""
        workspace_path = self.state_manager.get_full_project_root()

        status_code, _, _ = await self.process_manager.run_command(
            "git rev-parse --git-dir --is-inside-git-dir",
            cwd=workspace_path,
            show_output=False,
        )
        # Will return status code 0 only if .git exists in the current directory
        git_used = status_code == 0 and os.path.exists(os.path.join(workspace_path, ".git"))
        self.state_manager.git_used = git_used
        return git_used

    async def init_git_if_needed(self) -> bool:
        """
        Initialize git repository if it hasn't been initialized yet.
        Returns True if initialization was needed and successful.
        """

        workspace_path = self.state_manager.get_full_project_root()
        if await self.is_git_initialized():
            return False

        answer = await self.ui.ask_question(
            "Git is not initialized for this project. Do you want to initialize it now?",
            buttons={"yes": "Yes", "no": "No"},
            default="yes",
            buttons_only=True,
            source=pythagora_source,
        )

        if answer.button == "no":
            return False
        else:
            status_code, _, stderr = await self.process_manager.run_command("git init", cwd=workspace_path)
            if status_code != 0:
                raise RuntimeError(f"Failed to initialize git repository: {stderr}")

            gitignore_path = os.path.join(workspace_path, ".gitignore")
            try:
                with open(gitignore_path, "w") as f:
                    f.write(GITIGNORE_CONTENT)
            except Exception as e:
                raise RuntimeError(f"Failed to create .gitignore file: {str(e)}")

            # First check if there are any changes to commit
            status_code, stdout, stderr = await self.process_manager.run_command(
                "git status --porcelain",
                cwd=workspace_path,
            )

            if status_code == 0 and stdout.strip():  # If there are changes (stdout is not empty)
                # Stage all files
                status_code, _, stderr = await self.process_manager.run_command(
                    "git add .",
                    cwd=workspace_path,
                )
                if status_code != 0:
                    raise RuntimeError(f"Failed to stage files: {stderr}")

                # Create initial commit
                status_code, _, stderr = await self.process_manager.run_command(
                    'git commit -m "initial commit"', cwd=workspace_path
                )
                if status_code != 0:
                    raise RuntimeError(f"Failed to create initial commit: {stderr}")

            self.state_manager.git_used = True
            return True

    async def git_commit(self, commit_message: Optional[str] = None) -> None:
        """
        Create a git commit with the specified message. Commit message is optional.
        Raises RuntimeError if the commit fails.
        """
        workspace_path = self.state_manager.get_full_project_root()

        # Check if there are any changes to commit
        status_code, git_status, stderr = await self.process_manager.run_command(
            "git status --porcelain",
            cwd=workspace_path,
            show_output=False,
        )
        if status_code != 0:
            raise RuntimeError(f"Failed to get git status: {stderr}")

        if not git_status.strip():
            return

        answer = await self.ui.ask_question(
            "Do you want to create new git commit?",
            buttons={"yes": "Yes", "no": "No"},
            default="yes",
            buttons_only=True,
            source=pythagora_source,
        )

        if answer.button == "no":
            return

        # Stage all changes
        status_code, _, stderr = await self.process_manager.run_command("git add .", cwd=workspace_path)
        if status_code != 0:
            raise RuntimeError(f"Failed to stage changes: {stderr}")

        # Get git diff
        status_code, git_diff, stderr = await self.process_manager.run_command(
            "git diff --cached || git diff",
            cwd=workspace_path,
            show_output=False,
        )
        if status_code != 0:
            raise RuntimeError(f"Failed to create initial commit: {stderr}")

        if not commit_message:
            llm = self.get_llm()
            convo = AgentConvo(self).template(
                "commit",
                git_diff=git_diff,
            )
            commit_message: str = await llm(convo)

        answer = await self.ui.ask_question(
            f"Do you accept this 'git commit' message? Here is suggested message: '{commit_message}'",
            buttons={"yes": "Yes", "edit": "Edit", "no": "No, I don't want to commit changes."},
            default="yes",
            buttons_only=True,
            source=pythagora_source,
        )

        if answer.button == "no":
            return
        elif answer.button == "edit":
            user_message = await self.ui.ask_question(
                "Please enter the commit message",
                source=pythagora_source,
                initial_text=commit_message,
            )
            commit_message = user_message.text

        # Create commit
        status_code, _, stderr = await self.process_manager.run_command(
            f'git commit -m "{commit_message}"', cwd=workspace_path
        )
        if status_code != 0:
            raise RuntimeError(f"Failed to create commit: {stderr}")


================================================
FILE: core/agents/human_input.py
================================================
from core.agents.base import BaseAgent
from core.agents.response import AgentResponse, ResponseType
from core.config.actions import CONTINUE_WHEN_DONE, HUMAN_INTERVENTION_QUESTION


class HumanInput(BaseAgent):
    agent_type = "human-input"
    display_name = "Human Input"

    async def run(self) -> AgentResponse:
        if self.prev_response and self.prev_response.type == ResponseType.INPUT_REQUIRED:
            return await self.input_required(self.prev_response.data.get("files", []))

        return await self.human_intervention(self.step)

    async def human_intervention(self, step) -> AgentResponse:
        description = step["human_intervention_description"]

        await self.send_message(f"## {HUMAN_INTERVENTION_QUESTION}\n\n{description}")
        await self.ask_question(
            CONTINUE_WHEN_DONE,
            buttons={"continue": "Continue"},
            default="continue",
            buttons_only=True,
        )
        self.next_state.complete_step("human_intervention")
        return AgentResponse.done(self)

    async def input_required(self, files: list[dict]) -> AgentResponse:
        for item in files:
            file = item["file"]
            line = item["line"]

            # FIXME: this is an ugly hack, we shouldn't need to know how to get to VFS and
            # anyways the full path is only available for local vfs, so this is doubly wrong;
            # instead, we should just send the relative path to the extension and it should
            # figure out where its local files are and how to open it.
            full_path = self.state_manager.file_system.get_full_path(file)

            await self.ui.open_editor(full_path, line, True)
        return AgentResponse.done(self)


================================================
FILE: core/agents/importer.py
================================================
from uuid import uuid4

from core.agents.base import BaseAgent
from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse, ResponseType
from core.db.models import Complexity
from core.llm.parser import JSONParser
from core.log import get_logger
from core.telemetry import telemetry
from core.templates.example_project import EXAMPLE_PROJECT_DESCRIPTION

log = get_logger(__name__)

MAX_PROJECT_LINES = 10000


class Importer(BaseAgent):
    agent_type = "importer"
    display_name = "Project Analyist"

    async def run(self) -> AgentResponse:
        if self.prev_response and self.prev_response.type == ResponseType.IMPORT_PROJECT:
            # Called by SpecWriter to start the import process
            await self.start_import_process()
            return AgentResponse.describe_files(self)

        await self.analyze_project()
        return AgentResponse.done(self)

    async def start_import_process(self):
        # TODO: Send a signal to the UI to copy the project files to workspace
        project_root = self.state_manager.get_full_project_root()
        await self.ui.import_project(project_root)
        await self.send_message(
            f"This is experimental feature and is currently limited to projects with size up to {MAX_PROJECT_LINES} lines of code."
        )

        await self.ask_question(
            f"Please copy your project files to {project_root} and press Continue",
            allow_empty=False,
            buttons={
                "continue": "Continue",
            },
            buttons_only=True,
            default="continue",
        )

        imported_files, _ = await self.state_manager.import_files()
        imported_lines = sum(len(f.content.content.splitlines()) for f in imported_files)
        if imported_lines > MAX_PROJECT_LINES:
            await self.send_message(
                "WARNING: Your project ({imported_lines} LOC) is larger than supported and may cause issues in Pythagora."
            )
        await self.state_manager.commit()

    async def analyze_project(self):
        llm = self.get_llm(stream_output=True)

        self.send_message("Inspecting most important project files ...")

        convo = AgentConvo(self).template("get_entrypoints")
        llm_response = await llm(convo, parser=JSONParser())
        relevant_files = [f for f in self.current_state.files if f.path in llm_response]

        self.send_message("Analyzing project ...")

        convo = AgentConvo(self).template(
            "analyze_project", relevant_files=relevant_files, example_spec=EXAMPLE_PROJECT_DESCRIPTION
        )
        llm_response = await llm(convo)

        spec = self.current_state.specification.clone()
        spec.description = llm_response
        self.next_state.specification = spec
        self.next_state.epics = [
            {
                "id": uuid4().hex,
                "name": "Import project",
                "description": "Import an existing project into Pythagora",
                "tasks": [],
                "completed": True,
                "test_instructions": None,
                "source": "app",
                "summary": None,
                "complexity": Complexity.HARD if len(self.current_state.files) > 5 else Complexity.SIMPLE,
            }
        ]

        n_lines = sum(len(f.content.content.splitlines()) for f in self.current_state.files)
        await telemetry.trace_code_event(
            "existing-project",
            {
                "num_files": len(self.current_state.files),
                "num_lines": n_lines,
                "description": llm_response,
            },
        )


================================================
FILE: core/agents/legacy_handler.py
================================================
from core.agents.base import BaseAgent
from core.agents.response import AgentResponse


class LegacyHandler(BaseAgent):
    agent_type = "legacy-handler"
    display_name = "Legacy Handler"

    async def run(self) -> AgentResponse:
        if self.data["type"] == "review_task":
            self.next_state.complete_step("review_task")
            return AgentResponse.done(self)

        raise ValueError(f"Unknown reason for calling Legacy Handler with data: {self.data}")


================================================
FILE: core/agents/mixins.py
================================================
import asyncio
import json
from typing import List, Optional

from pydantic import BaseModel, Field

from core.agents.convo import AgentConvo
from core.agents.response import AgentResponse
from core.cli.helpers import get_line_changes
from core.config import GET_RELEVANT_FILES_AGENT_NAME, TASK_BREAKDOWN_AGENT_NAME, TROUBLESHOOTER_BUG_REPORT
from core.config.actions import MIX_BREAKDOWN_CHAT_PROMPT
from core.config.constants import CONVO_ITERATIONS_LIMIT
from core.config.magic_words import ALWAYS_RELEVANT_FILES
from core.llm.parser import JSONParser
from core.log import get_logger
from core.ui.base import ProjectStage

log = get_logger(__name__)


class RelevantFiles(BaseModel):
    relevant_files: Optional[List[str]] = Field(
        description="List of files you want to add to the list of relevant files."
    )


class Test(BaseModel):
    title: str = Field(description="Very short title of the test.")
    action: str = Field(description="More detailed description of what actions have to be taken to test the app.")
    result: str = Field(description="Expected result that verifies successful test.")


class TestSteps(BaseModel):
    steps: List[Test]


class ChatWithBreakdownMixin:
    """
    Provides a method to chat with the user and provide a breakdown of the conversation.
    """

    async def chat_with_breakdown(self, convo: AgentConvo, breakdown: str) -> AgentConvo:
        """
        Chat with the user and provide a breakdown of the conversation.

        :param convo: The conversation object.
        :param breakdown: The breakdown of the conversation.
        :return: The breakdown.
        """

        llm = self.get_llm(TASK_BREAKDOWN_AGENT_NAME, stream_output=True)
        while True:
            await self.ui.send_project_stage(
                {
                    "stage": ProjectStage.BREAKDOWN_CHAT,
                    "agent": self.agent_type,
                }
            )

            if self.state_manager.auto_confirm_breakdown:
                break

            chat = await self.ask_question(
                MIX_BREAKDOWN_CHAT_PROMPT,
                buttons={"yes": "Yes, looks good!"},
                default="yes",
                verbose=False,
            )
            if chat.button == "yes":
                break

            if len(convo.messages) > CONVO_ITERATIONS_LIMIT:
                convo.slice(3, CONVO_ITERATIONS_LIMIT)

            convo.user(chat.text)
            breakdown: str = await llm(convo)
            convo.assistant(breakdown)

        return breakdown


class IterationPromptMixin:
    """
    Provides a method to find a solution to a problem based on user feedback.

    Used by ProblemSolver and Troubleshooter agents.
    """

    async def find_solution(
        self,
        user_feedback: str,
        *,
        user_feedback_qa: Optional[list[str]] = None,
        next_solution_to_try: Optional[str] = None,
        bug_hunting_cycles: Optional[dict] = None,
    ) -> str:
        """
        Generate a new solution for the problem the user reported.

        :param user_feedback: User feedback about the problem.
        :param user_feedback_qa: Additional q/a about the problem provided by the user (optional).
        :param next_solution_to_try: Hint from ProblemSolver on which solution to try (optional).
        :param bug_hunting_cycles: Data about logs that need to be added to the code (optional).
        :return: The generated solution to the problem.
        """
        llm = self.get_llm(TROUBLESHOOTER_BUG_REPORT, stream_output=True)
        convo = AgentConvo(self).template(
            "iteration",
            user_feedback=user_feedback,
            user_feedback_qa=user_feedback_qa,
            next_solution_to_try=next_solution_to_try,
            bug_hunting_cycles=bug_hunting_cycles,
            test_instructions=json.loads(self.current_state.current_task.get("test_instructions", "[]")),
        )
        llm_solution: str = await llm(convo)

        llm_solution = await self.chat_with_breakdown(convo, llm_solution)

        return llm_solution


class RelevantFilesMixin:
    """
    Asynchronously retrieves relevant files for the current task by separating front-end and back-end files, and processing them in parallel.

    This method initiates two asynchronous tasks to fetch relevant files for the front-end (client) and back-end (server) respectively.
    It then combines the results, filters out any non-existing files, and updates the current and next state with the relevant files.
    """

    async def get_relevant_files_parallel(
        self, user_feedback: Optional[str] = None, solution_description: Optional[str] = None
    ) -> AgentResponse:
        tasks = [
            self.get_relevant_files(
                user_feedback=user_feedback, solution_description=solution_description, dir_type="client"
            ),
            self.get_relevant_files(
                user_feedback=user_feedback, solution_description=solution_description, dir_type="server"
            ),
        ]

        responses = await asyncio.gather(*tasks)

        relevant_files = [item for sublist in responses for item in sublist]

        existing_files = {file.path for file in self.current_state.files}
        relevant_files = [path for path in relevant_files if path in existing_files]
        self.current_state.relevant_files = relevant_files
        self.next_state.relevant_files = relevant_files

        return AgentResponse.done(self)

    async def get_relevant_files(
        self,
        user_feedback: Optional[str] = None,
        solution_description: Optional[str] = None,
        dir_type: Optional[str] = None,
    ) -> list[str]:
        log.debug(
            "Getting relevant files for the current task for: " + ("frontend" if dir_type == "client" else "backend")
        )
        relevant_files = set()
        llm = self.get_llm(GET_RELEVANT_FILES_AGENT_NAME)
        convo = (
            AgentConvo(self)
            .template(
                "filter_files",
                user_feedback=user_feedback,
                solution_description=solution_description,
                relevant_files=relevant_files,
                dir_type=dir_type,
            )
            .require_schema(RelevantFiles)
        )
        llm_response: RelevantFiles = await llm(convo, parser=JSONParser(RelevantFiles), temperature=0)
        existing_files = {file.path for file in self.current_state.files}
        if not llm_response.relevant_files:
            return []
        paths_list = [path for path in llm_response.relevant_files if path in existing_files]

        try:
            for file_path in ALWAYS_RELEVANT_FILES:
                if file_path not in paths_list and file_path in existing_files:
                    paths_list.append(file_path)
        except Exception as e:
            log.error(f"Error while getting most important files: {e}")

        return paths_list


class FileDiffMixin:
    """
    Provides a method to generate a diff between two files.
    """

    def get_line_changes(self, old_content: str, new_content: str) -> tuple[int, int]:
        """
        Get the number of added and deleted lines between two files.

        This uses Python difflib to produce a unified diff, then counts
        the number of added and deleted lines.

        :param old_content: old file content
        :param new_content: new file content
        :return: a tuple (n_new_lines, n_del_lines)
        """

        return get_line_changes(old_content, new_content)


================================================
FILE: core/agents/orchestrator.py
================================================
import asyncio
import json
import os
import re
from typing import List, Optional, Union

from core.agents.architect import Architect
from core.agents.base import BaseAgent
from core.agents.bug_hunter import BugHunter
from core.agents.code_monkey import CodeMonkey
from core.agents.developer import Developer
from core.agents.error_handler import ErrorHandler
from core.agents.executor import Executor
from core.agents.external_docs import ExternalDocumentation
from core.agents.frontend import Frontend
from core.agents.git import GitMixin
from core.agents.human_input import HumanInput
from core.agents.importer import Importer
from core.agents.legacy_handler import LegacyHandler
from core.agents.problem_solver import ProblemSolver
from core.agents.response import AgentResponse, ResponseType
from core.agents.spec_writer import SpecWriter
from core.agents.task_completer import TaskCompleter
from core.agents.tech_lead import TechLead
from core.agents.tech_writer import TechnicalWriter
from core.agents.troubleshooter import Troubleshooter
from core.agents.wizard import Wizard
from core.db.models.project_state import IterationStatus, TaskStatus
from core.log import get_logger
from core.telemetry import telemetry
from core.ui.base import UserInterruptError

log = get_logger(__name__)


class Orchestrator(BaseAgent, GitMixin):
    """
    Main agent that controls the flow of the process.

    Based on the current state of the project, the orchestrator invokes
    all other agents. It is also responsible for determining when each
    step is done and the project state needs to be committed to the database.
    """

    agent_type = "orchestrator"
    display_name = "Orchestrator"

    async def run(self) -> bool:
        """
        Run the Orchestrator agent.

        :return: True if the Orchestrator exited successfully, False otherwise.
        """
        response = None

        log.info(f"Starting {__name__}.Orchestrator")

        self.executor = Executor(self.state_manager, self.ui)
        self.process_manager = self.executor.process_manager
        # self.chat = Chat() TODO

        await self.init_ui()
        await self.offline_changes_check()

        await self.install_dependencies()

        if self.args.use_git and await self.check_git_installed():
            await self.init_git_if_needed()

        await self.set_fro
Download .txt
gitextract_kaxuv8xc/

├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.yml
│   │   ├── feature-request.yml
│   │   └── howto.yml
│   ├── copyright_template.txt
│   ├── ip_assignment.yml
│   └── workflows/
│       ├── ci.yml
│       └── cloud-staging-build.yaml
├── .gitignore
├── .pre-commit-config.yaml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── cloud/
│   ├── config-docker.json
│   ├── entrypoint.sh
│   ├── on-event-extension-install.sh
│   ├── posthog.html
│   ├── settings.json
│   └── setup-dependencies.sh
├── core/
│   ├── agents/
│   │   ├── __init__.py
│   │   ├── architect.py
│   │   ├── base.py
│   │   ├── bug_hunter.py
│   │   ├── code_monkey.py
│   │   ├── convo.py
│   │   ├── developer.py
│   │   ├── error_handler.py
│   │   ├── executor.py
│   │   ├── external_docs.py
│   │   ├── frontend.py
│   │   ├── git.py
│   │   ├── human_input.py
│   │   ├── importer.py
│   │   ├── legacy_handler.py
│   │   ├── mixins.py
│   │   ├── orchestrator.py
│   │   ├── problem_solver.py
│   │   ├── response.py
│   │   ├── spec_writer.py
│   │   ├── task_completer.py
│   │   ├── tech_lead.py
│   │   ├── tech_writer.py
│   │   ├── troubleshooter.py
│   │   └── wizard.py
│   ├── cli/
│   │   ├── __init__.py
│   │   ├── helpers.py
│   │   └── main.py
│   ├── config/
│   │   ├── __init__.py
│   │   ├── actions.py
│   │   ├── constants.py
│   │   ├── env_importer.py
│   │   ├── magic_words.py
│   │   ├── user_settings.py
│   │   └── version.py
│   ├── db/
│   │   ├── __init__.py
│   │   ├── alembic.ini
│   │   ├── fix_migrations.py
│   │   ├── migrations/
│   │   │   ├── README
│   │   │   ├── env.py
│   │   │   ├── script.py.mako
│   │   │   └── versions/
│   │   │       ├── 0173e14719aa_move_metadata_from_file_to_file_content_.py
│   │   │       ├── 0173e14719aa_vacuum_database.py
│   │   │       ├── 08d71952ec2f_refactor_specification_template_to_.py
│   │   │       ├── 0a1bb637fa26_initial.py
│   │   │       ├── 3968d770dced_add_project_type_to_project.py
│   │   │       ├── 675268601278_add_chat_messages_and_convos.py
│   │   │       ├── 69e50fdaf067_move_knowledge_base_to_separate_table.py
│   │   │       ├── b760f66138c0_add_docs_column_to_project_states.py
│   │   │       ├── c8905d4ce784_add_original_description_and_template_.py
│   │   │       ├── f352dbe45751_make_relevant_files_nullable.py
│   │   │       ├── f708791b9270_adding_knowledge_base_field_to_.py
│   │   │       └── ff891d366761_add_example_project_to_spec.py
│   │   ├── models/
│   │   │   ├── __init__.py
│   │   │   ├── base.py
│   │   │   ├── branch.py
│   │   │   ├── chat_convo.py
│   │   │   ├── chat_message.py
│   │   │   ├── exec_log.py
│   │   │   ├── file.py
│   │   │   ├── file_content.py
│   │   │   ├── knowledge_base.py
│   │   │   ├── llm_request.py
│   │   │   ├── project.py
│   │   │   ├── project_state.py
│   │   │   ├── specification.py
│   │   │   └── user_input.py
│   │   ├── session.py
│   │   ├── setup.py
│   │   └── v0importer.py
│   ├── disk/
│   │   ├── __init__.py
│   │   ├── ignore.py
│   │   └── vfs.py
│   ├── llm/
│   │   ├── __init__.py
│   │   ├── anthropic_client.py
│   │   ├── azure_client.py
│   │   ├── base.py
│   │   ├── convo.py
│   │   ├── groq_client.py
│   │   ├── openai_client.py
│   │   ├── parser.py
│   │   ├── prompt.py
│   │   ├── relace_client.py
│   │   └── request_log.py
│   ├── log/
│   │   └── __init__.py
│   ├── proc/
│   │   ├── __init__.py
│   │   ├── exec_log.py
│   │   └── process_manager.py
│   ├── prompts/
│   │   ├── architect/
│   │   │   ├── configure_template.prompt
│   │   │   ├── select_templates.prompt
│   │   │   ├── system.prompt
│   │   │   └── technologies.prompt
│   │   ├── bug-hunter/
│   │   │   ├── ask_a_question.prompt
│   │   │   ├── bug_found_or_add_logs.prompt
│   │   │   ├── data_about_logs.prompt
│   │   │   ├── get_bug_reproduction_instructions.prompt
│   │   │   ├── instructions_from_human_hint.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── log_data.prompt
│   │   │   ├── problem_explanation.prompt
│   │   │   ├── system.prompt
│   │   │   └── tell_me_more.prompt
│   │   ├── chat-agent/
│   │   │   ├── chat.prompt
│   │   │   └── system.prompt
│   │   ├── code-monkey/
│   │   │   ├── breakdown.prompt
│   │   │   ├── describe_file.prompt
│   │   │   ├── implement_changes.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── review_changes.prompt
│   │   │   ├── review_feedback.prompt
│   │   │   └── system.prompt
│   │   ├── developer/
│   │   │   ├── breakdown.prompt
│   │   │   ├── filter_files.prompt
│   │   │   ├── iteration.prompt
│   │   │   ├── parse_task.prompt
│   │   │   └── system.prompt
│   │   ├── error-handler/
│   │   │   └── debug.prompt
│   │   ├── executor/
│   │   │   └── ran_command.prompt
│   │   ├── external-docs/
│   │   │   ├── create_docs_queries.prompt
│   │   │   ├── select_docset.prompt
│   │   │   └── system.prompt
│   │   ├── frontend/
│   │   │   ├── build_frontend.prompt
│   │   │   ├── create_rag_query.prompt
│   │   │   ├── is_relevant_for_docs_search.prompt
│   │   │   ├── iterate_frontend.prompt
│   │   │   ├── remove_mock.prompt
│   │   │   ├── system.prompt
│   │   │   └── system_relace.prompt
│   │   ├── importer/
│   │   │   ├── analyze_project.prompt
│   │   │   └── get_entrypoints.prompt
│   │   ├── partials/
│   │   │   ├── breakdown_code_instructions.prompt
│   │   │   ├── coding_rules.prompt
│   │   │   ├── doc_snippets.prompt
│   │   │   ├── execution_order.prompt
│   │   │   ├── features_list.prompt
│   │   │   ├── file_naming.prompt
│   │   │   ├── file_size_limit.prompt
│   │   │   ├── files_descriptions.prompt
│   │   │   ├── files_list.prompt
│   │   │   ├── files_list_relevant.prompt
│   │   │   ├── human_intervention_explanation.prompt
│   │   │   ├── project_details.prompt
│   │   │   ├── project_tasks.prompt
│   │   │   ├── relative_paths.prompt
│   │   │   └── user_feedback.prompt
│   │   ├── problem-solver/
│   │   │   ├── get_alternative_solutions.prompt
│   │   │   ├── iteration.prompt
│   │   │   └── system.prompt
│   │   ├── pythagora/
│   │   │   └── commit.prompt
│   │   ├── spec-writer/
│   │   │   ├── add_new_feature.prompt
│   │   │   ├── add_to_specification.prompt
│   │   │   ├── ask_questions.prompt
│   │   │   ├── build_full_specification.prompt
│   │   │   ├── need_auth.prompt
│   │   │   ├── project_name.prompt
│   │   │   ├── prompt_complexity.prompt
│   │   │   ├── review_spec.prompt
│   │   │   └── system.prompt
│   │   ├── tech-lead/
│   │   │   ├── epic_breakdown.prompt
│   │   │   ├── filter_files.prompt
│   │   │   ├── plan.prompt
│   │   │   └── system.prompt
│   │   ├── tech-writer/
│   │   │   ├── create_readme.prompt
│   │   │   └── system.prompt
│   │   └── troubleshooter/
│   │       ├── breakdown.prompt
│   │       ├── bug_report.prompt
│   │       ├── define_user_review_goal.prompt
│   │       ├── filter_files.prompt
│   │       ├── get_route_files.prompt
│   │       ├── get_run_command.prompt
│   │       ├── iteration.prompt
│   │       └── system.prompt
│   ├── state/
│   │   ├── __init__.py
│   │   └── state_manager.py
│   ├── telemetry/
│   │   └── __init__.py
│   ├── templates/
│   │   ├── __init__.py
│   │   ├── base.py
│   │   ├── example_project.py
│   │   ├── info/
│   │   │   ├── javascript_react/
│   │   │   │   └── summary.tpl
│   │   │   ├── node_express_mongoose/
│   │   │   │   └── summary.tpl
│   │   │   ├── react_express/
│   │   │   │   └── summary.tpl
│   │   │   ├── vite_react/
│   │   │   │   └── summary.tpl
│   │   │   └── vite_react_swagger/
│   │   │       └── summary.tpl
│   │   ├── javascript_react.py
│   │   ├── node_express_mongoose.py
│   │   ├── react_express.py
│   │   ├── registry.py
│   │   ├── render.py
│   │   ├── tree/
│   │   │   ├── add_raw_tags.py
│   │   │   ├── javascript_react/
│   │   │   │   ├── .eslintrc.cjs
│   │   │   │   ├── .gitignore
│   │   │   │   ├── index.html
│   │   │   │   ├── package.json
│   │   │   │   ├── public/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── src/
│   │   │   │   │   ├── App.css
│   │   │   │   │   ├── App.jsx
│   │   │   │   │   ├── assets/
│   │   │   │   │   │   └── .gitkeep
│   │   │   │   │   ├── index.css
│   │   │   │   │   └── main.jsx
│   │   │   │   └── vite.config.js
│   │   │   ├── node_express_mongoose/
│   │   │   │   ├── models/
│   │   │   │   │   └── User.js
│   │   │   │   ├── package.json
│   │   │   │   ├── public/
│   │   │   │   │   ├── css/
│   │   │   │   │   │   └── style.css
│   │   │   │   │   └── js/
│   │   │   │   │       └── main.js
│   │   │   │   ├── routes/
│   │   │   │   │   ├── authRoutes.js
│   │   │   │   │   └── middleware/
│   │   │   │   │       └── authMiddleware.js
│   │   │   │   ├── server.js
│   │   │   │   ├── services/
│   │   │   │   │   └── llm.js
│   │   │   │   └── views/
│   │   │   │       ├── index.ejs
│   │   │   │       ├── login.ejs
│   │   │   │       ├── partials/
│   │   │   │       │   ├── _footer.ejs
│   │   │   │       │   ├── _head.ejs
│   │   │   │       │   └── _header.ejs
│   │   │   │       └── register.ejs
│   │   │   ├── react_express/
│   │   │   │   ├── .babelrc
│   │   │   │   ├── .eslintrc.json
│   │   │   │   ├── .gitignore
│   │   │   │   ├── README.md
│   │   │   │   ├── api/
│   │   │   │   │   ├── app.js
│   │   │   │   │   ├── middlewares/
│   │   │   │   │   │   ├── authMiddleware.js
│   │   │   │   │   │   └── errorMiddleware.js
│   │   │   │   │   ├── models/
│   │   │   │   │   │   ├── init.js
│   │   │   │   │   │   └── user.js
│   │   │   │   │   ├── routes/
│   │   │   │   │   │   ├── authRoutes.js
│   │   │   │   │   │   └── index.js
│   │   │   │   │   ├── services/
│   │   │   │   │   │   └── userService.js
│   │   │   │   │   └── utils/
│   │   │   │   │       ├── log.js
│   │   │   │   │       ├── mail.js
│   │   │   │   │       └── password.js
│   │   │   │   ├── components.json
│   │   │   │   ├── index.html
│   │   │   │   ├── jsconfig.json
│   │   │   │   ├── package.json
│   │   │   │   ├── postcss.config.js
│   │   │   │   ├── prisma/
│   │   │   │   │   └── schema.prisma
│   │   │   │   ├── public/
│   │   │   │   │   └── .gitkeep
│   │   │   │   ├── server.js
│   │   │   │   ├── tailwind.config.js
│   │   │   │   ├── tsconfig.json
│   │   │   │   ├── ui/
│   │   │   │   │   ├── assets/
│   │   │   │   │   │   └── .gitkeep
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── ui/
│   │   │   │   │   │       ├── alert.jsx
│   │   │   │   │   │       ├── button.jsx
│   │   │   │   │   │       ├── card.jsx
│   │   │   │   │   │       ├── input.jsx
│   │   │   │   │   │       └── label.jsx
│   │   │   │   │   ├── index.css
│   │   │   │   │   ├── lib/
│   │   │   │   │   │   └── utils.js
│   │   │   │   │   ├── main.jsx
│   │   │   │   │   └── pages/
│   │   │   │   │       ├── Home.css
│   │   │   │   │       ├── Home.jsx
│   │   │   │   │       ├── Login.jsx
│   │   │   │   │       └── Register.jsx
│   │   │   │   └── vite.config.js
│   │   │   ├── vite_react/
│   │   │   │   ├── .gitignore
│   │   │   │   ├── client/
│   │   │   │   │   ├── components.json
│   │   │   │   │   ├── eslint.config.js
│   │   │   │   │   ├── index.html
│   │   │   │   │   ├── package.json
│   │   │   │   │   ├── postcss.config.js
│   │   │   │   │   ├── src/
│   │   │   │   │   │   ├── App.css
│   │   │   │   │   │   ├── App.tsx
│   │   │   │   │   │   ├── api/
│   │   │   │   │   │   │   ├── api.ts
│   │   │   │   │   │   │   └── auth.ts
│   │   │   │   │   │   ├── components/
│   │   │   │   │   │   │   ├── Footer.tsx
│   │   │   │   │   │   │   ├── Header.tsx
│   │   │   │   │   │   │   ├── Layout.tsx
│   │   │   │   │   │   │   ├── ProtectedRoute.tsx
│   │   │   │   │   │   │   └── ui/
│   │   │   │   │   │   │       ├── accordion.tsx
│   │   │   │   │   │   │       ├── alert-dialog.tsx
│   │   │   │   │   │   │       ├── alert.tsx
│   │   │   │   │   │   │       ├── aspect-ratio.tsx
│   │   │   │   │   │   │       ├── avatar.tsx
│   │   │   │   │   │   │       ├── badge.tsx
│   │   │   │   │   │   │       ├── breadcrumb.tsx
│   │   │   │   │   │   │       ├── button.tsx
│   │   │   │   │   │   │       ├── calendar.tsx
│   │   │   │   │   │   │       ├── card.tsx
│   │   │   │   │   │   │       ├── carousel.tsx
│   │   │   │   │   │   │       ├── chart.tsx
│   │   │   │   │   │   │       ├── checkbox.tsx
│   │   │   │   │   │   │       ├── collapsible.tsx
│   │   │   │   │   │   │       ├── command.tsx
│   │   │   │   │   │   │       ├── context-menu.tsx
│   │   │   │   │   │   │       ├── dialog.tsx
│   │   │   │   │   │   │       ├── drawer.tsx
│   │   │   │   │   │   │       ├── dropdown-menu.tsx
│   │   │   │   │   │   │       ├── form.tsx
│   │   │   │   │   │   │       ├── hover-card.tsx
│   │   │   │   │   │   │       ├── input-otp.tsx
│   │   │   │   │   │   │       ├── input.tsx
│   │   │   │   │   │   │       ├── label.tsx
│   │   │   │   │   │   │       ├── menubar.tsx
│   │   │   │   │   │   │       ├── navigation-menu.tsx
│   │   │   │   │   │   │       ├── pagination.tsx
│   │   │   │   │   │   │       ├── popover.tsx
│   │   │   │   │   │   │       ├── progress.tsx
│   │   │   │   │   │   │       ├── radio-group.tsx
│   │   │   │   │   │   │       ├── resizable.tsx
│   │   │   │   │   │   │       ├── scroll-area.tsx
│   │   │   │   │   │   │       ├── select.tsx
│   │   │   │   │   │   │       ├── separator.tsx
│   │   │   │   │   │   │       ├── sheet.tsx
│   │   │   │   │   │   │       ├── sidebar.tsx
│   │   │   │   │   │   │       ├── skeleton.tsx
│   │   │   │   │   │   │       ├── slider.tsx
│   │   │   │   │   │   │       ├── sonner.tsx
│   │   │   │   │   │   │       ├── switch.tsx
│   │   │   │   │   │   │       ├── table.tsx
│   │   │   │   │   │   │       ├── tabs.tsx
│   │   │   │   │   │   │       ├── textarea.tsx
│   │   │   │   │   │   │       ├── theme-provider.tsx
│   │   │   │   │   │   │       ├── theme-toggle.tsx
│   │   │   │   │   │   │       ├── toast.tsx
│   │   │   │   │   │   │       ├── toaster.tsx
│   │   │   │   │   │   │       ├── toggle-group.tsx
│   │   │   │   │   │   │       ├── toggle.tsx
│   │   │   │   │   │   │       └── tooltip.tsx
│   │   │   │   │   │   ├── config/
│   │   │   │   │   │   │   └── constants.ts
│   │   │   │   │   │   ├── contexts/
│   │   │   │   │   │   │   └── AuthContext.tsx
│   │   │   │   │   │   ├── hooks/
│   │   │   │   │   │   │   ├── useMobile.tsx
│   │   │   │   │   │   │   └── useToast.ts
│   │   │   │   │   │   ├── index.css
│   │   │   │   │   │   ├── lib/
│   │   │   │   │   │   │   └── utils.ts
│   │   │   │   │   │   ├── main.tsx
│   │   │   │   │   │   ├── pages/
│   │   │   │   │   │   │   ├── BlankPage.tsx
│   │   │   │   │   │   │   ├── Login.tsx
│   │   │   │   │   │   │   └── Register.tsx
│   │   │   │   │   │   └── vite-env.d.ts
│   │   │   │   │   ├── tailwind.config.js
│   │   │   │   │   ├── tsconfig.app.json
│   │   │   │   │   ├── tsconfig.json
│   │   │   │   │   ├── tsconfig.node.json
│   │   │   │   │   └── vite.config.ts
│   │   │   │   ├── package.json
│   │   │   │   └── server/
│   │   │   │       ├── config/
│   │   │   │       │   └── database.js
│   │   │   │       ├── models/
│   │   │   │       │   ├── User.js
│   │   │   │       │   └── init.js
│   │   │   │       ├── package.json
│   │   │   │       ├── routes/
│   │   │   │       │   ├── authRoutes.js
│   │   │   │       │   ├── index.js
│   │   │   │       │   └── middleware/
│   │   │   │       │       └── auth.js
│   │   │   │       ├── server.js
│   │   │   │       ├── services/
│   │   │   │       │   ├── llmService.js
│   │   │   │       │   └── userService.js
│   │   │   │       └── utils/
│   │   │   │           ├── auth.js
│   │   │   │           └── password.js
│   │   │   └── vite_react_swagger/
│   │   │       ├── .gitignore
│   │   │       ├── client/
│   │   │       │   ├── components.json
│   │   │       │   ├── eslint.config.js
│   │   │       │   ├── index.html
│   │   │       │   ├── package.json
│   │   │       │   ├── postcss.config.js
│   │   │       │   ├── src/
│   │   │       │   │   ├── App.css
│   │   │       │   │   ├── App.tsx
│   │   │       │   │   ├── api/
│   │   │       │   │   │   ├── api.ts
│   │   │       │   │   │   └── auth.ts
│   │   │       │   │   ├── components/
│   │   │       │   │   │   ├── Footer.tsx
│   │   │       │   │   │   ├── Header.tsx
│   │   │       │   │   │   ├── Layout.tsx
│   │   │       │   │   │   ├── ProtectedRoute.tsx
│   │   │       │   │   │   └── ui/
│   │   │       │   │   │       ├── accordion.tsx
│   │   │       │   │   │       ├── alert-dialog.tsx
│   │   │       │   │   │       ├── alert.tsx
│   │   │       │   │   │       ├── aspect-ratio.tsx
│   │   │       │   │   │       ├── avatar.tsx
│   │   │       │   │   │       ├── badge.tsx
│   │   │       │   │   │       ├── breadcrumb.tsx
│   │   │       │   │   │       ├── button.tsx
│   │   │       │   │   │       ├── calendar.tsx
│   │   │       │   │   │       ├── card.tsx
│   │   │       │   │   │       ├── carousel.tsx
│   │   │       │   │   │       ├── chart.tsx
│   │   │       │   │   │       ├── checkbox.tsx
│   │   │       │   │   │       ├── collapsible.tsx
│   │   │       │   │   │       ├── command.tsx
│   │   │       │   │   │       ├── context-menu.tsx
│   │   │       │   │   │       ├── dialog.tsx
│   │   │       │   │   │       ├── drawer.tsx
│   │   │       │   │   │       ├── dropdown-menu.tsx
│   │   │       │   │   │       ├── form.tsx
│   │   │       │   │   │       ├── hover-card.tsx
│   │   │       │   │   │       ├── input-otp.tsx
│   │   │       │   │   │       ├── input.tsx
│   │   │       │   │   │       ├── label.tsx
│   │   │       │   │   │       ├── menubar.tsx
│   │   │       │   │   │       ├── navigation-menu.tsx
│   │   │       │   │   │       ├── pagination.tsx
│   │   │       │   │   │       ├── popover.tsx
│   │   │       │   │   │       ├── progress.tsx
│   │   │       │   │   │       ├── radio-group.tsx
│   │   │       │   │   │       ├── resizable.tsx
│   │   │       │   │   │       ├── scroll-area.tsx
│   │   │       │   │   │       ├── select.tsx
│   │   │       │   │   │       ├── separator.tsx
│   │   │       │   │   │       ├── sheet.tsx
│   │   │       │   │   │       ├── sidebar.tsx
│   │   │       │   │   │       ├── skeleton.tsx
│   │   │       │   │   │       ├── slider.tsx
│   │   │       │   │   │       ├── sonner.tsx
│   │   │       │   │   │       ├── switch.tsx
│   │   │       │   │   │       ├── table.tsx
│   │   │       │   │   │       ├── tabs.tsx
│   │   │       │   │   │       ├── textarea.tsx
│   │   │       │   │   │       ├── theme-provider.tsx
│   │   │       │   │   │       ├── theme-toggle.tsx
│   │   │       │   │   │       ├── toast.tsx
│   │   │       │   │   │       ├── toaster.tsx
│   │   │       │   │   │       ├── toggle-group.tsx
│   │   │       │   │   │       ├── toggle.tsx
│   │   │       │   │   │       └── tooltip.tsx
│   │   │       │   │   ├── config/
│   │   │       │   │   │   └── constants.ts
│   │   │       │   │   ├── contexts/
│   │   │       │   │   │   └── AuthContext.tsx
│   │   │       │   │   ├── hooks/
│   │   │       │   │   │   ├── useMobile.tsx
│   │   │       │   │   │   └── useToast.ts
│   │   │       │   │   ├── index.css
│   │   │       │   │   ├── lib/
│   │   │       │   │   │   └── utils.ts
│   │   │       │   │   ├── main.tsx
│   │   │       │   │   ├── pages/
│   │   │       │   │   │   ├── BlankPage.tsx
│   │   │       │   │   │   ├── Login.tsx
│   │   │       │   │   │   └── Register.tsx
│   │   │       │   │   └── vite-env.d.ts
│   │   │       │   ├── tailwind.config.js
│   │   │       │   ├── tsconfig.app.json
│   │   │       │   ├── tsconfig.json
│   │   │       │   ├── tsconfig.node.json
│   │   │       │   └── vite.config.ts
│   │   │       └── package.json
│   │   ├── vite_react.py
│   │   └── vite_react_swagger.py
│   ├── ui/
│   │   ├── api_server.py
│   │   ├── base.py
│   │   ├── console.py
│   │   ├── ipc_client.py
│   │   └── virtual.py
│   └── utils/
│       ├── __init__.py
│       └── text.py
├── docs/
│   └── TELEMETRY.md
├── example-config.json
├── main.py
├── pyproject.toml
├── requirements.txt
└── tests/
    ├── __init__.py
    ├── agents/
    │   ├── __init__.py
    │   ├── test_base.py
    │   ├── test_convo.py
    │   ├── test_external_docs.py
    │   ├── test_orchestrator.py
    │   └── test_tech_lead.py
    ├── cli/
    │   ├── __init__.py
    │   └── test_cli.py
    ├── config/
    │   ├── __init__.py
    │   ├── test_config.py
    │   ├── test_env_importer.py
    │   ├── test_version.py
    │   └── testconfig.json
    ├── conftest.py
    ├── db/
    │   ├── __init__.py
    │   ├── factories.py
    │   ├── test_branch.py
    │   ├── test_db.py
    │   ├── test_project.py
    │   └── test_project_state.py
    ├── disk/
    │   ├── __init__.py
    │   ├── test_ignore.py
    │   └── test_vfs.py
    ├── integration/
    │   ├── __init__.py
    │   └── llm/
    │       ├── __init__.py
    │       ├── test_anthropic.py
    │       ├── test_groq.py
    │       └── test_openai.py
    ├── llm/
    │   ├── __init__.py
    │   ├── prompts/
    │   │   └── test.txt
    │   ├── test_convo.py
    │   ├── test_openai.py
    │   ├── test_parser.py
    │   └── test_prompt.py
    ├── log/
    │   ├── __init__.py
    │   └── test_log.py
    ├── proc/
    │   ├── __init__.py
    │   └── test_process_manager.py
    ├── state/
    │   ├── __init__.py
    │   └── test_state_manager.py
    ├── telemetry/
    │   └── test_telemetry.py
    ├── templates/
    │   └── test_templates.py
    └── ui/
        ├── __init__.py
        ├── test_console.py
        └── test_ipc_client.py
Download .txt
SYMBOL INDEX (1255 symbols across 191 files)

FILE: core/agents/architect.py
  class AppType (line 29) | class AppType(str, Enum):
  class SystemDependency (line 39) | class SystemDependency(BaseModel):
  class PackageDependency (line 58) | class PackageDependency(BaseModel):
  class Architecture (line 69) | class Architecture(BaseModel):
  class TemplateSelection (line 84) | class TemplateSelection(BaseModel):
  class Architect (line 95) | class Architect(BaseAgent):
    method run (line 99) | async def run(self) -> AgentResponse:
    method select_templates (line 124) | async def select_templates(self, spec: Specification) -> tuple[str, di...
    method plan_architecture (line 173) | async def plan_architecture(self, spec: Specification):
    method check_compatibility (line 199) | async def check_compatibility(self, arch: Architecture) -> bool:
    method prepare_example_project (line 227) | def prepare_example_project(self, spec: Specification):
    method check_system_dependencies (line 237) | async def check_system_dependencies(self, spec: Specification):
    method configure_template (line 268) | async def configure_template(self, spec: Specification, template_class...

FILE: core/agents/base.py
  class BaseAgent (line 15) | class BaseAgent:
    method __init__ (line 23) | def __init__(
    method current_state (line 47) | def current_state(self) -> ProjectState:
    method next_state (line 52) | def next_state(self) -> ProjectState:
    method send_message (line 56) | async def send_message(self, message: str, extra_info: Optional[dict] ...
    method ask_question (line 70) | async def ask_question(
    method stream_handler (line 126) | async def stream_handler(self, content: str):
    method error_handler (line 142) | async def error_handler(self, error: LLMError, message: Optional[str] ...
    method get_llm (line 175) | def get_llm(self, name=None, stream_output=False, route=None) -> Calla...
    method run (line 225) | async def run() -> AgentResponse:

FILE: core/agents/bug_hunter.py
  class HuntConclusionType (line 31) | class HuntConclusionType(str, Enum):
  class HuntConclusionOptions (line 36) | class HuntConclusionOptions(BaseModel):
  class ImportantLog (line 42) | class ImportantLog(BaseModel):
  class ImportantLogsForDebugging (line 53) | class ImportantLogsForDebugging(BaseModel):
  class BugHunter (line 57) | class BugHunter(ChatWithBreakdownMixin, BaseAgent):
    method run (line 61) | async def run(self) -> AgentResponse:
    method get_bug_reproduction_instructions (line 82) | async def get_bug_reproduction_instructions(self):
    method check_logs (line 103) | async def check_logs(self, logs_message: str = None):
    method ask_user_to_test (line 144) | async def ask_user_to_test(self, awaiting_bug_reproduction: bool = Fal...
    method start_pair_programming (line 241) | async def start_pair_programming(self):
    method generate_iteration_convo_so_far (line 363) | def generate_iteration_convo_so_far(self, omit_last_cycle=False):
    method async_task_finish (line 393) | async def async_task_finish(self):
    method set_data_for_next_hunting_cycle (line 400) | def set_data_for_next_hunting_cycle(self, human_readable_instructions,...

FILE: core/agents/code_monkey.py
  class Decision (line 35) | class Decision(str, Enum):
  class Hunk (line 41) | class Hunk(BaseModel):
  class ReviewChanges (line 47) | class ReviewChanges(BaseModel):
  class FileDescription (line 52) | class FileDescription(BaseModel):
  function extract_code_blocks (line 61) | def extract_code_blocks(content):
  class CodeMonkey (line 68) | class CodeMonkey(FileDiffMixin, BaseAgent):
    method run (line 72) | async def run(self) -> AgentResponse:
    method implement_changes (line 81) | async def implement_changes(self, data: Optional[dict] = None) -> dict:
    method describe_files (line 160) | async def describe_files(self) -> AgentResponse:
    method describe_file (line 186) | async def describe_file(self, file: File, content: str):
    method accept_changes (line 213) | async def accept_changes(self, file_path: str, old_content: str, new_c...

FILE: core/agents/convo.py
  class AgentConvo (line 20) | class AgentConvo(Convo):
    method __init__ (line 23) | def __init__(self, agent: "BaseAgent"):
    method _init_templates (line 34) | def _init_templates(cls):
    method _get_default_template_vars (line 41) | def _get_default_template_vars(self) -> dict:
    method _serialize_prompt_context (line 55) | def _serialize_prompt_context(context: dict) -> dict:
    method render (line 64) | def render(self, name: str, **kwargs) -> str:
    method template (line 74) | def template(self, template_name: str, **kwargs) -> "AgentConvo":
    method fork (line 85) | def fork(self) -> "AgentConvo":
    method trim (line 91) | def trim(self, trim_index: int, trim_count: int) -> "AgentConvo":
    method slice (line 100) | def slice(self, slice_index: int, slice_count: int) -> "AgentConvo":
    method require_schema (line 112) | def require_schema(self, model: BaseModel) -> "AgentConvo":
    method remove_last_x_messages (line 130) | def remove_last_x_messages(self, x: int) -> "AgentConvo":

FILE: core/agents/developer.py
  class StepType (line 32) | class StepType(str, Enum):
  class CommandOptions (line 39) | class CommandOptions(BaseModel):
  class SaveFileOptions (line 45) | class SaveFileOptions(BaseModel):
  class SaveFileStep (line 49) | class SaveFileStep(BaseModel):
  class CommandStep (line 54) | class CommandStep(BaseModel):
  class HumanInterventionStep (line 59) | class HumanInterventionStep(BaseModel):
  class UtilityFunction (line 64) | class UtilityFunction(BaseModel):
  class TaskSteps (line 80) | class TaskSteps(BaseModel):
  function has_correct_num_of_tags (line 84) | def has_correct_num_of_tags(response: str) -> bool:
  class Developer (line 91) | class Developer(ChatWithBreakdownMixin, RelevantFilesMixin, BaseAgent):
    method run (line 95) | async def run(self) -> AgentResponse:
    method breakdown_current_iteration (line 121) | async def breakdown_current_iteration(self) -> AgentResponse:
    method breakdown_current_task (line 204) | async def breakdown_current_task(self) -> AgentResponse:
    method set_next_steps (line 313) | def set_next_steps(self, response: TaskSteps, source: str):
    method remove_duplicate_steps (line 330) | def remove_duplicate_steps(self, data):
    method ask_to_execute_task (line 349) | async def ask_to_execute_task(self) -> bool:
    method update_knowledge_base (line 478) | async def update_knowledge_base(self):

FILE: core/agents/error_handler.py
  class ErrorHandler (line 12) | class ErrorHandler(BaseAgent):
    method run (line 24) | async def run(self) -> AgentResponse:
    method handle_command_error (line 54) | async def handle_command_error(self, message: str, details: dict) -> A...

FILE: core/agents/executor.py
  class CommandResult (line 23) | class CommandResult(BaseModel):
  class Executor (line 36) | class Executor(BaseAgent):
    method __init__ (line 40) | def __init__(
    method for_step (line 59) | def for_step(self, step):
    method output_handler (line 65) | async def output_handler(self, out, err):
    method exit_handler (line 69) | async def exit_handler(self, process):
    method run (line 72) | async def run(self) -> AgentResponse:
    method check_command_output (line 147) | async def check_command_output(
    method complete (line 169) | def complete(self):

FILE: core/agents/external_docs.py
  class DocQueries (line 18) | class DocQueries(BaseModel):
  class SelectedDocsets (line 22) | class SelectedDocsets(BaseModel):
  class ExternalDocumentation (line 26) | class ExternalDocumentation(BaseAgent):
    method run (line 46) | async def run(self) -> AgentResponse:
    method _get_available_docsets (line 72) | async def _get_available_docsets(self) -> list[tuple]:
    method _select_docsets (line 85) | async def _select_docsets(self, available_docsets: list[tuple]) -> dic...
    method _create_queries (line 106) | async def _create_queries(self, docsets: dict[str, str]) -> dict[str, ...
    method _fetch_snippets (line 131) | async def _fetch_snippets(self, queries: dict[str, list[str]]) -> list...
    method _store_docs (line 158) | async def _store_docs(self, snippets: list[tuple], available_docsets: ...

FILE: core/agents/frontend.py
  function has_correct_num_of_backticks (line 33) | def has_correct_num_of_backticks(response: str) -> bool:
  class Frontend (line 40) | class Frontend(FileDiffMixin, GitMixin, BaseAgent):
    method run (line 44) | async def run(self) -> AgentResponse:
    method start_frontend (line 61) | async def start_frontend(self):
    method continue_frontend (line 112) | async def continue_frontend(self):
    method iterate_frontend (line 151) | async def iterate_frontend(self):
    method end_frontend_iteration (line 320) | async def end_frontend_iteration(self, finished: bool) -> AgentResponse:
    method process_response (line 356) | async def process_response(self, response_blocks: list, removed_mock: ...
    method remove_mock (line 451) | async def remove_mock(self):
    method set_app_details (line 519) | async def set_app_details(self):
    method kill_app (line 532) | async def kill_app(self):
    method try_auto_debug (line 554) | async def try_auto_debug(self) -> str:

FILE: core/agents/git.py
  class GitMixin (line 9) | class GitMixin:
    method check_git_installed (line 14) | async def check_git_installed(self) -> bool:
    method is_git_initialized (line 21) | async def is_git_initialized(self) -> bool:
    method init_git_if_needed (line 35) | async def init_git_if_needed(self) -> bool:
    method git_commit (line 92) | async def git_commit(self, commit_message: Optional[str] = None) -> None:

FILE: core/agents/human_input.py
  class HumanInput (line 6) | class HumanInput(BaseAgent):
    method run (line 10) | async def run(self) -> AgentResponse:
    method human_intervention (line 16) | async def human_intervention(self, step) -> AgentResponse:
    method input_required (line 29) | async def input_required(self, files: list[dict]) -> AgentResponse:

FILE: core/agents/importer.py
  class Importer (line 17) | class Importer(BaseAgent):
    method run (line 21) | async def run(self) -> AgentResponse:
    method start_import_process (line 30) | async def start_import_process(self):
    method analyze_project (line 56) | async def analyze_project(self):

FILE: core/agents/legacy_handler.py
  class LegacyHandler (line 5) | class LegacyHandler(BaseAgent):
    method run (line 9) | async def run(self) -> AgentResponse:

FILE: core/agents/mixins.py
  class RelevantFiles (line 21) | class RelevantFiles(BaseModel):
  class Test (line 27) | class Test(BaseModel):
  class TestSteps (line 33) | class TestSteps(BaseModel):
  class ChatWithBreakdownMixin (line 37) | class ChatWithBreakdownMixin:
    method chat_with_breakdown (line 42) | async def chat_with_breakdown(self, convo: AgentConvo, breakdown: str)...
  class IterationPromptMixin (line 82) | class IterationPromptMixin:
    method find_solution (line 89) | async def find_solution(
  class RelevantFilesMixin (line 122) | class RelevantFilesMixin:
    method get_relevant_files_parallel (line 130) | async def get_relevant_files_parallel(
    method get_relevant_files (line 153) | async def get_relevant_files(
  class FileDiffMixin (line 191) | class FileDiffMixin:
    method get_line_changes (line 196) | def get_line_changes(self, old_content: str, new_content: str) -> tupl...

FILE: core/agents/orchestrator.py
  class Orchestrator (line 36) | class Orchestrator(BaseAgent, GitMixin):
    method run (line 48) | async def run(self) -> bool:
    method install_dependencies (line 169) | async def install_dependencies(self):
    method set_frontend_script (line 182) | async def set_frontend_script(self):
    method enable_debugger (line 222) | async def enable_debugger(self):
    method set_favicon (line 252) | async def set_favicon(self):
    method set_package_json (line 283) | async def set_package_json(self):
    method set_vite_config (line 306) | async def set_vite_config(self):
    method handle_parallel_responses (line 345) | def handle_parallel_responses(self, agent: BaseAgent, responses: List[...
    method offline_changes_check (line 370) | async def offline_changes_check(self):
    method handle_done (line 418) | async def handle_done(self, agent: BaseAgent, response: AgentResponse)...
    method create_agent (line 463) | def create_agent(self, prev_response: Optional[AgentResponse]) -> Unio...
    method create_agent_for_step (line 554) | def create_agent_for_step(self, step: dict) -> Union[List[BaseAgent], ...
    method import_files (line 575) | async def import_files(self) -> Optional[AgentResponse]:
    method init_ui (line 600) | async def init_ui(self):
    method update_stats (line 620) | async def update_stats(self):

FILE: core/agents/problem_solver.py
  class AlternativeSolutions (line 16) | class AlternativeSolutions(BaseModel):
  class ProblemSolver (line 27) | class ProblemSolver(IterationPromptMixin, BaseAgent):
    method __init__ (line 31) | def __init__(self, *args, **kwargs):
    method run (line 38) | async def run(self) -> AgentResponse:
    method generate_alternative_solutions (line 49) | async def generate_alternative_solutions(self):
    method try_alternative_solutions (line 76) | async def try_alternative_solutions(self) -> AgentResponse:
    method ask_for_preferred_solution (line 107) | async def ask_for_preferred_solution(self) -> Optional[tuple[int, str]]:

FILE: core/agents/response.py
  class ResponseType (line 14) | class ResponseType(str, Enum):
  class AgentResponse (line 46) | class AgentResponse:
    method __init__ (line 51) | def __init__(self, type: ResponseType, agent: "BaseAgent", data: Optio...
    method __repr__ (line 56) | def __repr__(self) -> str:
    method done (line 60) | def done(agent: "BaseAgent") -> "AgentResponse":
    method error (line 64) | def error(agent: "BaseAgent", message: str, details: Optional[dict] = ...
    method cancel (line 72) | def cancel(agent: "BaseAgent") -> "AgentResponse":
    method exit (line 76) | def exit(agent: "ErrorHandler") -> "AgentResponse":
    method describe_files (line 80) | def describe_files(agent: "BaseAgent") -> "AgentResponse":
    method input_required (line 84) | def input_required(agent: "BaseAgent", files: list[dict[str, int]]) ->...
    method import_project (line 88) | def import_project(agent: "BaseAgent") -> "AgentResponse":
    method external_docs_required (line 92) | def external_docs_required(agent: "BaseAgent") -> "AgentResponse":
    method update_specification (line 96) | def update_specification(agent: "BaseAgent", description: str) -> "Age...
    method create_specification (line 106) | def create_specification(agent: "BaseAgent") -> "AgentResponse":

FILE: core/agents/spec_writer.py
  class SpecWriter (line 19) | class SpecWriter(BaseAgent):
    method run (line 23) | async def run(self) -> AgentResponse:
    method apply_template (line 34) | async def apply_template(self):
    method initialize_spec_and_project (line 63) | async def initialize_spec_and_project(self) -> AgentResponse:
    method change_spec (line 163) | async def change_spec(self) -> AgentResponse:
    method update_spec (line 305) | async def update_spec(self, iteration_mode) -> AgentResponse:
    method check_prompt_complexity (line 345) | async def check_prompt_complexity(self, prompt: str) -> str:

FILE: core/agents/task_completer.py
  class TaskCompleter (line 11) | class TaskCompleter(BaseAgent, GitMixin):
    method run (line 15) | async def run(self) -> AgentResponse:

FILE: core/agents/tech_lead.py
  class APIEndpoint (line 29) | class APIEndpoint(BaseModel):
  class Epic (line 37) | class Epic(BaseModel):
  class Task (line 41) | class Task(BaseModel):
  class DevelopmentPlan (line 47) | class DevelopmentPlan(BaseModel):
  class EpicPlan (line 51) | class EpicPlan(BaseModel):
  class TechLead (line 55) | class TechLead(RelevantFilesMixin, BaseAgent):
    method run (line 59) | async def run(self) -> AgentResponse:
    method create_initial_project_epic (line 92) | def create_initial_project_epic(self):
    method apply_project_templates (line 111) | async def apply_project_templates(self):
    method ask_for_new_feature (line 146) | async def ask_for_new_feature(self) -> AgentResponse:
    method process_epic (line 262) | async def process_epic(self, sub_epic_number, sub_epic):
    method plan_epic (line 296) | async def plan_epic(self, epic) -> AgentResponse:
    method remove_mocked_data (line 371) | async def remove_mocked_data(self):
    method update_epics_and_tasks (line 381) | async def update_epics_and_tasks(self):

FILE: core/agents/tech_writer.py
  class TechnicalWriter (line 12) | class TechnicalWriter(BaseAgent):
    method run (line 16) | async def run(self) -> AgentResponse:
    method send_congratulations (line 31) | async def send_congratulations(self):
    method create_readme (line 53) | async def create_readme(self):

FILE: core/agents/troubleshooter.py
  class BugReportQuestions (line 25) | class BugReportQuestions(BaseModel):
  class RouteFilePaths (line 31) | class RouteFilePaths(BaseModel):
  class Troubleshooter (line 35) | class Troubleshooter(ChatWithBreakdownMixin, IterationPromptMixin, Relev...
    method run (line 39) | async def run(self) -> AgentResponse:
    method propose_solution (line 48) | async def propose_solution(self) -> AgentResponse:
    method create_iteration (line 63) | async def create_iteration(self) -> AgentResponse:
    method complete_task (line 148) | async def complete_task(self) -> AgentResponse:
    method _get_task_convo (line 161) | def _get_task_convo(self) -> AgentConvo:
    method get_run_command (line 187) | async def get_run_command(self) -> Optional[str]:
    method get_user_instructions (line 203) | async def get_user_instructions(self) -> Optional[str]:
    method _get_route_files (line 233) | async def _get_route_files(self) -> list[File]:
    method get_user_feedback (line 244) | async def get_user_feedback(
    method try_next_alternative_solution (line 348) | def try_next_alternative_solution(self, user_feedback: str, user_feedb...
    method generate_bug_report (line 368) | async def generate_bug_report(
    method trace_loop (line 426) | async def trace_loop(self, trace_event: str):

FILE: core/agents/wizard.py
  class Wizard (line 20) | class Wizard(BaseAgent):
    method run (line 24) | async def run(self) -> AgentResponse:
    method init_template (line 30) | async def init_template(self) -> bool:
    method upload_docs (line 133) | async def upload_docs(self, docs: str) -> (bool, str, list):

FILE: core/cli/helpers.py
  function parse_llm_endpoint (line 61) | def parse_llm_endpoint(value: str) -> Optional[tuple[LLMProvider, str]]:
  function get_line_changes (line 88) | def get_line_changes(old_content: str, new_content: str) -> tuple[int, i...
  function calculate_pr_changes (line 117) | def calculate_pr_changes(convo_entries):
  function parse_llm_key (line 156) | def parse_llm_key(value: str) -> Optional[tuple[LLMProvider, str]]:
  function parse_arguments (line 180) | def parse_arguments() -> Namespace:
  function load_config (line 284) | def load_config(args: Namespace) -> Optional[Config]:
  function list_projects_json (line 333) | async def list_projects_json(db: SessionManager):
  function insert_new_task (line 354) | def insert_new_task(tasks, new_task):
  function find_task_by_id (line 370) | def find_task_by_id(tasks, task_id):
  function change_order_of_task (line 385) | def change_order_of_task(tasks, task_to_move, new_position):
  function find_first_todo_task (line 395) | def find_first_todo_task(tasks):
  function find_first_todo_task_index (line 412) | def find_first_todo_task_index(tasks):
  function get_epic_task_number (line 419) | def get_epic_task_number(state, current_task) -> (int, int):
  function get_source_for_history (line 437) | def get_source_for_history(msg_type: Optional[str] = "", question: Optio...
  function print_convo (line 481) | async def print_convo(ui: UIBase, convo: list, fake: Optional[bool] = Tr...
  function load_convo (line 583) | async def load_convo(
  function init_sentry (line 833) | def init_sentry():
  function capture_exception (line 843) | def capture_exception(exc: Exception):
  function list_projects_branches_states (line 849) | async def list_projects_branches_states(db: SessionManager):
  function load_project (line 864) | async def load_project(
  function delete_project (line 905) | async def delete_project(db: SessionManager, project_id: UUID) -> bool:
  function show_config (line 918) | def show_config():
  function init (line 926) | def init() -> tuple[UIBase, SessionManager, Namespace]:

FILE: core/cli/main.py
  function cleanup (line 51) | async def cleanup(ui: UIBase):
  function sync_cleanup (line 59) | def sync_cleanup(ui: UIBase):
  function run_project (line 63) | async def run_project(sm: StateManager, ui: UIBase, args) -> bool:
  function send_error (line 108) | async def send_error(ui: UIBase, error_source: str, err: Exception):
  function start_new_project (line 121) | async def start_new_project(sm: StateManager, ui: UIBase, args: Namespac...
  function run_pythagora_session (line 194) | async def run_pythagora_session(sm: StateManager, ui: UIBase, args: Name...
  function async_main (line 324) | async def async_main(
  function run_pythagora (line 429) | def run_pythagora():

FILE: core/config/__init__.py
  class _StrictModel (line 60) | class _StrictModel(BaseModel):
  class LLMProvider (line 70) | class LLMProvider(str, Enum):
  class UIAdapter (line 83) | class UIAdapter(str, Enum):
  class ProviderConfig (line 93) | class ProviderConfig(_StrictModel):
  class AgentLLMConfig (line 122) | class AgentLLMConfig(_StrictModel):
  class LLMConfig (line 140) | class LLMConfig(_StrictModel):
    method from_provider_and_agent_configs (line 177) | def from_provider_and_agent_configs(cls, provider: ProviderConfig, age...
  class PromptConfig (line 190) | class PromptConfig(_StrictModel):
    method validate_paths (line 202) | def validate_paths(cls, v: list[str]) -> list[str]:
  class LogConfig (line 209) | class LogConfig(_StrictModel):
  class DBConfig (line 233) | class DBConfig(_StrictModel):
    method validate_url_scheme (line 251) | def validate_url_scheme(cls, v: str) -> str:
  class PlainUIConfig (line 263) | class PlainUIConfig(_StrictModel):
  class LocalIPCConfig (line 271) | class LocalIPCConfig(_StrictModel):
  class VirtualUIConfig (line 281) | class VirtualUIConfig(_StrictModel):
  class FileSystemType (line 296) | class FileSystemType(str, Enum):
  class FileSystemConfig (line 305) | class FileSystemConfig(_StrictModel):
  class Config (line 325) | class Config(_StrictModel):
    method llm_for_agent (line 423) | def llm_for_agent(self, agent_name: str = "default") -> LLMConfig:
    method all_llms (line 436) | def all_llms(self) -> list[LLMConfig]:
  class ConfigLoader (line 444) | class ConfigLoader:
    method __init__ (line 457) | def __init__(self):
    method _remove_json_comments (line 462) | def _remove_json_comments(json_str: str) -> str:
    method from_json (line 474) | def from_json(cls: "ConfigLoader", config: str) -> Config:
    method load (line 483) | def load(self, path: str) -> Config:
  function adapt_for_bedrock (line 507) | def adapt_for_bedrock(config: Config) -> Config:
  function get_config (line 535) | def get_config() -> Config:

FILE: core/config/env_importer.py
  function import_from_dotenv (line 8) | def import_from_dotenv(new_config_path: str) -> bool:
  function convert_config (line 44) | def convert_config(values: dict) -> Config:

FILE: core/config/user_settings.py
  class TelemetrySettings (line 17) | class TelemetrySettings(BaseModel):
  function resolve_config_dir (line 23) | def resolve_config_dir() -> Path:
  class UserSettings (line 44) | class UserSettings(BaseModel):
    method load (line 68) | def load():
    method save (line 81) | def save(self):
    method config_path (line 87) | def config_path(self):

FILE: core/config/version.py
  function get_git_commit (line 8) | def get_git_commit() -> Optional[str]:
  function get_git_branch (line 42) | def get_git_branch() -> Optional[str]:
  function get_package_version (line 66) | def get_package_version() -> str:
  function get_version (line 90) | def get_version() -> str:

FILE: core/db/fix_migrations.py
  function get_latest_revision (line 12) | def get_latest_revision(alembic_cfg: Config) -> Optional[str]:
  function fix_alembic_version (line 20) | def fix_alembic_version(db_path: str, version: str) -> None:
  function main (line 35) | def main():

FILE: core/db/migrations/env.py
  function run_migrations_offline (line 30) | def run_migrations_offline() -> None:
  function run_migrations_online (line 55) | def run_migrations_online() -> None:

FILE: core/db/migrations/versions/0173e14719aa_move_metadata_from_file_to_file_content_.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 39) | def downgrade() -> None:

FILE: core/db/migrations/versions/0173e14719aa_vacuum_database.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 32) | def downgrade() -> None:

FILE: core/db/migrations/versions/08d71952ec2f_refactor_specification_template_to_.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 30) | def downgrade() -> None:

FILE: core/db/migrations/versions/0a1bb637fa26_initial.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 191) | def downgrade() -> None:

FILE: core/db/migrations/versions/3968d770dced_add_project_type_to_project.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 29) | def downgrade() -> None:

FILE: core/db/migrations/versions/675268601278_add_chat_messages_and_convos.py
  function upgrade (line 22) | def upgrade() -> None:
  function downgrade (line 46) | def downgrade() -> None:

FILE: core/db/migrations/versions/69e50fdaf067_move_knowledge_base_to_separate_table.py
  function upgrade (line 24) | def upgrade() -> None:
  function downgrade (line 113) | def downgrade() -> None:

FILE: core/db/migrations/versions/b760f66138c0_add_docs_column_to_project_states.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 29) | def downgrade() -> None:

FILE: core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 30) | def downgrade() -> None:

FILE: core/db/migrations/versions/f352dbe45751_make_relevant_files_nullable.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 29) | def downgrade() -> None:

FILE: core/db/migrations/versions/f708791b9270_adding_knowledge_base_field_to_.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 29) | def downgrade() -> None:

FILE: core/db/migrations/versions/ff891d366761_add_example_project_to_spec.py
  function upgrade (line 21) | def upgrade() -> None:
  function downgrade (line 29) | def downgrade() -> None:

FILE: core/db/models/base.py
  class Base (line 13) | class Base(AsyncAttrs, DeclarativeBase):
    method __repr__ (line 34) | def __repr__(self) -> str:

FILE: core/db/models/branch.py
  class Branch (line 17) | class Branch(Base):
    method get_by_id (line 38) | async def get_by_id(session: "AsyncSession", branch_id: Union[str, UUI...
    method get_last_state (line 52) | async def get_last_state(self) -> Optional["ProjectState"]:
    method get_state_at_step (line 73) | async def get_state_at_step(self, step_index: int) -> Optional["Projec...

FILE: core/db/models/chat_convo.py
  class ChatConvo (line 16) | class ChatConvo(Base):
    method get_chat_history (line 31) | async def get_chat_history(session: AsyncSession, convo_id) -> list["C...
    method get_project_state_for_convo_id (line 38) | async def get_project_state_for_convo_id(session: AsyncSession, convo_...

FILE: core/db/models/chat_message.py
  class ChatMessage (line 12) | class ChatMessage(Base):

FILE: core/db/models/exec_log.py
  class ExecLog (line 16) | class ExecLog(Base):
    method from_exec_log (line 42) | def from_exec_log(cls, project_state: "ProjectState", exec_log: ExecLo...

FILE: core/db/models/file.py
  class File (line 15) | class File(Base):
    method clone (line 31) | def clone(self) -> "File":
    method get_referencing_files (line 46) | async def get_referencing_files(session: "AsyncSession", project_state...

FILE: core/db/models/file_content.py
  class FileContent (line 13) | class FileContent(Base):
    method store (line 27) | async def store(cls, session: AsyncSession, hash: str, content: str, m...
    method delete_orphans (line 54) | async def delete_orphans(cls, session: AsyncSession):

FILE: core/db/models/knowledge_base.py
  class KnowledgeBase (line 14) | class KnowledgeBase(Base):
    method clone (line 35) | def clone(self) -> "KnowledgeBase":
    method delete_orphans (line 51) | async def delete_orphans(cls, session: AsyncSession):

FILE: core/db/models/llm_request.py
  class LLMRequest (line 18) | class LLMRequest(Base):
    method from_request_log (line 46) | def from_request_log(

FILE: core/db/models/project.py
  class Project (line 18) | class Project(Base):
    method get_by_id (line 36) | async def get_by_id(session: "AsyncSession", project_id: Union[str, UU...
    method rename (line 51) | async def rename(session: "AsyncSession", id: UUID, name: str, dir_nam...
    method get_branch (line 75) | async def get_branch(self, name: Optional[str] = None) -> Optional["Br...
    method get_file_for_project (line 96) | async def get_file_for_project(session: AsyncSession, project_state_id...
    method get_branches_for_project_id (line 103) | async def get_branches_for_project_id(session: AsyncSession, project_i...
    method get_all_projects (line 110) | async def get_all_projects(session: "AsyncSession") -> list[Row]:
    method get_all_projects_with_branches_states (line 117) | async def get_all_projects_with_branches_states(session: "AsyncSession...
    method get_folder_from_project_name (line 153) | def get_folder_from_project_name(name: str):
    method delete_by_id (line 167) | async def delete_by_id(session: "AsyncSession", project_id: UUID) -> int:

FILE: core/db/models/project_state.py
  class TaskStatus (line 32) | class TaskStatus:
  class IterationStatus (line 43) | class IterationStatus:
  class ProjectState (line 59) | class ProjectState(Base):
    method unfinished_steps (line 112) | def unfinished_steps(self) -> list[dict]:
    method current_step (line 121) | def current_step(self) -> Optional[dict]:
    method unfinished_iterations (line 133) | def unfinished_iterations(self) -> list[dict]:
    method current_iteration (line 146) | def current_iteration(self) -> Optional[dict]:
    method unfinished_tasks (line 158) | def unfinished_tasks(self) -> list[dict]:
    method current_task (line 169) | def current_task(self) -> Optional[dict]:
    method unfinished_epics (line 181) | def unfinished_epics(self) -> list[dict]:
    method current_epic (line 190) | def current_epic(self) -> Optional[dict]:
    method relevant_file_objects (line 202) | def relevant_file_objects(self):
    method create_initial_state (line 215) | def create_initial_state(branch: "Branch") -> "ProjectState":
    method get_project_states (line 238) | async def get_project_states(
    method create_next_state (line 269) | async def create_next_state(self) -> "ProjectState":
    method complete_step (line 317) | def complete_step(self, step_type: str):
    method complete_task (line 327) | def complete_task(self):
    method complete_epic (line 345) | def complete_epic(self):
    method complete_iteration (line 358) | def complete_iteration(self):
    method flag_iterations_as_modified (line 370) | def flag_iterations_as_modified(self):
    method flag_tasks_as_modified (line 380) | def flag_tasks_as_modified(self):
    method flag_epics_as_modified (line 390) | def flag_epics_as_modified(self):
    method flag_knowledge_base_as_modified (line 400) | def flag_knowledge_base_as_modified(self):
    method set_current_task_status (line 414) | def set_current_task_status(self, status: str):
    method get_file_by_path (line 428) | def get_file_by_path(self, path: str) -> Optional["File"]:
    method get_file_content_by_path (line 441) | def get_file_content_by_path(self, path: str) -> Union[FileContent, str]:
    method save_file (line 452) | def save_file(self, path: str, content: "FileContent", external: bool ...
    method delete_after (line 492) | async def delete_after(self):
    method get_last_iteration_steps (line 536) | def get_last_iteration_steps(self) -> list:
    method get_source_index (line 544) | def get_source_index(self, source: str) -> int:
    method get_steps_of_type (line 562) | def get_steps_of_type(self, step_type: str) -> [dict]:
    method has_frontend (line 571) | def has_frontend(self) -> bool:
    method working_on_frontend (line 580) | def working_on_frontend(self) -> bool:
    method is_feature (line 583) | def is_feature(self) -> bool:
    method get_state_for_redo_task (line 592) | async def get_state_for_redo_task(session: AsyncSession, project_state...
    method get_by_id (line 615) | async def get_by_id(session: "AsyncSession", state_id: UUID) -> Option...
    method get_all_epics_and_tasks (line 631) | async def get_all_epics_and_tasks(session: "AsyncSession", branch_id: ...
    method get_project_states_in_between (line 711) | async def get_project_states_in_between(
    method get_task_conversation_project_states (line 758) | async def get_task_conversation_project_states(
    method get_fe_states (line 906) | async def get_fe_states(
    method get_epic_task_number (line 958) | def get_epic_task_number(state, current_task) -> (int, int):
    method get_be_back_logs (line 976) | async def get_be_back_logs(session: "AsyncSession", branch_id: UUID) -...

FILE: core/db/models/specification.py
  class Complexity (line 14) | class Complexity:
  class Specification (line 22) | class Specification(Base):
    method clone (line 43) | def clone(self) -> "Specification":
    method delete_orphans (line 61) | async def delete_orphans(cls, session: AsyncSession):
    method update_specification (line 74) | async def update_specification(session: AsyncSession, specification: "...

FILE: core/db/models/user_input.py
  class UserInput (line 17) | class UserInput(Base):
    method from_user_input (line 37) | def from_user_input(cls, project_state: "ProjectState", question: str,...
    method find_user_inputs (line 63) | async def find_user_inputs(session: AsyncSession, project_state, branc...
    method delete_orphans (line 75) | async def delete_orphans(cls, session: AsyncSession):

FILE: core/db/session.py
  class SessionManager (line 10) | class SessionManager:
    method __init__ (line 21) | def __init__(self, config: DBConfig, args=None):
    method _on_connect (line 38) | def _on_connect(self, dbapi_connection, _):
    method start (line 49) | async def start(self) -> AsyncSession:
    method close (line 58) | async def close(self):
    method __aenter__ (line 69) | async def __aenter__(self) -> AsyncSession:
    method __aexit__ (line 72) | async def __aexit__(self, exc_type, exc_val, exc_tb):

FILE: core/db/setup.py
  function _async_to_sync_db_scheme (line 12) | def _async_to_sync_db_scheme(url: str) -> str:
  function run_migrations (line 29) | def run_migrations(config: DBConfig):

FILE: core/db/v0importer.py
  class ImporterStateManager (line 18) | class ImporterStateManager(StateManager):
    method init_file_system (line 19) | async def init_file_system(self, load_existing: bool) -> MemoryVFS:
  class LegacyDatabaseImporter (line 30) | class LegacyDatabaseImporter:
    method __init__ (line 31) | def __init__(self, session_manager: SessionManager, dbpath: str):
    method import_database (line 40) | async def import_database(self):
    method load_legacy_database (line 49) | async def load_legacy_database(self):
    method verify_schema (line 67) | async def verify_schema(self) -> bool:
    method get_apps (line 75) | async def get_apps(self) -> dict[str, str]:
    method get_app_info (line 83) | async def get_app_info(self, app_id: str) -> dict:
    method get_task_info (line 113) | async def get_task_info(self, dev_step_id, prompt_data_json: str, llm_...
    method get_task_files (line 132) | async def get_task_files(self, dev_step_id: int):
    method save_to_new_database (line 157) | async def save_to_new_database(self, info: dict) -> int:
    method save_app (line 178) | async def save_app(self, app_id: str, app_info: dict):
    method save_latest_task (line 207) | async def save_latest_task(self, task: dict):
    method save_task_files (line 265) | async def save_task_files(self, files: dict):

FILE: core/disk/ignore.py
  class IgnoreMatcher (line 6) | class IgnoreMatcher:
    method __init__ (line 12) | def __init__(
    method ignore (line 35) | def ignore(self, path: str) -> bool:
    method _is_in_ignore_list (line 57) | def _is_in_ignore_list(self, path: str) -> bool:
    method _is_large_file (line 72) | def _is_large_file(self, full_path: str) -> bool:
    method _is_binary (line 97) | def _is_binary(self, full_path: str) -> bool:

FILE: core/disk/vfs.py
  class VirtualFileSystem (line 12) | class VirtualFileSystem:
    method save (line 13) | def save(self, path: str, content: str):
    method read (line 22) | def read(self, path: str) -> str:
    method remove (line 31) | def remove(self, path: str):
    method get_full_path (line 42) | def get_full_path(self, path: str) -> str:
    method _filter_by_prefix (line 56) | def _filter_by_prefix(self, file_list: list[str], prefix: str) -> list...
    method _get_file_list (line 62) | def _get_file_list(self) -> list[str]:
    method list (line 65) | def list(self, prefix: str = None) -> list[str]:
    method hash (line 79) | def hash(self, path: str) -> str:
    method hash_string (line 84) | def hash_string(content: str) -> str:
  class MemoryVFS (line 88) | class MemoryVFS(VirtualFileSystem):
    method __init__ (line 91) | def __init__(self):
    method save (line 94) | def save(self, path: str, content: str):
    method read (line 97) | def read(self, path: str) -> str:
    method remove (line 103) | def remove(self, path: str):
    method get_full_path (line 107) | def get_full_path(self, path: str) -> str:
    method _get_file_list (line 111) | def _get_file_list(self) -> list[str]:
  class LocalDiskVFS (line 115) | class LocalDiskVFS(VirtualFileSystem):
    method __init__ (line 116) | def __init__(
    method get_full_path (line 138) | def get_full_path(self, path: str) -> str:
    method save (line 141) | def save(self, path: str, content: str):
    method read (line 148) | def read(self, path: str) -> str:
    method remove (line 157) | def remove(self, path: str):
    method _get_file_list (line 169) | def _get_file_list(self) -> list[str]:

FILE: core/llm/anthropic_client.py
  class CustomAssertionError (line 22) | class CustomAssertionError(Exception):
  class AnthropicClient (line 26) | class AnthropicClient(BaseLLMClient):
    method _init_client (line 29) | def _init_client(self):
    method _adapt_messages (line 41) | def _adapt_messages(self, convo: Convo) -> list[dict[str, str]]:
    method _make_request (line 68) | async def _make_request(
    method rate_limit_sleep (line 124) | def rate_limit_sleep(self, err: RateLimitError) -> Optional[datetime.t...

FILE: core/llm/azure_client.py
  class AzureClient (line 11) | class AzureClient(OpenAIClient):
    method _init_client (line 15) | def _init_client(self):

FILE: core/llm/base.py
  class LLMError (line 25) | class LLMError(str, Enum):
  class APIError (line 31) | class APIError(Exception):
    method __init__ (line 32) | def __init__(self, message: str):
  class BaseLLMClient (line 36) | class BaseLLMClient:
    method __init__ (line 55) | def __init__(
    method _init_client (line 77) | def _init_client(self):
    method _make_request (line 80) | async def _make_request(
    method _adapt_messages (line 98) | async def _adapt_messages(self, convo: Convo) -> list[dict[str, str]]:
    method __call__ (line 124) | async def __call__(
    method for_provider (line 416) | def for_provider(provider: LLMProvider) -> type["BaseLLMClient"]:
    method rate_limit_sleep (line 442) | def rate_limit_sleep(self, err: Exception) -> Optional[datetime.timede...

FILE: core/llm/convo.py
  class Convo (line 5) | class Convo:
    method __init__ (line 18) | def __init__(self, content: Optional[str] = None):
    method _dedent (line 31) | def _dedent(text: str) -> str:
    method add (line 46) | def add(self, role: str, content: str, name: Optional[str] = None) -> ...
    method system (line 75) | def system(self, content: str, name: Optional[str] = None) -> "Convo":
    method user (line 88) | def user(self, content: str, name: Optional[str] = None) -> "Convo":
    method assistant (line 98) | def assistant(self, content: str, name: Optional[str] = None) -> "Convo":
    method function (line 108) | def function(self, content: str, name: Optional[str] = None) -> "Convo":
    method fork (line 118) | def fork(self) -> "Convo":
    method after (line 133) | def after(self, parent: "Convo") -> "Convo":
    method last (line 150) | def last(self) -> Optional[dict[str, str]]:
    method __iter__ (line 158) | def __iter__(self) -> Iterator[dict[str, str]]:
    method __repr__ (line 166) | def __repr__(self) -> str:

FILE: core/llm/groq_client.py
  class GroqClient (line 17) | class GroqClient(BaseLLMClient):
    method _init_client (line 20) | def _init_client(self):
    method _make_request (line 31) | async def _make_request(
    method rate_limit_sleep (line 77) | def rate_limit_sleep(self, err: RateLimitError) -> Optional[datetime.t...

FILE: core/llm/openai_client.py
  class OpenAIClient (line 18) | class OpenAIClient(BaseLLMClient):
    method _init_client (line 22) | def _init_client(self):
    method _make_request (line 33) | async def _make_request(
    method rate_limit_sleep (line 89) | def rate_limit_sleep(self, err: RateLimitError) -> Optional[datetime.t...

FILE: core/llm/parser.py
  class CodeBlock (line 9) | class CodeBlock(BaseModel):
  class ParsedBlocks (line 14) | class ParsedBlocks(BaseModel):
  class DescriptiveCodeBlockParser (line 19) | class DescriptiveCodeBlockParser:
    method __init__ (line 40) | def __init__(self):
    method __call__ (line 43) | def __call__(self, text: str) -> ParsedBlocks:
  class MultiCodeBlockParser (line 60) | class MultiCodeBlockParser:
    method __init__ (line 88) | def __init__(self):
    method __call__ (line 91) | def __call__(self, text: str) -> list[str]:
  class CodeBlockParser (line 98) | class CodeBlockParser(MultiCodeBlockParser):
    method __call__ (line 114) | def __call__(self, text: str) -> str:
  class OptionalCodeBlockParser (line 123) | class OptionalCodeBlockParser:
    method __call__ (line 124) | def __call__(self, text: str) -> str:
  class JSONParser (line 136) | class JSONParser:
    method __init__ (line 137) | def __init__(self, spec: Optional[BaseModel] = None, strict: bool = Tr...
    method schema (line 143) | def schema(self):
    method errors_to_markdown (line 147) | def errors_to_markdown(errors: list) -> str:
    method __call__ (line 156) | def __call__(self, text: str) -> Union[BaseModel, dict, None]:
  class EnumParser (line 199) | class EnumParser:
    method __init__ (line 200) | def __init__(self, spec: Enum, ignore_case: bool = True):
    method __call__ (line 204) | def __call__(self, text: str) -> Enum:
  class StringParser (line 215) | class StringParser:
    method __call__ (line 216) | def __call__(self, text: str) -> str:

FILE: core/llm/prompt.py
  class FormatTemplate (line 7) | class FormatTemplate:
    method __call__ (line 8) | def __call__(self, template: str, **kwargs: dict[str, Any]) -> str:
  class BaseJinjaTemplate (line 12) | class BaseJinjaTemplate:
    method __init__ (line 13) | def __init__(self, loader: Optional[BaseLoader]):
  class JinjaStringTemplate (line 24) | class JinjaStringTemplate(BaseJinjaTemplate):
    method __init__ (line 25) | def __init__(self):
    method __call__ (line 28) | def __call__(self, template: str, **kwargs: dict[str, Any]) -> str:
  class JinjaFileTemplate (line 33) | class JinjaFileTemplate(BaseJinjaTemplate):
    method __init__ (line 34) | def __init__(self, template_dirs: list[str]):
    method __call__ (line 40) | def __call__(self, template: str, **kwargs: dict[str, Any]) -> str:

FILE: core/llm/relace_client.py
  class RelaceClient (line 14) | class RelaceClient(BaseLLMClient):
    method _init_client (line 17) | def _init_client(self):
    method _make_request (line 24) | async def _make_request(

FILE: core/llm/request_log.py
  class LLMRequestStatus (line 10) | class LLMRequestStatus(str, Enum):
  class LLMRequestLog (line 15) | class LLMRequestLog(BaseModel):

FILE: core/log/__init__.py
  class LineCountLimitedFileHandler (line 9) | class LineCountLimitedFileHandler(FileHandler):
    method __init__ (line 15) | def __init__(self, filename, max_lines=LOGS_LINE_LIMIT, mode="a", enco...
    method _load_existing_lines (line 30) | def _load_existing_lines(self):
    method emit (line 45) | def emit(self, record):
  function setup (line 65) | def setup(config: LogConfig, force: bool = False):
  function get_logger (line 102) | def get_logger(name) -> Logger:

FILE: core/proc/exec_log.py
  class ExecLog (line 7) | class ExecLog(BaseModel):

FILE: core/proc/process_manager.py
  class LocalProcess (line 25) | class LocalProcess:
    method __hash__ (line 35) | def __hash__(self) -> int:
    method start (line 39) | async def start(
    method wait (line 64) | async def wait(self, timeout: Optional[float] = None) -> int:
    method _nonblock_read (line 79) | async def _nonblock_read(reader: asyncio.StreamReader, timeout: float)...
    method read_output (line 99) | async def read_output(self, timeout: float = NONBLOCK_READ_TIMEOUT) ->...
    method _terminate_process_tree (line 106) | async def _terminate_process_tree(self, signal: int):
    method terminate (line 121) | async def terminate(self, kill: bool = True):
    method is_running (line 129) | def is_running(self) -> bool:
    method pid (line 136) | def pid(self) -> int:
  class ProcessManager (line 140) | class ProcessManager:
    method __init__ (line 141) | def __init__(
    method stop_watcher (line 159) | async def stop_watcher(self):
    method watcher (line 171) | async def watcher(self):
    method start_process (line 202) | async def start_process(
    method run_command (line 218) | async def run_command(
    method list_running_processes (line 268) | def list_running_processes(self):
    method terminate_process (line 271) | async def terminate_process(self, process_id: UUID) -> tuple[str, str]:

FILE: core/state/state_manager.py
  class StateManager (line 47) | class StateManager:
    method __init__ (line 60) | def __init__(self, session_manager: SessionManager, ui: Optional[UIBas...
    method db_blocker (line 84) | async def db_blocker(self):
    method list_projects (line 94) | async def list_projects(self) -> list[Row]:
    method get_referencing_files (line 101) | async def get_referencing_files(self, project_state, file_content: str...
    method list_projects_with_branches_states (line 106) | async def list_projects_with_branches_states(self) -> list[Project]:
    method get_project_states (line 113) | async def get_project_states(self, project_id: Optional[UUID], branch_...
    method get_branches_for_project_id (line 116) | async def get_branches_for_project_id(self, project_id: UUID) -> list[...
    method get_project_state_for_redo_task (line 119) | async def get_project_state_for_redo_task(self, project_state: Project...
    method get_project_state_by_id (line 122) | async def get_project_state_by_id(self, state_id: UUID) -> Optional[Pr...
    method get_all_epics_and_tasks (line 125) | async def get_all_epics_and_tasks(self, branch_id: UUID) -> list:
    method find_user_input (line 128) | async def find_user_input(self, project_state, branch_id) -> Optional[...
    method get_file_for_project (line 131) | async def get_file_for_project(self, state_id: UUID, path: str):
    method get_chat_history (line 134) | async def get_chat_history(self, convo_id) -> Optional[list["ChatMessa...
    method get_project_state_for_convo_id (line 137) | async def get_project_state_for_convo_id(self, convo_id) -> Optional["...
    method get_task_conversation_project_states (line 140) | async def get_task_conversation_project_states(
    method get_project_states_in_between (line 151) | async def get_project_states_in_between(
    method get_fe_states (line 162) | async def get_fe_states(self, limit: Optional[int] = None) -> Optional...
    method get_be_back_logs (line 165) | async def get_be_back_logs(self):
    method create_project (line 172) | async def create_project(
    method delete_project (line 248) | async def delete_project(self, project_id: UUID) -> bool:
    method update_specification (line 261) | async def update_specification(self, specification: Specification) -> ...
    method load_project (line 272) | async def load_project(
    method commit_with_retry (line 418) | async def commit_with_retry(self):
    method commit (line 433) | async def commit(self) -> ProjectState:
    method rollback (line 480) | async def rollback(self):
    method log_llm_request (line 491) | async def log_llm_request(self, request_log: LLMRequestLog, agent: Opt...
    method log_user_input (line 524) | async def log_user_input(self, question: str, response: UserInputData):
    method log_command_run (line 539) | async def log_command_run(self, exec_log: ExecLogData):
    method log_event (line 553) | async def log_event(self, type: str, **kwargs):
    method log_task_completed (line 569) | async def log_task_completed(self):
    method get_file_by_path (line 580) | async def get_file_by_path(self, path: str) -> Optional[File]:
    method save_file (line 589) | async def save_file(
    method init_file_system (line 628) | async def init_file_system(self, load_existing: bool) -> VirtualFileSy...
    method get_full_project_root (line 667) | def get_full_project_root(self) -> str:
    method get_full_parent_project_root (line 679) | def get_full_parent_project_root(self) -> str:
    method get_project_info (line 686) | def get_project_info(self) -> dict:
    method import_files (line 703) | async def import_files(self) -> tuple[list[File], list[File]]:
    method restore_files (line 740) | async def restore_files(self) -> list[File]:
    method get_modified_files (line 762) | async def get_modified_files(self) -> list[str]:
    method get_modified_files_with_content (line 786) | async def get_modified_files_with_content(self) -> list[dict]:
    method workspace_is_empty (line 832) | def workspace_is_empty(self) -> bool:
    method get_implemented_pages (line 840) | def get_implemented_pages(self) -> list[str]:
    method update_implemented_pages_and_apis (line 850) | async def update_implemented_pages_and_apis(self):
    method update_utility_functions (line 876) | async def update_utility_functions(self, utility_function: dict):
    method get_apis (line 908) | async def get_apis(self) -> list[dict]:
    method find_backend_implementation (line 950) | async def find_backend_implementation(self, endpoint_line: str) -> dict:
    method update_apis (line 988) | async def update_apis(self, files_with_implemented_apis: list[dict] = ...
    method get_input_required (line 1014) | def get_input_required(content: str, file_path: str) -> list[int]:
    method update_access_token (line 1033) | def update_access_token(self, access_token: str):
    method get_access_token (line 1039) | def get_access_token(self) -> Optional[str]:

FILE: core/telemetry/__init__.py
  class Telemetry (line 22) | class Telemetry:
    method __init__ (line 49) | def __init__(self):
    method clear_data (line 63) | def clear_data(self):
    method clear_counters (line 113) | def clear_counters(self):
    method set (line 166) | def set(self, name: str, value: Any):
    method inc (line 181) | def inc(self, name: str, value: int = 1):
    method start (line 196) | def start(self):
    method stop (line 203) | def stop(self):
    method record_crash (line 214) | def record_crash(
    method record_llm_request (line 275) | def record_llm_request(
    method calculate_statistics (line 300) | def calculate_statistics(self):
    method send (line 322) | async def send(self, event: str = "pythagora-core-telemetry"):
    method get_project_stats (line 356) | def get_project_stats(self) -> dict:
    method trace_code_event (line 363) | async def trace_code_event(self, name: str, data: dict):
    method trace_loop (line 391) | async def trace_loop(self, name: str, task_with_loop: dict):

FILE: core/templates/base.py
  class NoOptions (line 19) | class NoOptions(BaseModel):
    class Config (line 24) | class Config:
  class BaseProjectTemplate (line 30) | class BaseProjectTemplate:
    method __init__ (line 42) | def __init__(
    method filter (line 65) | def filter(self, path: str) -> Optional[str]:
    method apply (line 81) | async def apply(self) -> Optional[str]:
    method install_hook_template (line 123) | async def install_hook_template(self) -> Any:
    method get_summary (line 132) | def get_summary(self):
    method install_hook (line 141) | async def install_hook(self):
    method options_dict (line 148) | def options_dict(self) -> dict[str, Any]:

FILE: core/templates/javascript_react.py
  class JavascriptReactProjectTemplate (line 4) | class JavascriptReactProjectTemplate(BaseProjectTemplate):
    method install_hook (line 34) | async def install_hook(self):

FILE: core/templates/node_express_mongoose.py
  class NodeExpressMongooseProjectTemplate (line 4) | class NodeExpressMongooseProjectTemplate(BaseProjectTemplate):
    method install_hook (line 40) | async def install_hook(self):

FILE: core/templates/react_express.py
  class DatabaseType (line 13) | class DatabaseType(str, Enum):
  class TemplateOptions (line 19) | class TemplateOptions(BaseModel):
  class ReactExpressProjectTemplate (line 35) | class ReactExpressProjectTemplate(BaseProjectTemplate):
    method install_hook (line 84) | async def install_hook(self):
    method filter (line 90) | def filter(self, path: str) -> Optional[str]:

FILE: core/templates/registry.py
  class ProjectTemplateEnum (line 15) | class ProjectTemplateEnum(str, Enum):

FILE: core/templates/render.py
  function escape_string (line 12) | def escape_string(str: str) -> str:
  class Renderer (line 22) | class Renderer:
    method __init__ (line 44) | def __init__(self, template_dir: str):
    method render_template (line 56) | def render_template(self, template: str, context: Any) -> str:
    method render_tree (line 71) | def render_tree(self, root: str, context: Any, full_root_dir: str, fil...

FILE: core/templates/tree/add_raw_tags.py
  function add_raw_tags_to_file (line 5) | def add_raw_tags_to_file(file_path):
  function process_directory (line 30) | def process_directory(directory):

FILE: core/templates/tree/javascript_react/src/App.jsx
  function App (line 3) | function App() {

FILE: core/templates/tree/node_express_mongoose/services/llm.js
  constant MAX_RETRIES (line 16) | const MAX_RETRIES = 3;
  constant RETRY_DELAY (line 17) | const RETRY_DELAY = 1000;
  function sleep (line 19) | async function sleep(ms) {
  function sendRequestToOpenAI (line 23) | async function sendRequestToOpenAI(model, message) {
  function sendRequestToAnthropic (line 40) | async function sendRequestToAnthropic(model, message) {
  function sendLLMRequest (line 59) | async function sendLLMRequest(provider, model, message) {

FILE: core/templates/tree/react_express/api/services/userService.js
  class UserService (line 11) | class UserService {
    method list (line 12) | static async list() {
    method get (line 25) | static async get(id) {
    method getByEmail (line 44) | static async getByEmail(email) {
    method update (line 63) | static async update(id, data) {
    method delete (line 79) | static async delete(id) {
    method authenticateWithPassword (line 94) | static async authenticateWithPassword(email, password) {
    method authenticateWithToken (line 129) | static async authenticateWithToken(token) {
    method createUser (line 147) | static async createUser({ email, password, name = '' }) {
    method setPassword (line 184) | static async setPassword(user, password) {
    method regenerateToken (line 208) | static async regenerateToken(user) {

FILE: core/templates/tree/react_express/api/utils/log.js
  constant DEFAULT_LOG_LEVEL (line 3) | const DEFAULT_LOG_LEVEL = process.env.NODE_ENV === "production" ? "info"...

FILE: core/templates/tree/react_express/ui/components/ui/alert.jsx
  function AlertDestructive (line 4) | function AlertDestructive({ title, description }) {

FILE: core/templates/tree/react_express/ui/lib/utils.js
  function cn (line 4) | function cn(...inputs) {

FILE: core/templates/tree/react_express/ui/main.jsx
  function PageNotFound (line 26) | function PageNotFound() {

FILE: core/templates/tree/react_express/ui/pages/Home.jsx
  function Home (line 3) | function Home() {

FILE: core/templates/tree/react_express/ui/pages/Login.jsx
  function Login (line 23) | function Login() {

FILE: core/templates/tree/react_express/ui/pages/Register.jsx
  function Register (line 16) | function Register() {

FILE: core/templates/tree/vite_react/client/src/App.tsx
  function App (line 13) | function App() {

FILE: core/templates/tree/vite_react/client/src/api/api.ts
  constant API_KEY (line 5) | const API_KEY = import.meta.env.VITE_API_KEY;
  constant EXTERNAL_API_URL (line 9) | const EXTERNAL_API_URL = import.meta.env.VITE_EXTERNAL_API_URL;

FILE: core/templates/tree/vite_react/client/src/components/Footer.tsx
  function Footer (line 2) | function Footer() {

FILE: core/templates/tree/vite_react/client/src/components/Header.tsx
  function Header (line 8) | function Header() {

FILE: core/templates/tree/vite_react/client/src/components/Layout.tsx
  function Layout (line 5) | function Layout() {

FILE: core/templates/tree/vite_react/client/src/components/ProtectedRoute.tsx
  function ProtectedRoute (line 5) | function ProtectedRoute({ children }: { children: React.ReactNode }) {

FILE: core/templates/tree/vite_react/client/src/components/ui/badge.tsx
  type BadgeProps (line 27) | interface BadgeProps
  function Badge (line 31) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: core/templates/tree/vite_react/client/src/components/ui/button.tsx
  type ButtonProps (line 37) | interface ButtonProps

FILE: core/templates/tree/vite_react/client/src/components/ui/calendar.tsx
  type CalendarProps (line 9) | type CalendarProps = React.ComponentProps<typeof DayPicker>
  function Calendar (line 11) | function Calendar({

FILE: core/templates/tree/vite_react/client/src/components/ui/carousel.tsx
  type CarouselApi (line 11) | type CarouselApi = UseEmblaCarouselType[1]
  type UseCarouselParameters (line 12) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
  type CarouselOptions (line 13) | type CarouselOptions = UseCarouselParameters[0]
  type CarouselPlugin (line 14) | type CarouselPlugin = UseCarouselParameters[1]
  type CarouselProps (line 16) | type CarouselProps = {
  type CarouselContextProps (line 23) | type CarouselContextProps = {
  function useCarousel (line 34) | function useCarousel() {

FILE: core/templates/tree/vite_react/client/src/components/ui/chart.tsx
  constant THEMES (line 10) | const THEMES = { light: "", dark: ".dark" } as const
  type ChartConfig (line 12) | type ChartConfig = {
  type ChartContextProps (line 22) | type ChartContextProps = {
  function useChart (line 28) | function useChart() {
  function getPayloadConfigFromPayload (line 321) | function getPayloadConfigFromPayload(

FILE: core/templates/tree/vite_react/client/src/components/ui/form.tsx
  type FormFieldContextValue (line 21) | type FormFieldContextValue<
  type FormItemContextValue (line 68) | type FormItemContextValue = {

FILE: core/templates/tree/vite_react/client/src/components/ui/pagination.tsx
  type PaginationLinkProps (line 38) | type PaginationLinkProps = {

FILE: core/templates/tree/vite_react/client/src/components/ui/sheet.tsx
  type SheetContentProps (line 53) | interface SheetContentProps

FILE: core/templates/tree/vite_react/client/src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 21) | const SIDEBAR_COOKIE_NAME = "sidebar:state"
  constant SIDEBAR_COOKIE_MAX_AGE (line 22) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
  constant SIDEBAR_WIDTH (line 23) | const SIDEBAR_WIDTH = "16rem"
  constant SIDEBAR_WIDTH_MOBILE (line 24) | const SIDEBAR_WIDTH_MOBILE = "18rem"
  constant SIDEBAR_WIDTH_ICON (line 25) | const SIDEBAR_WIDTH_ICON = "3rem"
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 26) | const SIDEBAR_KEYBOARD_SHORTCUT = "b"
  type SidebarContext (line 28) | type SidebarContext = {
  function useSidebar (line 40) | function useSidebar() {

FILE: core/templates/tree/vite_react/client/src/components/ui/skeleton.tsx
  function Skeleton (line 4) | function Skeleton({

FILE: core/templates/tree/vite_react/client/src/components/ui/sonner.tsx
  type ToasterProps (line 7) | type ToasterProps = React.ComponentProps<typeof Sonner>

FILE: core/templates/tree/vite_react/client/src/components/ui/theme-provider.tsx
  type Theme (line 3) | type Theme = "dark" | "light" | "system"
  type ThemeProviderProps (line 5) | type ThemeProviderProps = {
  type ThemeProviderState (line 11) | type ThemeProviderState = {
  function ThemeProvider (line 23) | function ThemeProvider({

FILE: core/templates/tree/vite_react/client/src/components/ui/theme-toggle.tsx
  function ThemeToggle (line 7) | function ThemeToggle() {

FILE: core/templates/tree/vite_react/client/src/components/ui/toast.tsx
  type ToastProps (line 142) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
  type ToastActionElement (line 144) | type ToastActionElement = React.ReactElement<typeof ToastAction>

FILE: core/templates/tree/vite_react/client/src/components/ui/toaster.tsx
  function Toaster (line 12) | function Toaster() {

FILE: core/templates/tree/vite_react/client/src/contexts/AuthContext.tsx
  type AuthContextType (line 6) | type AuthContextType = {
  function AuthProvider (line 15) | function AuthProvider({ children }: { children: ReactNode }) {
  function useAuth (line 63) | function useAuth() {

FILE: core/templates/tree/vite_react/client/src/hooks/useMobile.tsx
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: core/templates/tree/vite_react/client/src/hooks/useToast.ts
  constant TOAST_LIMIT (line 9) | const TOAST_LIMIT = 1
  type ToasterToast (line 11) | type ToasterToast = ToastProps & {
  function genId (line 27) | function genId() {
  type ActionType (line 32) | type ActionType = typeof actionTypes
  type Action (line 34) | type Action =
  type State (line 52) | interface State {
  function dispatch (line 133) | function dispatch(action: Action) {
  type Toast (line 140) | type Toast = Omit<ToasterToast, "id">
  function toast (line 142) | function toast({ ...props }: Toast) {
  function useToast (line 173) | function useToast() {

FILE: core/templates/tree/vite_react/client/src/lib/utils.ts
  function cn (line 5) | function cn(...inputs: ClassValue[]) {

FILE: core/templates/tree/vite_react/client/src/pages/BlankPage.tsx
  function BlankPage (line 7) | function BlankPage() {

FILE: core/templates/tree/vite_react/client/src/pages/Login.tsx
  type LoginForm (line 24) | type LoginForm = {
  function Login (line 29) | function Login() {

FILE: core/templates/tree/vite_react/client/src/pages/Register.tsx
  type RegisterForm (line 24) | type RegisterForm = {
  function Register (line 29) | function Register() {

FILE: core/templates/tree/vite_react/server/services/llmService.js
  constant MAX_RETRIES (line 16) | const MAX_RETRIES = 3;
  constant RETRY_DELAY (line 17) | const RETRY_DELAY = 1000;
  function sleep (line 19) | async function sleep(ms) {
  function sendRequestToOpenAI (line 23) | async function sendRequestToOpenAI(model, message) {
  function sendRequestToAnthropic (line 40) | async function sendRequestToAnthropic(model, message) {
  function sendLLMRequest (line 59) | async function sendLLMRequest(provider, model, message) {

FILE: core/templates/tree/vite_react/server/services/userService.js
  class UserService (line 7) | class UserService {
    method list (line 8) | static async list() {
    method get (line 16) | static async get(id) {
    method getByEmail (line 24) | static async getByEmail(email) {
    method update (line 32) | static async update(id, data) {
    method delete (line 40) | static async delete(id) {
    method authenticateWithPassword (line 49) | static async authenticateWithPassword(email, password) {
    method create (line 68) | static async create({ email, password, name = '' }) {
    method setPassword (line 91) | static async setPassword(user, password) {

FILE: core/templates/tree/vite_react_swagger/client/src/App.tsx
  function App (line 13) | function App() {

FILE: core/templates/tree/vite_react_swagger/client/src/api/api.ts
  constant API_KEY (line 5) | const API_KEY = import.meta.env.VITE_API_KEY;
  constant EXTERNAL_API_URL (line 9) | const EXTERNAL_API_URL = import.meta.env.VITE_EXTERNAL_API_URL;

FILE: core/templates/tree/vite_react_swagger/client/src/components/Footer.tsx
  function Footer (line 2) | function Footer() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/Header.tsx
  function Header (line 8) | function Header() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/Layout.tsx
  function Layout (line 5) | function Layout() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ProtectedRoute.tsx
  function ProtectedRoute (line 5) | function ProtectedRoute({ children }: { children: React.ReactNode }) {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/badge.tsx
  type BadgeProps (line 27) | interface BadgeProps
  function Badge (line 31) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/button.tsx
  type ButtonProps (line 37) | interface ButtonProps

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/calendar.tsx
  type CalendarProps (line 9) | type CalendarProps = React.ComponentProps<typeof DayPicker>
  function Calendar (line 11) | function Calendar({

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/carousel.tsx
  type CarouselApi (line 11) | type CarouselApi = UseEmblaCarouselType[1]
  type UseCarouselParameters (line 12) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
  type CarouselOptions (line 13) | type CarouselOptions = UseCarouselParameters[0]
  type CarouselPlugin (line 14) | type CarouselPlugin = UseCarouselParameters[1]
  type CarouselProps (line 16) | type CarouselProps = {
  type CarouselContextProps (line 23) | type CarouselContextProps = {
  function useCarousel (line 34) | function useCarousel() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/chart.tsx
  constant THEMES (line 10) | const THEMES = { light: "", dark: ".dark" } as const
  type ChartConfig (line 12) | type ChartConfig = {
  type ChartContextProps (line 22) | type ChartContextProps = {
  function useChart (line 28) | function useChart() {
  function getPayloadConfigFromPayload (line 321) | function getPayloadConfigFromPayload(

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/form.tsx
  type FormFieldContextValue (line 21) | type FormFieldContextValue<
  type FormItemContextValue (line 68) | type FormItemContextValue = {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/pagination.tsx
  type PaginationLinkProps (line 38) | type PaginationLinkProps = {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/sheet.tsx
  type SheetContentProps (line 53) | interface SheetContentProps

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 21) | const SIDEBAR_COOKIE_NAME = "sidebar:state"
  constant SIDEBAR_COOKIE_MAX_AGE (line 22) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
  constant SIDEBAR_WIDTH (line 23) | const SIDEBAR_WIDTH = "16rem"
  constant SIDEBAR_WIDTH_MOBILE (line 24) | const SIDEBAR_WIDTH_MOBILE = "18rem"
  constant SIDEBAR_WIDTH_ICON (line 25) | const SIDEBAR_WIDTH_ICON = "3rem"
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 26) | const SIDEBAR_KEYBOARD_SHORTCUT = "b"
  type SidebarContext (line 28) | type SidebarContext = {
  function useSidebar (line 40) | function useSidebar() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/skeleton.tsx
  function Skeleton (line 4) | function Skeleton({

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/sonner.tsx
  type ToasterProps (line 7) | type ToasterProps = React.ComponentProps<typeof Sonner>

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/theme-provider.tsx
  type Theme (line 3) | type Theme = "dark" | "light" | "system"
  type ThemeProviderProps (line 5) | type ThemeProviderProps = {
  type ThemeProviderState (line 11) | type ThemeProviderState = {
  function ThemeProvider (line 23) | function ThemeProvider({

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/theme-toggle.tsx
  function ThemeToggle (line 7) | function ThemeToggle() {

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/toast.tsx
  type ToastProps (line 142) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
  type ToastActionElement (line 144) | type ToastActionElement = React.ReactElement<typeof ToastAction>

FILE: core/templates/tree/vite_react_swagger/client/src/components/ui/toaster.tsx
  function Toaster (line 12) | function Toaster() {

FILE: core/templates/tree/vite_react_swagger/client/src/contexts/AuthContext.tsx
  type AuthContextType (line 6) | type AuthContextType = {
  function AuthProvider (line 15) | function AuthProvider({ children }: { children: ReactNode }) {
  function useAuth (line 63) | function useAuth() {

FILE: core/templates/tree/vite_react_swagger/client/src/hooks/useMobile.tsx
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: core/templates/tree/vite_react_swagger/client/src/hooks/useToast.ts
  constant TOAST_LIMIT (line 9) | const TOAST_LIMIT = 1
  type ToasterToast (line 11) | type ToasterToast = ToastProps & {
  function genId (line 27) | function genId() {
  type ActionType (line 32) | type ActionType = typeof actionTypes
  type Action (line 34) | type Action =
  type State (line 52) | interface State {
  function dispatch (line 133) | function dispatch(action: Action) {
  type Toast (line 140) | type Toast = Omit<ToasterToast, "id">
  function toast (line 142) | function toast({ ...props }: Toast) {
  function useToast (line 173) | function useToast() {

FILE: core/templates/tree/vite_react_swagger/client/src/lib/utils.ts
  function cn (line 5) | function cn(...inputs: ClassValue[]) {

FILE: core/templates/tree/vite_react_swagger/client/src/pages/BlankPage.tsx
  function BlankPage (line 7) | function BlankPage() {

FILE: core/templates/tree/vite_react_swagger/client/src/pages/Login.tsx
  type LoginForm (line 24) | type LoginForm = {
  function Login (line 29) | function Login() {

FILE: core/templates/tree/vite_react_swagger/client/src/pages/Register.tsx
  type RegisterForm (line 24) | type RegisterForm = {
  function Register (line 29) | function Register() {

FILE: core/templates/vite_react.py
  class ViteReactProjectTemplate (line 4) | class ViteReactProjectTemplate(BaseProjectTemplate):
    method install_hook (line 122) | async def install_hook(self):

FILE: core/templates/vite_react_swagger.py
  class ViteReactSwaggerProjectTemplate (line 4) | class ViteReactSwaggerProjectTemplate(BaseProjectTemplate):
    method install_hook (line 107) | async def install_hook(self):

FILE: core/ui/api_server.py
  function send_stream_chunk (line 33) | async def send_stream_chunk(writer: asyncio.StreamWriter, message_type, ...
  class IPCServer (line 43) | class IPCServer:
    method __init__ (line 48) | def __init__(self, host: str, port: int, state_manager: StateManager):
    method _setup_handlers (line 64) | def _setup_handlers(self):
    method start (line 80) | async def start(self) -> bool:
    method stop (line 99) | async def stop(self):
    method _handle_client (line 107) | async def _handle_client(self, reader: asyncio.StreamReader, writer: a...
    method _process_message (line 152) | async def _process_message(self, message: Message, writer: asyncio.Str...
    method _send_response (line 169) | async def _send_response(self, writer: asyncio.StreamWriter, message: ...
    method _add_to_chat_history (line 184) | async def _add_to_chat_history(
    method _send_streaming_response (line 246) | async def _send_streaming_response(self, writer: asyncio.StreamWriter,...
    method _send_error (line 356) | async def _send_error(self, writer: asyncio.StreamWriter, error_messag...
    method _handle_start_chat (line 367) | async def _handle_start_chat(self, message: Message, writer: asyncio.S...
    method _handle_get_chat_history (line 383) | async def _handle_get_chat_history(self, message: Message, writer: asy...
    method _handle_epics_and_tasks (line 410) | async def _handle_epics_and_tasks(self, message: Message, writer: asyn...
    method _handle_project_info (line 434) | async def _handle_project_info(self, message: Message, writer: asyncio...
    method _handle_knowledge_base (line 449) | async def _handle_knowledge_base(self, message: Message, writer: async...
    method _handle_project_specs (line 484) | async def _handle_project_specs(self, message: Message, writer: asynci...
    method _handle_current_task_status (line 511) | async def _handle_current_task_status(self, message: Message, writer: ...
    method _handle_add_new_task (line 540) | async def _handle_add_new_task(self, message: Message, writer: asyncio...
    method _handle_start_another_task (line 600) | async def _handle_start_another_task(self, message: Message, writer: a...
    method _handle_chat_message (line 639) | async def _handle_chat_message(self, message: Message, writer: asyncio...
    method _handle_task_convo (line 661) | async def _handle_task_convo(self, message: Message, writer: asyncio.S...
    method _handle_edit_specs (line 702) | async def _handle_edit_specs(self, message: Message, writer: asyncio.S...
    method _handle_file_diff (line 735) | async def _handle_file_diff(self, message: Message, writer: asyncio.St...

FILE: core/ui/base.py
  class ProjectStage (line 7) | class ProjectStage(str, Enum):
  class UIClosedError (line 30) | class UIClosedError(Exception):
  class UserInterruptError (line 34) | class UserInterruptError(Exception):
  class UISource (line 38) | class UISource:
    method __init__ (line 52) | def __init__(self, display_name: str, type_name: str):
    method __str__ (line 62) | def __str__(self) -> str:
  class AgentSource (line 66) | class AgentSource(UISource):
    method __init__ (line 75) | def __init__(self, display_name: str, agent_type: str):
  class UserInput (line 85) | class UserInput(BaseModel):
  class UIBase (line 104) | class UIBase:
    method start (line 109) | async def start(self) -> bool:
    method stop (line 117) | async def stop(self):
    method send_stream_chunk (line 123) | async def send_stream_chunk(
    method send_user_input_history (line 141) | async def send_user_input_history(
    method send_message (line 158) | async def send_message(
    method send_key_expired (line 178) | async def send_key_expired(self, message: Optional[str] = None):
    method send_token_expired (line 184) | async def send_token_expired(self):
    method send_app_finished (line 190) | async def send_app_finished(
    method send_feature_finished (line 205) | async def send_feature_finished(
    method ask_question (line 220) | async def ask_question(
    method send_project_stage (line 264) | async def send_project_stage(self, data: dict):
    method send_epics_and_tasks (line 272) | async def send_epics_and_tasks(
    method send_task_progress (line 285) | async def send_task_progress(
    method send_step_progress (line 308) | async def send_step_progress(
    method send_modified_files (line 325) | async def send_modified_files(
    method send_data_about_logs (line 336) | async def send_data_about_logs(
    method send_run_command (line 347) | async def send_run_command(self, run_command: str):
    method send_app_link (line 355) | async def send_app_link(self, app_link: str):
    method open_editor (line 363) | async def open_editor(self, file: str, line: Optional[int] = None, wai...
    method send_project_info (line 372) | async def send_project_info(self, name: str, project_id: str, folder_n...
    method set_important_stream (line 383) | async def set_important_stream(self, important_stream: bool = True):
    method start_breakdown_stream (line 390) | async def start_breakdown_stream(self):
    method send_project_stats (line 396) | async def send_project_stats(self, stats: dict):
    method send_test_instructions (line 409) | async def send_test_instructions(
    method knowledge_base_update (line 421) | async def knowledge_base_update(self, knowledge_base: dict):
    method send_file_status (line 429) | async def send_file_status(
    method send_bug_hunter_status (line 442) | async def send_bug_hunter_status(self, status: str, num_cycles: int):
    method generate_diff (line 451) | async def generate_diff(
    method stop_app (line 474) | async def stop_app(self):
    method close_diff (line 480) | async def close_diff(self):
    method loading_finished (line 486) | async def loading_finished(self):
    method send_project_description (line 492) | async def send_project_description(self, state: dict):
    method send_features_list (line 500) | async def send_features_list(self, features: list[str]):
    method import_project (line 510) | async def import_project(self, project_dir: str):
    method send_back_logs (line 521) | async def send_back_logs(
    method send_fatal_error (line 536) | async def send_fatal_error(
    method send_front_logs_headers (line 545) | async def send_front_logs_headers(
    method clear_main_logs (line 562) | async def clear_main_logs(self):

FILE: core/ui/console.py
  class PlainConsoleUI (line 11) | class PlainConsoleUI(UIBase):
    method start (line 16) | async def start(self) -> bool:
    method stop (line 20) | async def stop(self):
    method send_stream_chunk (line 23) | async def send_stream_chunk(
    method send_user_input_history (line 37) | async def send_user_input_history(
    method send_message (line 49) | async def send_message(
    method send_key_expired (line 63) | async def send_key_expired(self, message: Optional[str] = None):
    method send_token_expired (line 67) | async def send_token_expired(self):
    method send_app_finished (line 70) | async def send_app_finished(
    method send_feature_finished (line 78) | async def send_feature_finished(
    method ask_question (line 86) | async def ask_question(
    method send_project_stage (line 134) | async def send_project_stage(self, data: dict):
    method send_epics_and_tasks (line 137) | async def send_epics_and_tasks(
    method send_task_progress (line 144) | async def send_task_progress(
    method send_step_progress (line 156) | async def send_step_progress(
    method send_modified_files (line 165) | async def send_modified_files(
    method send_data_about_logs (line 171) | async def send_data_about_logs(
    method send_run_command (line 177) | async def send_run_command(self, run_command: str):
    method send_app_link (line 180) | async def send_app_link(self, app_link: str):
    method open_editor (line 183) | async def open_editor(self, file: str, line: Optional[int] = None, wai...
    method send_project_info (line 186) | async def send_project_info(self, name: str, project_id: str, folder_n...
    method send_project_stats (line 189) | async def send_project_stats(self, stats: dict):
    method send_test_instructions (line 192) | async def send_test_instructions(
    method knowledge_base_update (line 197) | async def knowledge_base_update(self, knowledge_base: dict):
    method send_file_status (line 200) | async def send_file_status(
    method send_bug_hunter_status (line 205) | async def send_bug_hunter_status(self, status: str, num_cycles: int):
    method generate_diff (line 208) | async def generate_diff(
    method stop_app (line 220) | async def stop_app(self):
    method close_diff (line 223) | async def close_diff(self):
    method loading_finished (line 226) | async def loading_finished(self):
    method send_project_description (line 229) | async def send_project_description(self, state: dict):
    method send_features_list (line 232) | async def send_features_list(self, features: list[str]):
    method import_project (line 235) | async def import_project(self, project_dir: str):
    method set_important_stream (line 238) | async def set_important_stream(self, important_stream: bool = True):
    method start_breakdown_stream (line 241) | async def start_breakdown_stream(self):
    method send_back_logs (line 244) | async def send_back_logs(
    method send_fatal_error (line 250) | async def send_fatal_error(
    method send_front_logs_headers (line 259) | async def send_front_logs_headers(
    method clear_main_logs (line 268) | async def clear_main_logs(self):

FILE: core/ui/ipc_client.py
  class MessageType (line 21) | class MessageType(str, Enum):
  class Message (line 75) | class Message(BaseModel):
    method to_bytes (line 101) | def to_bytes(self) -> bytes:
    method from_bytes (line 108) | def from_bytes(self, data: bytes) -> "Message":
  class IPCClientUI (line 130) | class IPCClientUI(UIBase):
    method __init__ (line 135) | def __init__(self, config: LocalIPCConfig):
    method start (line 143) | async def start(self):
    method _send (line 156) | async def _send(self, type: MessageType, fake: Optional[bool] = False,...
    method _receive (line 174) | async def _receive(self) -> Message:
    method stop (line 203) | async def stop(self):
    method send_stream_chunk (line 219) | async def send_stream_chunk(
    method send_user_input_history (line 241) | async def send_user_input_history(
    method send_message (line 256) | async def send_message(
    method send_key_expired (line 278) | async def send_key_expired(self, message: Optional[str] = None):
    method send_token_expired (line 281) | async def send_token_expired(self):
    method send_app_finished (line 286) | async def send_app_finished(
    method send_feature_finished (line 301) | async def send_feature_finished(
    method ask_question (line 316) | async def ask_question(
    method send_project_stage (line 396) | async def send_project_stage(self, data: dict):
    method send_epics_and_tasks (line 399) | async def send_epics_and_tasks(
    method send_task_progress (line 412) | async def send_task_progress(
    method send_modified_files (line 437) | async def send_modified_files(
    method send_step_progress (line 446) | async def send_step_progress(
    method send_data_about_logs (line 465) | async def send_data_about_logs(
    method send_run_command (line 474) | async def send_run_command(self, run_command: str):
    method send_app_link (line 480) | async def send_app_link(self, app_link: str):
    method open_editor (line 486) | async def open_editor(self, file: str, line: Optional[int] = None, wai...
    method send_project_info (line 501) | async def send_project_info(self, name: str, project_id: str, folder_n...
    method set_important_stream (line 513) | async def set_important_stream(self, important_stream: bool = True):
    method start_breakdown_stream (line 519) | async def start_breakdown_stream(self):
    method send_project_stats (line 525) | async def send_project_stats(self, stats: dict):
    method send_test_instructions (line 531) | async def send_test_instructions(
    method knowledge_base_update (line 555) | async def knowledge_base_update(self, knowledge_base: dict):
    method send_file_status (line 565) | async def send_file_status(
    method send_bug_hunter_status (line 578) | async def send_bug_hunter_status(self, status: str, num_cycles: int):
    method generate_diff (line 587) | async def generate_diff(
    method stop_app (line 610) | async def stop_app(self):
    method close_diff (line 614) | async def close_diff(self):
    method loading_finished (line 618) | async def loading_finished(self):
    method send_project_description (line 622) | async def send_project_description(self, state: dict):
    method send_features_list (line 625) | async def send_features_list(self, features: list[str]):
    method import_project (line 628) | async def import_project(self, project_dir: str):
    method send_back_logs (line 631) | async def send_back_logs(
    method send_fatal_error (line 641) | async def send_fatal_error(
    method send_front_logs_headers (line 656) | async def send_front_logs_headers(
    method clear_main_logs (line 673) | async def clear_main_logs(self):
    method load_front_logs (line 678) | async def load_front_logs(

FILE: core/ui/virtual.py
  class VirtualUI (line 9) | class VirtualUI(UIBase):
    method __init__ (line 14) | def __init__(self, inputs: list[dict[str, str]]):
    method start (line 17) | async def start(self) -> bool:
    method stop (line 21) | async def stop(self):
    method send_stream_chunk (line 24) | async def send_stream_chunk(
    method send_user_input_history (line 38) | async def send_user_input_history(
    method send_message (line 46) | async def send_message(
    method send_key_expired (line 59) | async def send_key_expired(self, message: Optional[str] = None):
    method send_token_expired (line 62) | async def send_token_expired(self):
    method send_app_finished (line 65) | async def send_app_finished(
    method send_feature_finished (line 73) | async def send_feature_finished(
    method ask_question (line 81) | async def ask_question(
    method send_project_stage (line 120) | async def send_project_stage(self, data: dict):
    method send_epics_and_tasks (line 123) | async def send_epics_and_tasks(
    method send_task_progress (line 130) | async def send_task_progress(
    method send_step_progress (line 142) | async def send_step_progress(
    method send_data_about_logs (line 151) | async def send_data_about_logs(
    method send_modified_files (line 157) | async def send_modified_files(
    method send_run_command (line 163) | async def send_run_command(self, run_command: str):
    method send_app_link (line 166) | async def send_app_link(self, app_link: str):
    method open_editor (line 169) | async def open_editor(self, file: str, line: Optional[int] = None, wai...
    method send_project_info (line 172) | async def send_project_info(self, name: str, project_id: str, folder_n...
    method set_important_stream (line 175) | async def set_important_stream(self, important_stream: bool = True):
    method start_breakdown_stream (line 178) | async def start_breakdown_stream(self):
    method send_project_stats (line 181) | async def send_project_stats(self, stats: dict):
    method send_test_instructions (line 184) | async def send_test_instructions(self, test_instructions: str, project...
    method knowledge_base_update (line 187) | async def knowledge_base_update(self, knowledge_base: dict):
    method send_file_status (line 190) | async def send_file_status(self, file_path: str, file_status: str, sou...
    method send_bug_hunter_status (line 193) | async def send_bug_hunter_status(self, status: str, num_cycles: int):
    method generate_diff (line 196) | async def generate_diff(
    method stop_app (line 207) | async def stop_app(self):
    method close_diff (line 210) | async def close_diff(self):
    method loading_finished (line 213) | async def loading_finished(self):
    method send_project_description (line 216) | async def send_project_description(self, state: dict):
    method send_features_list (line 219) | async def send_features_list(self, features: list[str]):
    method import_project (line 222) | async def import_project(self, project_dir: str):
    method send_back_logs (line 225) | async def send_back_logs(
    method send_fatal_error (line 231) | async def send_fatal_error(
    method send_front_logs_headers (line 240) | async def send_front_logs_headers(
    method clear_main_logs (line 249) | async def clear_main_logs(self):

FILE: core/utils/text.py
  function trim_logs (line 6) | def trim_logs(logs: str) -> str:

FILE: tests/agents/test_base.py
  class AgentUnderTest (line 9) | class AgentUnderTest(BaseAgent):
  function test_send_message (line 15) | async def test_send_message():
  function test_stream_handler (line 30) | async def test_stream_handler():
  function test_ask_question (line 42) | async def test_ask_question():
  function test_get_llm (line 71) | async def test_get_llm(mock_BaseLLMClient):

FILE: tests/agents/test_convo.py
  function test_init (line 8) | def test_init():
  function test_fork (line 18) | def test_fork():
  function test_require_schema (line 32) | def test_require_schema():

FILE: tests/agents/test_external_docs.py
  function test_stores_documentation_snippets_for_task (line 10) | async def test_stores_documentation_snippets_for_task(agentcontext):
  function test_continues_without_docs_for_invalid_docset (line 25) | async def test_continues_without_docs_for_invalid_docset(agentcontext):
  function test_continues_without_docs_if_api_is_down (line 40) | async def test_continues_without_docs_if_api_is_down(agentcontext):

FILE: tests/agents/test_orchestrator.py
  function test_offline_changes_check_restores_if_workspace_empty (line 9) | async def test_offline_changes_check_restores_if_workspace_empty():
  function test_offline_changes_check_imports_changes_from_disk (line 19) | async def test_offline_changes_check_imports_changes_from_disk():
  function test_offline_changes_check_restores_changes_from_db (line 32) | async def test_offline_changes_check_restores_changes_from_db():
  function test_import_if_new_files (line 44) | async def test_import_if_new_files(agentcontext):
  function test_import_if_modified_files (line 64) | async def test_import_if_modified_files(agentcontext):
  function test_import_if_deleted_files (line 85) | async def test_import_if_deleted_files(agentcontext):

FILE: tests/agents/test_tech_lead.py
  function test_create_initial_epic (line 12) | async def test_create_initial_epic(agentcontext):
  function test_apply_project_template (line 35) | async def test_apply_project_template(agentcontext):
  function test_ask_for_feature (line 52) | async def test_ask_for_feature(agentcontext):
  function test_plan_epic (line 89) | async def test_plan_epic(agentcontext):

FILE: tests/cli/test_cli.py
  function write_test_config (line 23) | def write_test_config(tmp_path):
  function test_parse_arguments (line 34) | def test_parse_arguments(mock_ArgumentParser):
  function test_parse_llm_endpoint (line 81) | def test_parse_llm_endpoint(value, expected):
  function test_parse_llm_key (line 98) | def test_parse_llm_key(value, expected):
  function test_load_config_not_found (line 108) | def test_load_config_not_found(mock_import_from_dotenv, tmp_path, capsys):
  function test_load_config_not_json (line 119) | def test_load_config_not_json(tmp_path, capsys):
  function test_load_config_defaults (line 130) | def test_load_config_defaults(tmp_path):
  function test_load_config_overridden (line 141) | def test_load_config_overridden(tmp_path):
  function test_show_default_config (line 164) | def test_show_default_config(capsys):
  function test_list_projects_json (line 173) | async def test_list_projects_json(mock_StateManager, capsys):
  function test_list_projects (line 204) | async def test_list_projects(mock_StateManager, capsys):
  function test_load_project (line 254) | async def test_load_project(args, kwargs, retval, should_succeed, capsys):
  function test_init (line 269) | def test_init(tmp_path):
  function test_main (line 298) | async def test_main(mock_Orchestrator, args, run_orchestrator, retval, t...
  function test_main_handles_crash (line 331) | async def test_main_handles_crash(mock_Orchestrator, tmp_path):

FILE: tests/config/test_config.py
  function test_parse_config (line 40) | def test_parse_config():
  function test_default_agent_llm_config (line 53) | def test_default_agent_llm_config():
  function test_builtin_defaults (line 64) | def test_builtin_defaults():
  function test_unsupported_provider (line 73) | def test_unsupported_provider():
  function test_load_from_file_with_comments (line 97) | def test_load_from_file_with_comments():
  function test_default_config (line 104) | def test_default_config():
  function test_encodings (line 120) | def test_encodings(encoding, bom, tmp_path):

FILE: tests/config/test_env_importer.py
  function test_convert_config (line 5) | def test_convert_config():
  function test_convert_openai_config (line 31) | def test_convert_openai_config():
  function test_convert_azure_config (line 45) | def test_convert_azure_config():
  function test_convert_openrouter_config (line 57) | def test_convert_openrouter_config():
  function test_convert_anthropic_config (line 69) | def test_convert_anthropic_config():

FILE: tests/config/test_version.py
  function test_get_package_version (line 8) | def test_get_package_version():
  function test_get_git_version (line 16) | def test_get_git_version():
  function test_get_version (line 24) | def test_get_version(_mock_get_package_version, _mock_get_git_commit):

FILE: tests/conftest.py
  function disable_test_telemetry (line 15) | def disable_test_telemetry(monkeypatch):
  function testmanager (line 20) | async def testmanager():
  function testdb (line 35) | async def testdb(testmanager):
  function agentcontext (line 47) | async def agentcontext(testmanager):

FILE: tests/db/factories.py
  function create_project_state (line 4) | def create_project_state(project_name="Test Project", branch_name=Branch...

FILE: tests/db/test_branch.py
  function test_get_by_id_requires_valid_uuid (line 11) | async def test_get_by_id_requires_valid_uuid(testdb):
  function test_get_by_id_no_match (line 17) | async def test_get_by_id_no_match(testdb):
  function test_get_by_id (line 24) | async def test_get_by_id(testdb):
  function test_get_last_state_no_steps (line 36) | async def test_get_last_state_no_steps(testdb):
  function test_get_last_state (line 47) | async def test_get_last_state(testdb):
  function test_get_last_state_no_session (line 63) | async def test_get_last_state_no_session():

FILE: tests/db/test_db.py
  function test_migrations (line 11) | def test_migrations(tmp_path):
  function test_select_empty (line 17) | async def test_select_empty(testdb):
  function test_create_select_project_branch_state (line 24) | async def test_create_select_project_branch_state(testdb):
  function test_deleting_project_state_clears_back_references (line 37) | async def test_deleting_project_state_clears_back_references(testdb):

FILE: tests/db/test_project.py
  function test_get_by_id_requires_valid_uuid (line 16) | async def test_get_by_id_requires_valid_uuid(testdb):
  function test_get_by_id_no_match (line 22) | async def test_get_by_id_no_match(testdb):
  function test_get_by_id (line 29) | async def test_get_by_id(testdb):
  function test_delete_by_id (line 39) | async def test_delete_by_id(testdb):
  function test_get_branch_no_match (line 50) | async def test_get_branch_no_match(testdb):
  function test_get_branch (line 60) | async def test_get_branch(testdb):
  function test_get_branch_no_session (line 72) | async def test_get_branch_no_session():
  function test_get_all_projects (line 80) | async def test_get_all_projects(testdb, capsys):
  function test_default_folder_name (line 135) | async def test_default_folder_name(testdb):
  function test_get_folder_from_project_name (line 154) | def test_get_folder_from_project_name(project_name, expected_folder_name):

FILE: tests/db/test_project_state.py
  function test_get_by_id (line 11) | async def test_get_by_id(testdb):
  function test_get_last_state_no_session (line 22) | async def test_get_last_state_no_session():
  function test_get_by_id_preloads_branch_project_files (line 31) | async def test_get_by_id_preloads_branch_project_files(testdb):
  function test_create_next_state_clones_files (line 51) | async def test_create_next_state_clones_files(testdb):
  function test_create_next_deep_copies_fields (line 68) | async def test_create_next_deep_copies_fields(testdb):
  function test_deleting_state_removes_child_objects (line 96) | async def test_deleting_state_removes_child_objects(testdb):
  function test_completing_unfinished_steps (line 123) | async def test_completing_unfinished_steps(testdb):
  function test_completing_unfinished_iterations (line 148) | async def test_completing_unfinished_iterations(testdb):
  function test_completing_unfinished_tasks (line 173) | async def test_completing_unfinished_tasks(testdb):
  function test_completing_unfinished_epics (line 199) | async def test_completing_unfinished_epics(testdb):

FILE: tests/disk/test_ignore.py
  function test_ignore_paths (line 28) | def test_ignore_paths(_mock_open, _mock_isfile, path, expected):
  function test_ignore_large_files (line 52) | def test_ignore_large_files(
  function test_ignore_binary (line 67) | def test_ignore_binary(_mock_open, _mock_isfile):

FILE: tests/disk/test_vfs.py
  function test_memory_vfs (line 7) | def test_memory_vfs():
  function test_local_disk_vfs (line 31) | def test_local_disk_vfs(tmp_path):
  function test_local_disk_vfs_with_matcher (line 55) | def test_local_disk_vfs_with_matcher(tmp_path):

FILE: tests/integration/llm/test_anthropic.py
  function test_incorrect_key (line 24) | async def test_incorrect_key():
  function test_unknown_model (line 43) | async def test_unknown_model():
  function test_anthropic_success (line 58) | async def test_anthropic_success():
  function test_anthropic_json_mode (line 83) | async def test_anthropic_json_mode():
  function test_context_too_large (line 106) | async def test_context_too_large():

FILE: tests/integration/llm/test_groq.py
  function test_incorrect_key (line 23) | async def test_incorrect_key():
  function test_unknown_model (line 42) | async def test_unknown_model():
  function test_groq_success (line 57) | async def test_groq_success():
  function test_groq_json_mode (line 85) | async def test_groq_json_mode():
  function test_context_too_large (line 104) | async def test_context_too_large():

FILE: tests/integration/llm/test_openai.py
  function test_incorrect_key (line 23) | async def test_incorrect_key():
  function test_unknown_model (line 42) | async def test_unknown_model():
  function test_openai_success (line 57) | async def test_openai_success():
  function test_openai_json_mode (line 82) | async def test_openai_json_mode():
  function test_context_too_large (line 101) | async def test_context_too_large():

FILE: tests/llm/test_convo.py
  function test_convo_constructor_without_content (line 6) | def test_convo_constructor_without_content():
  function test_convo_constructor_with_content (line 11) | def test_convo_constructor_with_content():
  function test_convo_constructor_with_whitespace_content (line 18) | def test_convo_constructor_with_whitespace_content():
  function test_add_unknown_role_raises_value_error (line 25) | def test_add_unknown_role_raises_value_error():
  function test_add_adds_message_with_role_and_content (line 32) | def test_add_adds_message_with_role_and_content():
  function test_add_adds_message_with_role_content_and_name (line 38) | def test_add_adds_message_with_role_content_and_name():
  function test_add_dedents_string_content (line 44) | def test_add_dedents_string_content():
  function test_add_forwards_dict_content (line 50) | def test_add_forwards_dict_content():
  function test_system_adds_system_message (line 56) | def test_system_adds_system_message():
  function test_system_adds_system_message_with_name (line 62) | def test_system_adds_system_message_with_name():
  function test_system_dedents_content (line 68) | def test_system_dedents_content():
  function test_system_preserves_lines_in_content (line 74) | def test_system_preserves_lines_in_content():
  function test_user_adds_user_message (line 80) | def test_user_adds_user_message():
  function test_user_adds_user_message_with_name (line 86) | def test_user_adds_user_message_with_name():
  function test_user_raises_error_if_content_is_empty_string (line 96) | def test_user_raises_error_if_content_is_empty_string():
  function test_user_raises_error_if_content_is_none (line 102) | def test_user_raises_error_if_content_is_none():
  function test_assistant_adds_correct_message (line 108) | def test_assistant_adds_correct_message():
  function test_assistant_dedents_content (line 114) | def test_assistant_dedents_content():
  function test_assistant_adds_name_if_provided (line 120) | def test_assistant_adds_name_if_provided():
  function test_assistant_returns_self (line 126) | def test_assistant_returns_self():
  function test_function_message_added_correctly (line 132) | def test_function_message_added_correctly():
  function test_function_message_with_name_added_correctly (line 140) | def test_function_message_with_name_added_correctly():
  function test_function_message_content_dedented (line 149) | def test_function_message_content_dedented():
  function test_function_message_return_convo_object (line 157) | def test_function_message_return_convo_object():
  function test_function_message_with_empty_content (line 163) | def test_function_message_with_empty_content():
  function test_function_message_with_non_string_content (line 169) | def test_function_message_with_non_string_content():
  function test_convo_fork (line 175) | def test_convo_fork():
  function test_convo_fork_with_no_messages (line 187) | def test_convo_fork_with_no_messages():
  function test_convo_fork_with_multiple_messages (line 197) | def test_convo_fork_with_multiple_messages():
  function test_after_with_empty_convos (line 208) | def test_after_with_empty_convos():
  function test_after_with_no_common_messages (line 215) | def test_after_with_no_common_messages():
  function test_after_with_some_common_messages (line 224) | def test_after_with_some_common_messages():
  function test_after_with_all_common_messages (line 233) | def test_after_with_all_common_messages():
  function test_after_with_more_messages_in_parent_convo (line 241) | def test_after_with_more_messages_in_parent_convo():
  function test_last_empty_convo (line 250) | def test_last_empty_convo():
  function test_last_single_message_convo (line 255) | def test_last_single_message_convo():
  function test_last_multiple_messages_convo (line 261) | def test_last_multiple_messages_convo():
  function test_last_after_fork (line 268) | def test_last_after_fork():
  function test_last_after_deepcopy (line 277) | def test_last_after_deepcopy():
  function test_message_iterator (line 288) | def test_message_iterator():

FILE: tests/llm/test_openai.py
  function mock_response_generator (line 13) | async def mock_response_generator(*content):
  function test_openai_calls_gpt (line 23) | async def test_openai_calls_gpt(mock_AsyncOpenAI, mock_state_manager):
  function test_openai_stream_handler (line 68) | async def test_openai_stream_handler(mock_AsyncOpenAI, mock_state_manager):
  function test_openai_parser_with_retries (line 98) | async def test_openai_parser_with_retries(mock_AsyncOpenAI, mock_state_m...
  function test_openai_parser_fails (line 147) | async def test_openai_parser_fails(mock_AsyncOpenAI, mock_state_manager):
  function test_openai_error_handler_success (line 179) | async def test_openai_error_handler_success(mock_AsyncOpenAI, mock_state...
  function test_openai_error_handler_failure (line 226) | async def test_openai_error_handler_failure(mock_AsyncOpenAI, mock_state...
  function test_openai_rate_limit_parser (line 271) | def test_openai_rate_limit_parser(

FILE: tests/llm/test_parser.py
  function test_multi_code_block_parser (line 25) | def test_multi_code_block_parser(input, expected):
  function test_code_block_parser (line 39) | def test_code_block_parser(input, expected):
  function test_parse_json_no_spec (line 63) | def test_parse_json_no_spec(input, strict, expected):
  function test_parse_json_with_spec (line 123) | def test_parse_json_with_spec(input, expected):
  function test_parse_json_schema (line 148) | def test_parse_json_schema():
  function test_enum_parser (line 181) | def test_enum_parser(input, expected):
  function test_optional_block_parser (line 204) | def test_optional_block_parser(input, expected):

FILE: tests/llm/test_prompt.py
  function test_format_template (line 7) | def test_format_template():
  function test_jinja_string_template (line 19) | def test_jinja_string_template():
  function test_jinja_template_catches_undefined_variable (line 37) | def test_jinja_template_catches_undefined_variable():
  function test_jinja_file_template (line 44) | def test_jinja_file_template():
  function test_jinja_file_template_nonexistent_directory (line 57) | def test_jinja_file_template_nonexistent_directory():

FILE: tests/log/test_log.py
  function test_file_handler (line 7) | def test_file_handler(tmp_path):
  function test_log_level (line 24) | def test_log_level(capsys):

FILE: tests/proc/test_process_manager.py
  function test_local_process_start_terminate (line 14) | async def test_local_process_start_terminate(tmp_path):
  function test_local_process_wait (line 39) | async def test_local_process_wait(tmp_path):
  function test_process_manager_run_command_capture_stdout (line 58) | async def test_process_manager_run_command_capture_stdout(tmp_path):
  function test_process_manager_run_command_capture_stderr (line 75) | async def test_process_manager_run_command_capture_stderr(tmp_path):
  function test_process_manager_start_list_terminate (line 92) | async def test_process_manager_start_list_terminate(tmp_path):
  function test_watcher (line 119) | async def test_watcher(tmp_path):

FILE: tests/state/test_state_manager.py
  function test_list_projects_empty (line 11) | async def test_list_projects_empty(testmanager):
  function test_create_project (line 19) | async def test_create_project(mock_get_config, testmanager):
  function test_load_project (line 40) | async def test_load_project(mock_get_config, testmanager):
  function test_delete_project (line 52) | async def test_delete_project(mock_get_config, testmanager):
  function test_load_project_branch (line 68) | async def test_load_project_branch(mock_get_config, testmanager):
  function test_load_nonexistent_step (line 81) | async def test_load_nonexistent_step(mock_get_config, testmanager):
  function test_load_specific_step (line 92) | async def test_load_specific_step(mock_get_config, testmanager):
  function test_commit (line 105) | async def test_commit(mock_get_config, testmanager):
  function test_save_file (line 137) | async def test_save_file(mock_get_config, testmanager):
  function test_importing_changed_files_to_db (line 170) | async def test_importing_changed_files_to_db(mock_get_config, tmpdir, te...
  function test_restoring_files_from_db (line 202) | async def test_restoring_files_from_db(mock_get_config, tmpdir, testmana...

FILE: tests/telemetry/test_telemetry.py
  function mock_httpx_post (line 12) | async def mock_httpx_post():
  function test_clear_data_resets_data (line 24) | def test_clear_data_resets_data():
  function test_clear_data_resets_times (line 45) | def test_clear_data_resets_times():
  function test_clear_counter_resets_times_but_leaves_data (line 56) | def test_clear_counter_resets_times_but_leaves_data():
  function test_set_updates_data (line 70) | def test_set_updates_data(mock_settings):
  function test_set_ignores_unknown_field (line 78) | def test_set_ignores_unknown_field(mock_settings):
  function test_inc_increments_known_data_field (line 86) | def test_inc_increments_known_data_field(mock_settings):
  function test_inc_ignores_unknown_data_field (line 94) | def test_inc_ignores_unknown_data_field(mock_settings):
  function test_start_with_telemetry_enabled (line 104) | def test_start_with_telemetry_enabled(mock_settings, mock_time, mock_get...
  function test_stop_when_not_enabled_does_nothing (line 115) | def test_stop_when_not_enabled_does_nothing(mock_settings):
  function test_stop_calculates_elapsed_time (line 126) | def test_stop_calculates_elapsed_time(mock_settings, mock_time):
  function test_send_enabled_and_successful (line 140) | async def test_send_enabled_and_successful(mock_settings, mock_getenv, m...
  function test_send_enabled_but_post_fails (line 159) | async def test_send_enabled_but_post_fails(mock_settings, mock_getenv, m...
  function test_send_not_enabled (line 178) | async def test_send_not_enabled(mock_settings, mock_httpx_post):
  function test_send_no_endpoint_configured (line 190) | async def test_send_no_endpoint_configured(mock_settings, mock_getenv, m...
  function test_send_clears_counters_after_sending (line 203) | async def test_send_clears_counters_after_sending(mock_settings, mock_ge...
  function test_record_crash (line 217) | def test_record_crash(mock_settings):
  function test_record_llm_request (line 235) | def test_record_llm_request(mock_settings):
  function test_calculate_statistics (line 257) | def test_calculate_statistics(mock_settings):

FILE: tests/templates/test_templates.py
  function test_render_react_express_sql (line 12) | async def test_render_react_express_sql(mock_get_config, testmanager):
  function test_render_react_express_nosql (line 37) | async def test_render_react_express_nosql(mock_get_config, testmanager):
  function test_render_node_express_mongoose (line 63) | async def test_render_node_express_mongoose(mock_get_config, testmanager):

FILE: tests/ui/test_console.py
  function test_send_message (line 10) | async def test_send_message(capsys):
  function test_stream (line 24) | async def test_stream(capsys):
  function test_ask_question_simple (line 39) | async def test_ask_question_simple(mock_PromptSession):
  function test_ask_question_with_buttons (line 57) | async def test_ask_question_with_buttons(mock_PromptSession):
  function test_ask_question_interrupted (line 78) | async def test_ask_question_interrupted(mock_PromptSession):

FILE: tests/ui/test_ipc_client.py
  class IPCServer (line 18) | class IPCServer:
    method __init__ (line 23) | def __init__(self, responses: list[dict]):
    method handle_connection (line 39) | async def handle_connection(
    method __aenter__ (line 72) | async def __aenter__(self) -> tuple[int, list]:
    method __aexit__ (line 77) | async def __aexit__(self, exc_type, exc_val, exc_tb):
  function test_send_message (line 86) | async def test_send_message():
  function test_stream (line 129) | async def test_stream():
  function test_server_not_running (line 185) | async def test_server_not_running():
  function test_server_closes_connection (line 193) | async def test_server_closes_connection():
  function test_ask_question (line 205) | async def test_ask_question():
  function test_ask_question_buttons (line 227) | async def test_ask_question_buttons():
  function test_ask_question_buttons_only_with_default (line 256) | async def test_ask_question_buttons_only_with_default():
  function test_handle_garbage_response (line 286) | async def test_handle_garbage_response():
Condensed preview — 510 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,614K chars).
[
  {
    "path": ".gitattributes",
    "chars": 25,
    "preview": "vsc-dl-x64 export-ignore\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 5050,
    "preview": "# File: CODEOWNERS\n* @Pythagora-io/maintainers\n\n# File: .github/ip_assignment.yml\nassign_to_owner: true\nlicense: FSL-1.1"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 5477,
    "preview": "\n# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make particip"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.yml",
    "chars": 1853,
    "preview": "name: Bug Report\ndescription: File a bug report\ntitle: \"[Bug]: \"\nlabels:\n  - bug\nbody:\n  - type: markdown\n    attributes"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.yml",
    "chars": 1322,
    "preview": "name: Feature request\ndescription: Suggest a feature or improvement\ntitle: \"[Enhancement]: \"\nlabels:\n  - enhancement\nbod"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/howto.yml",
    "chars": 2054,
    "preview": "name: How do I...?\ndescription: Ask for help if you're stuck\ntitle: \"[Howto]: \"\nlabels:\n  - question\nbody:\n  - type: mar"
  },
  {
    "path": ".github/copyright_template.txt",
    "chars": 137,
    "preview": "Copyright (c) 2024 Pythagora Technologies Inc. All rights reserved.\nThis project is licensed under the terms of the FSL-"
  },
  {
    "path": ".github/ip_assignment.yml",
    "chars": 43,
    "preview": "assign_to_owner: true\nlicense: FSL-1.1-MIT\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 877,
    "preview": "name: Run unit tests\n\non:\n  push:\n    branches: [ \"main\" ]\n  pull_request:\n    branches: [ \"main\", \"rewrite\" ]\n\njobs:\n  "
  },
  {
    "path": ".github/workflows/cloud-staging-build.yaml",
    "chars": 5855,
    "preview": "name: Staging Cloud Build and Deploy\n\non:\n  workflow_dispatch:\n    inputs:\n      extension_commit_sha:\n        descripti"
  },
  {
    "path": ".gitignore",
    "chars": 225,
    "preview": "__pycache__/\n.venv/\nvenv/\n.vscode/\n.idea/\nhtmlcov/\ndist/\nworkspace/\npilot-env/\nvenv/\ndata/\n\n.coverage\n*.code-workspace\n."
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 727,
    "preview": "fail_fast: true\nrepos:\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    # Ruff version.\n    rev: v0.3.5\n    ho"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 180,
    "preview": "#  (2025-08-25)\n\n\n### Reverts\n\n* Revert \"Implemented weekend discount\" ([734e0c7](https://github.com/Pythagora-io/pythag"
  },
  {
    "path": "Dockerfile",
    "chars": 3713,
    "preview": "# Use Ubuntu 22.04 as the base image with multi-arch support\nFROM ubuntu:22.04\n\n# Use buildx args for multi-arch support"
  },
  {
    "path": "LICENSE",
    "chars": 4228,
    "preview": "# Functional Source License, Version 1.1, MIT Future License\n\n## Abbreviation\n\nFSL-1.1-MIT\n\n## Notice\n\nCopyright 2024 Py"
  },
  {
    "path": "README.md",
    "chars": 10299,
    "preview": "<div align=\"center\">\n\n# 🧑‍✈️ GPT PILOT 🧑‍✈️\n\n</div>\n\n---\n\n<div align=\"center\">\n\n[![Discord](https://img.shields.io/badge"
  },
  {
    "path": "cloud/config-docker.json",
    "chars": 3570,
    "preview": "{\n  \"llm\": {\n    \"openai\": {\n      \"base_url\": null,\n      \"api_key\": null,\n      \"connect_timeout\": 60.0,\n      \"read_t"
  },
  {
    "path": "cloud/entrypoint.sh",
    "chars": 936,
    "preview": "#!/bin/bash\n\nset -e\n# Production instances are slow with date command and stderr\n# export PS4='+ $(date \"+%Y-%m-%d %H:%M"
  },
  {
    "path": "cloud/on-event-extension-install.sh",
    "chars": 988,
    "preview": "#!/bin/bash\n\nset -e\n\nVSCODE_SERVER_PORT=8080\n\n# Create workspace directory and settings\nmkdir -p /pythagora/pythagora-co"
  },
  {
    "path": "cloud/posthog.html",
    "chars": 2020,
    "preview": "<script>\n    !function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e"
  },
  {
    "path": "cloud/settings.json",
    "chars": 1116,
    "preview": "{\n    \"workbench.startupEditor\": \"none\",\n    \"workbench.statusBar.visible\": false,\n    \"workbench.editor.showTabs\": \"non"
  },
  {
    "path": "cloud/setup-dependencies.sh",
    "chars": 4303,
    "preview": "#!/bin/bash\nset -e\n\n# Set environment variables\nexport DEBIAN_FRONTEND=noninteractive\nexport TZ=Etc/UTC\n\n# IMPORTANT: Cr"
  },
  {
    "path": "core/agents/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/agents/architect.py",
    "chars": 11373,
    "preview": "import json\nfrom enum import Enum\nfrom typing import Any, Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom core.age"
  },
  {
    "path": "core/agents/base.py",
    "chars": 8346,
    "preview": "from typing import Any, Callable, Optional\n\nfrom core.agents.response import AgentResponse\nfrom core.config import get_c"
  },
  {
    "path": "core/agents/bug_hunter.py",
    "chars": 19245,
    "preview": "import asyncio\nimport json\nfrom enum import Enum\n\nfrom pydantic import BaseModel, Field\n\nfrom core.agents.base import Ba"
  },
  {
    "path": "core/agents/code_monkey.py",
    "chars": 9030,
    "preview": "import asyncio\nimport re\nfrom enum import Enum\nfrom typing import Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom "
  },
  {
    "path": "core/agents/convo.py",
    "chars": 4646,
    "preview": "import json\nimport sys\nfrom copy import deepcopy\nfrom typing import TYPE_CHECKING, Optional\n\nimport jsonref\nfrom pydanti"
  },
  {
    "path": "core/agents/developer.py",
    "chars": 20028,
    "preview": "import json\nfrom enum import Enum\nfrom typing import Annotated, Literal, Union\nfrom uuid import UUID, uuid4\n\nfrom pydant"
  },
  {
    "path": "core/agents/error_handler.py",
    "chars": 4795,
    "preview": "from uuid import uuid4\n\nfrom core.agents.base import BaseAgent\nfrom core.agents.convo import AgentConvo\nfrom core.agents"
  },
  {
    "path": "core/agents/executor.py",
    "chars": 6225,
    "preview": "from datetime import datetime, timezone\nfrom typing import Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom core.ag"
  },
  {
    "path": "core/agents/external_docs.py",
    "chars": 6733,
    "preview": "import asyncio\nfrom urllib.parse import urljoin\n\nimport httpx\nfrom pydantic import BaseModel\n\nfrom core.agents.base impo"
  },
  {
    "path": "core/agents/frontend.py",
    "chars": 27297,
    "preview": "import asyncio\nimport json\nimport os\nimport sys\nfrom urllib.parse import urljoin\n\nimport httpx\n\nfrom core.agents.base im"
  },
  {
    "path": "core/agents/git.py",
    "chars": 6280,
    "preview": "import os\nfrom typing import Optional\n\nfrom core.agents.convo import AgentConvo\nfrom core.config.magic_words import GITI"
  },
  {
    "path": "core/agents/human_input.py",
    "chars": 1738,
    "preview": "from core.agents.base import BaseAgent\nfrom core.agents.response import AgentResponse, ResponseType\nfrom core.config.act"
  },
  {
    "path": "core/agents/importer.py",
    "chars": 3658,
    "preview": "from uuid import uuid4\n\nfrom core.agents.base import BaseAgent\nfrom core.agents.convo import AgentConvo\nfrom core.agents"
  },
  {
    "path": "core/agents/legacy_handler.py",
    "chars": 476,
    "preview": "from core.agents.base import BaseAgent\nfrom core.agents.response import AgentResponse\n\n\nclass LegacyHandler(BaseAgent):\n"
  },
  {
    "path": "core/agents/mixins.py",
    "chars": 7559,
    "preview": "import asyncio\nimport json\nfrom typing import List, Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom core.agents.co"
  },
  {
    "path": "core/agents/orchestrator.py",
    "chars": 30705,
    "preview": "import asyncio\nimport json\nimport os\nimport re\nfrom typing import List, Optional, Union\n\nfrom core.agents.architect impo"
  },
  {
    "path": "core/agents/problem_solver.py",
    "chars": 5357,
    "preview": "from typing import Optional\n\nfrom pydantic import BaseModel, Field\n\nfrom core.agents.base import BaseAgent\nfrom core.age"
  },
  {
    "path": "core/agents/response.py",
    "chars": 3441,
    "preview": "from enum import Enum\nfrom typing import TYPE_CHECKING, Optional\n\nfrom core.log import get_logger\n\nif TYPE_CHECKING:\n   "
  },
  {
    "path": "core/agents/spec_writer.py",
    "chars": 15309,
    "preview": "import secrets\n\nfrom core.agents.base import BaseAgent\nfrom core.agents.convo import AgentConvo\nfrom core.agents.respons"
  },
  {
    "path": "core/agents/task_completer.py",
    "chars": 2224,
    "preview": "from core.agents.base import BaseAgent\nfrom core.agents.git import GitMixin\nfrom core.agents.response import AgentRespon"
  },
  {
    "path": "core/agents/tech_lead.py",
    "chars": 18852,
    "preview": "import asyncio\nfrom uuid import uuid4\n\nfrom pydantic import BaseModel, Field\n\nfrom core.agents.base import BaseAgent\nfro"
  },
  {
    "path": "core/agents/tech_writer.py",
    "chars": 2435,
    "preview": "from core.agents.base import BaseAgent\nfrom core.agents.convo import AgentConvo\nfrom core.agents.response import AgentRe"
  },
  {
    "path": "core/agents/troubleshooter.py",
    "chars": 18095,
    "preview": "import json\nfrom typing import Optional\nfrom uuid import uuid4\n\nfrom pydantic import BaseModel, Field\n\nfrom core.agents."
  },
  {
    "path": "core/agents/wizard.py",
    "chars": 7274,
    "preview": "import json\nfrom urllib.parse import urljoin\nfrom uuid import uuid4\n\nimport httpx\nfrom sqlalchemy import inspect\n\nfrom c"
  },
  {
    "path": "core/cli/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/cli/helpers.py",
    "chars": 36271,
    "preview": "import json\nimport os\nimport os.path\nimport sys\nfrom argparse import ArgumentParser, ArgumentTypeError, Namespace\nfrom d"
  },
  {
    "path": "core/cli/main.py",
    "chars": 14526,
    "preview": "import asyncio\nimport atexit\nimport gc\nimport signal\nimport sys\nimport traceback\nfrom argparse import Namespace\nfrom asy"
  },
  {
    "path": "core/config/__init__.py",
    "chars": 16296,
    "preview": "from enum import Enum\nfrom os.path import abspath, dirname, isdir, join\nfrom typing import Any, Literal, Optional, Union"
  },
  {
    "path": "core/config/actions.py",
    "chars": 2362,
    "preview": "BH_START_BUG_HUNT = \"Start bug hunt for task #{}\"\nBH_WAIT_BUG_REP_INSTRUCTIONS = \"Awaiting bug reproduction instructions"
  },
  {
    "path": "core/config/constants.py",
    "chars": 51,
    "preview": "CONVO_ITERATIONS_LIMIT = 8\nLOGS_LINE_LIMIT = 20000\n"
  },
  {
    "path": "core/config/env_importer.py",
    "chars": 3033,
    "preview": "from os.path import dirname, exists, join\n\nfrom dotenv import dotenv_values\n\nfrom core.config import Config, LLMProvider"
  },
  {
    "path": "core/config/magic_words.py",
    "chars": 499,
    "preview": "PROBLEM_IDENTIFIED = \"PROBLEM_IDENTIFIED\"\nADD_LOGS = \"ADD_LOGS\"\nALWAYS_RELEVANT_FILES = [\n    \"client/src/App.tsx\",\n]\nGI"
  },
  {
    "path": "core/config/user_settings.py",
    "chars": 2906,
    "preview": "import sys\nfrom os import getenv, makedirs\nfrom pathlib import Path\nfrom uuid import uuid4\n\nfrom pydantic import BaseMod"
  },
  {
    "path": "core/config/version.py",
    "chars": 2771,
    "preview": "import re\nfrom os.path import abspath, basename, dirname, isdir, isfile, join\nfrom typing import Optional\n\nGIT_DIR_PATH "
  },
  {
    "path": "core/db/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/db/alembic.ini",
    "chars": 3576,
    "preview": "# A generic, single database configuration.\n\n[alembic]\n# path to migration scripts\nscript_location = core/db/migrations\n"
  },
  {
    "path": "core/db/fix_migrations.py",
    "chars": 2851,
    "preview": "#!/usr/bin/env python\nimport os\nimport sqlite3\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom alembic import"
  },
  {
    "path": "core/db/migrations/README",
    "chars": 344,
    "preview": "Pythagora uses Alembic for database migrations.\n\nAfter changing any of the database models, create a new migration:\n\n   "
  },
  {
    "path": "core/db/migrations/env.py",
    "chars": 2364,
    "preview": "from logging.config import fileConfig\n\nfrom alembic import context\nfrom sqlalchemy import engine_from_config, pool\n\nfrom"
  },
  {
    "path": "core/db/migrations/script.py.mako",
    "chars": 635,
    "preview": "\"\"\"${message}\n\nRevision ID: ${up_revision}\nRevises: ${down_revision | comma,n}\nCreate Date: ${create_date}\n\n\"\"\"\nfrom typ"
  },
  {
    "path": "core/db/migrations/versions/0173e14719aa_move_metadata_from_file_to_file_content_.py",
    "chars": 1634,
    "preview": "\"\"\"move metadata from file to file content table\n\nRevision ID: 0173e14719aa\nRevises: 3968d770dced\nCreate Date: 2025-05-1"
  },
  {
    "path": "core/db/migrations/versions/0173e14719aa_vacuum_database.py",
    "chars": 727,
    "preview": "\"\"\"vacuum database\n\nRevision ID: 0173e14719ab\nRevises: 69e50fdaf067\nCreate Date: 2025-05-15 15:33:03.084670\n\n\"\"\"\n\nfrom t"
  },
  {
    "path": "core/db/migrations/versions/08d71952ec2f_refactor_specification_template_to_.py",
    "chars": 1112,
    "preview": "\"\"\"refactor specification.template to specification.templates\n\nRevision ID: 08d71952ec2f\nRevises: ff891d366761\nCreate Da"
  },
  {
    "path": "core/db/migrations/versions/0a1bb637fa26_initial.py",
    "chars": 9327,
    "preview": "\"\"\"initial\n\nRevision ID: 0a1bb637fa26\nRevises:\nCreate Date: 2024-05-28 09:49:51.582784\n\n\"\"\"\n\nfrom typing import Sequence"
  },
  {
    "path": "core/db/migrations/versions/3968d770dced_add_project_type_to_project.py",
    "chars": 980,
    "preview": "\"\"\"Add project type to project\n\nRevision ID: 3968d770dced\nRevises: f708791b9270\nCreate Date: 2025-02-15 10:30:13.163098\n"
  },
  {
    "path": "core/db/migrations/versions/675268601278_add_chat_messages_and_convos.py",
    "chars": 1652,
    "preview": "\"\"\"Add chat messages and convos\n\nRevision ID: 675268601278\nRevises: 0173e14719ab\nCreate Date: 2025-05-14 10:38:19.130649"
  },
  {
    "path": "core/db/migrations/versions/69e50fdaf067_move_knowledge_base_to_separate_table.py",
    "chars": 5720,
    "preview": "\"\"\"move knowledge base to separate table\n\nRevision ID: 69e50fdaf067\nRevises: 0173e14719aa\nCreate Date: 2025-05-15 17:27:"
  },
  {
    "path": "core/db/migrations/versions/b760f66138c0_add_docs_column_to_project_states.py",
    "chars": 956,
    "preview": "\"\"\"Add docs column to project_states\n\nRevision ID: b760f66138c0\nRevises: f352dbe45751\nCreate Date: 2024-06-08 10:00:44.2"
  },
  {
    "path": "core/db/migrations/versions/c8905d4ce784_add_original_description_and_template_.py",
    "chars": 1163,
    "preview": "\"\"\"Add original description and template summary fields to specifications\n\nRevision ID: c8905d4ce784\nRevises: 08d71952ec"
  },
  {
    "path": "core/db/migrations/versions/f352dbe45751_make_relevant_files_nullable.py",
    "chars": 1041,
    "preview": "\"\"\"Make relevant_files nullable\n\nRevision ID: f352dbe45751\nRevises: 0a1bb637fa26\nCreate Date: 2024-06-04 15:07:40.175466"
  },
  {
    "path": "core/db/migrations/versions/f708791b9270_adding_knowledge_base_field_to_.py",
    "chars": 1008,
    "preview": "\"\"\"Adding knowledge_base field to ProjectState\n\nRevision ID: f708791b9270\nRevises: c8905d4ce784\nCreate Date: 2024-12-22 "
  },
  {
    "path": "core/db/migrations/versions/ff891d366761_add_example_project_to_spec.py",
    "chars": 974,
    "preview": "\"\"\"add example project to spec\n\nRevision ID: ff891d366761\nRevises: b760f66138c0\nCreate Date: 2024-06-13 09:38:33.329161\n"
  },
  {
    "path": "core/db/models/__init__.py",
    "chars": 831,
    "preview": "# Pythagora database models\n#\n# Always import models from this module to ensure the SQLAlchemy registry\n# is correctly p"
  },
  {
    "path": "core/db/models/base.py",
    "chars": 1201,
    "preview": "# DeclarativeBase enables declarative configuration of\n# database models within SQLAlchemy.\n#\n# It also sets up a regist"
  },
  {
    "path": "core/db/models/branch.py",
    "chars": 3339,
    "preview": "from datetime import datetime\nfrom typing import TYPE_CHECKING, Optional, Union\nfrom uuid import UUID, uuid4\n\nfrom sqlal"
  },
  {
    "path": "core/db/models/chat_convo.py",
    "chars": 1863,
    "preview": "from datetime import datetime\nfrom typing import TYPE_CHECKING, Optional\nfrom uuid import UUID, uuid4\n\nfrom sqlalchemy i"
  },
  {
    "path": "core/db/models/chat_message.py",
    "chars": 885,
    "preview": "from datetime import datetime\nfrom typing import Optional\nfrom uuid import UUID, uuid4\n\nfrom sqlalchemy import ForeignKe"
  },
  {
    "path": "core/db/models/exec_log.py",
    "chars": 2673,
    "preview": "from datetime import datetime\nfrom typing import TYPE_CHECKING, Optional\nfrom uuid import UUID\n\nfrom sqlalchemy import F"
  },
  {
    "path": "core/db/models/file.py",
    "chars": 3135,
    "preview": "import re\nfrom typing import TYPE_CHECKING, Optional\nfrom uuid import UUID\n\nfrom sqlalchemy import ForeignKey, UniqueCon"
  },
  {
    "path": "core/db/models/file_content.py",
    "chars": 2072,
    "preview": "from typing import TYPE_CHECKING\n\nfrom sqlalchemy import delete, distinct, select\nfrom sqlalchemy.ext.asyncio import Asy"
  },
  {
    "path": "core/db/models/knowledge_base.py",
    "chars": 2316,
    "preview": "from copy import deepcopy\nfrom typing import TYPE_CHECKING\n\nfrom sqlalchemy import JSON, delete, distinct, select\nfrom s"
  },
  {
    "path": "core/db/models/llm_request.py",
    "chars": 3167,
    "preview": "from datetime import datetime\nfrom typing import TYPE_CHECKING, Optional\nfrom uuid import UUID\n\nfrom sqlalchemy import F"
  },
  {
    "path": "core/db/models/project.py",
    "chars": 6335,
    "preview": "import re\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Optional, Union\nfrom unicodedata import normal"
  },
  {
    "path": "core/db/models/project_state.py",
    "chars": 40984,
    "preview": "from copy import deepcopy\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Optional, Union\nfrom uuid impo"
  },
  {
    "path": "core/db/models/specification.py",
    "chars": 3149,
    "preview": "from copy import deepcopy\nfrom typing import TYPE_CHECKING, Optional\n\nfrom sqlalchemy import delete, distinct, select\nfr"
  },
  {
    "path": "core/db/models/user_input.py",
    "chars": 3084,
    "preview": "from datetime import datetime\nfrom typing import TYPE_CHECKING, Optional\nfrom uuid import UUID\n\nfrom sqlalchemy import F"
  },
  {
    "path": "core/db/session.py",
    "chars": 2694,
    "preview": "from sqlalchemy import event\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\n\nf"
  },
  {
    "path": "core/db/setup.py",
    "chars": 1399,
    "preview": "from os.path import dirname, join\n\nfrom alembic import command\nfrom alembic.config import Config\n\nfrom core.config impor"
  },
  {
    "path": "core/db/v0importer.py",
    "chars": 10476,
    "preview": "from json import loads\nfrom os.path import exists\nfrom pathlib import Path\nfrom uuid import UUID, uuid4\n\nimport aiosqlit"
  },
  {
    "path": "core/disk/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/disk/ignore.py",
    "chars": 4046,
    "preview": "import os.path\nfrom fnmatch import fnmatch\nfrom typing import Optional\n\n\nclass IgnoreMatcher:\n    \"\"\"\n    A class to mat"
  },
  {
    "path": "core/disk/vfs.py",
    "chars": 5861,
    "preview": "import os\nimport os.path\nfrom hashlib import sha1\nfrom pathlib import Path\n\nfrom core.disk.ignore import IgnoreMatcher\nf"
  },
  {
    "path": "core/llm/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/llm/anthropic_client.py",
    "chars": 5649,
    "preview": "import asyncio\nimport datetime\nimport zoneinfo\nfrom typing import Optional, Tuple\n\nfrom anthropic import AsyncAnthropic,"
  },
  {
    "path": "core/llm/azure_client.py",
    "chars": 903,
    "preview": "from httpx import Timeout\nfrom openai import AsyncAzureOpenAI\n\nfrom core.config import LLMProvider\nfrom core.llm.openai_"
  },
  {
    "path": "core/llm/base.py",
    "chars": 20115,
    "preview": "import asyncio\nimport datetime\nimport json\nimport sys\nfrom enum import Enum\nfrom time import time\nfrom typing import Any"
  },
  {
    "path": "core/llm/convo.py",
    "chars": 5376,
    "preview": "from copy import deepcopy\nfrom typing import Any, Iterator, Optional\n\n\nclass Convo:\n    \"\"\"\n    A conversation between a"
  },
  {
    "path": "core/llm/groq_client.py",
    "chars": 3005,
    "preview": "import datetime\nfrom typing import Optional\n\nimport tiktoken\nfrom groq import AsyncGroq, RateLimitError\nfrom httpx impor"
  },
  {
    "path": "core/llm/openai_client.py",
    "chars": 4187,
    "preview": "import datetime\nimport re\nfrom typing import Optional\n\nimport tiktoken\nfrom httpx import Timeout\nfrom openai import Asyn"
  },
  {
    "path": "core/llm/parser.py",
    "chars": 7186,
    "preview": "import json\nimport re\nfrom enum import Enum\nfrom typing import List, Optional, Union\n\nfrom pydantic import BaseModel, Va"
  },
  {
    "path": "core/llm/prompt.py",
    "chars": 1525,
    "preview": "from os.path import isdir\nfrom typing import Any, Optional\n\nfrom jinja2 import BaseLoader, Environment, FileSystemLoader"
  },
  {
    "path": "core/llm/relace_client.py",
    "chars": 2194,
    "preview": "from typing import Optional\n\nimport httpx\nfrom httpx import AsyncClient\n\nfrom core.config import LLMProvider\nfrom core.l"
  },
  {
    "path": "core/llm/request_log.py",
    "chars": 755,
    "preview": "from datetime import datetime\nfrom enum import Enum\nfrom typing import Any\n\nfrom pydantic import BaseModel, Field\n\nfrom "
  },
  {
    "path": "core/log/__init__.py",
    "chars": 3571,
    "preview": "import os\nfrom collections import deque\nfrom logging import FileHandler, Formatter, Logger, StreamHandler, getLogger\n\nfr"
  },
  {
    "path": "core/proc/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/proc/exec_log.py",
    "chars": 1109,
    "preview": "from datetime import datetime, timezone\nfrom typing import Optional\n\nfrom pydantic import BaseModel, Field\n\n\nclass ExecL"
  },
  {
    "path": "core/proc/process_manager.py",
    "chars": 9329,
    "preview": "import asyncio\nimport signal\nimport sys\nimport time\nfrom copy import deepcopy\nfrom dataclasses import dataclass\nfrom os "
  },
  {
    "path": "core/prompts/architect/configure_template.prompt",
    "chars": 357,
    "preview": "You're starting a new software project. The specification provided by the client is:\n\n```\n{{ project_description }}\n```\n"
  },
  {
    "path": "core/prompts/architect/select_templates.prompt",
    "chars": 1771,
    "preview": "You're designing the architecture and technical specifications for a new project.\n\nTo speed up the project development, "
  },
  {
    "path": "core/prompts/architect/system.prompt",
    "chars": 200,
    "preview": "You are a world class software architect.\nYou focus on creating architecture for Minimum Viable Product versions of apps"
  },
  {
    "path": "core/prompts/architect/technologies.prompt",
    "chars": 2990,
    "preview": "You're designing the architecture and technical specifications for a new project.\n\nIf the project requirements call out "
  },
  {
    "path": "core/prompts/bug-hunter/ask_a_question.prompt",
    "chars": 307,
    "preview": "The developer wants to ask you a question. Here is the question:\n{{question}}\n\nPlease answer and refer to all the files "
  },
  {
    "path": "core/prompts/bug-hunter/bug_found_or_add_logs.prompt",
    "chars": 369,
    "preview": "We are working on a solving a technical problem in a codebase and here is a conclusion from a team member:\n--- TEAM_MEMB"
  },
  {
    "path": "core/prompts/bug-hunter/data_about_logs.prompt",
    "chars": 753,
    "preview": "Tell me the most important logs that are relevant for this issue. For each log, tell me the the following:\n 1. line in t"
  },
  {
    "path": "core/prompts/bug-hunter/get_bug_reproduction_instructions.prompt",
    "chars": 912,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you need to write code for the entire application"
  },
  {
    "path": "core/prompts/bug-hunter/instructions_from_human_hint.prompt",
    "chars": 279,
    "preview": "The human is sending you a hint about how to solve this bug. Here is what human said:\n```\n{{ human_hint }}\n```\n\nNow, bas"
  },
  {
    "path": "core/prompts/bug-hunter/iteration.prompt",
    "chars": 2643,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you need to write code for the entire application"
  },
  {
    "path": "core/prompts/bug-hunter/log_data.prompt",
    "chars": 1366,
    "preview": "{% if backend_logs and backend_logs|trim %}\nHere are the logs we added to the backend:\n```\n{{ backend_logs }}\n```\n{% end"
  },
  {
    "path": "core/prompts/bug-hunter/problem_explanation.prompt",
    "chars": 588,
    "preview": "This also didn't help to solve the issue so we can conclude that you are unable to solve this problem yourself so I got "
  },
  {
    "path": "core/prompts/bug-hunter/system.prompt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/prompts/bug-hunter/tell_me_more.prompt",
    "chars": 131,
    "preview": "Please tell me more about the problem we're working on and don't repeat things you said before but tell me something I d"
  },
  {
    "path": "core/prompts/chat-agent/chat.prompt",
    "chars": 1267,
    "preview": "This is the description of the app you are working with:\n{{ initial_description }}\n\n{% if task_description is defined an"
  },
  {
    "path": "core/prompts/chat-agent/system.prompt",
    "chars": 397,
    "preview": "You are a world class full stack software developer working in a team.\n\nYou write modular, well-organized code split acr"
  },
  {
    "path": "core/prompts/code-monkey/breakdown.prompt",
    "chars": 156,
    "preview": "{# This is the same template as for Developer's breakdown because Code Monkey is reusing it in a conversation #}\n{% exte"
  },
  {
    "path": "core/prompts/code-monkey/describe_file.prompt",
    "chars": 1310,
    "preview": "Your task is to explain the functionality implemented by a particular source code file.\n\nGiven a file path and file cont"
  },
  {
    "path": "core/prompts/code-monkey/implement_changes.prompt",
    "chars": 2619,
    "preview": "{% if file_content %}\nYou are working on a project and your job is to implement new code changes based on given instruct"
  },
  {
    "path": "core/prompts/code-monkey/iteration.prompt",
    "chars": 47,
    "preview": "{% extends \"troubleshooter/iteration.prompt\" %}"
  },
  {
    "path": "core/prompts/code-monkey/review_changes.prompt",
    "chars": 3191,
    "preview": "A developer on your team has been working on the task described in previous message. Based on those instructions, the de"
  },
  {
    "path": "core/prompts/code-monkey/review_feedback.prompt",
    "chars": 732,
    "preview": "Your changes have been reviewed.\n{% if content != original_content %}\nThe reviewer approved and applied some of your cha"
  },
  {
    "path": "core/prompts/code-monkey/system.prompt",
    "chars": 175,
    "preview": "You are a world class full stack software developer.\nYou write modular, clean, maintainable, production-ready code.\nYour"
  },
  {
    "path": "core/prompts/developer/breakdown.prompt",
    "chars": 5461,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you are a primary developer who needs to write an"
  },
  {
    "path": "core/prompts/developer/filter_files.prompt",
    "chars": 907,
    "preview": "{% if state.current_task %}\nThe next task we need to work on, and have to focus on, is this task:\n```\n{{ state.current_t"
  },
  {
    "path": "core/prompts/developer/iteration.prompt",
    "chars": 47,
    "preview": "{% extends \"troubleshooter/iteration.prompt\" %}"
  },
  {
    "path": "core/prompts/developer/parse_task.prompt",
    "chars": 2725,
    "preview": "For the implementation instructions defined below, create a list of actionable steps that will be executed by a machine."
  },
  {
    "path": "core/prompts/developer/system.prompt",
    "chars": 397,
    "preview": "You are a world class full stack software developer working in a team.\n\nYou write modular, well-organized code split acr"
  },
  {
    "path": "core/prompts/error-handler/debug.prompt",
    "chars": 2719,
    "preview": "A coding task has been implemented for the new project we're working on.\n\n{% include \"partials/project_details.prompt\" %"
  },
  {
    "path": "core/prompts/executor/ran_command.prompt",
    "chars": 2187,
    "preview": "A coding task has been implemented for the new project, \"{{ state.branch.project.name }}\", we're working on.\n\nYour job i"
  },
  {
    "path": "core/prompts/external-docs/create_docs_queries.prompt",
    "chars": 1197,
    "preview": "{% include \"partials/project_details.prompt\" %}\n\nHere is the next task that needs to be implemented:\n```\n{{ current_task"
  },
  {
    "path": "core/prompts/external-docs/select_docset.prompt",
    "chars": 1035,
    "preview": "{% include \"partials/project_details.prompt\" %}\n\nHere is the next task that needs to be implemented:\n{{ current_task.des"
  },
  {
    "path": "core/prompts/external-docs/system.prompt",
    "chars": 166,
    "preview": "You are a world class full stack software developer working in a team.\n\nYour job is to select the documentation that mig"
  },
  {
    "path": "core/prompts/frontend/build_frontend.prompt",
    "chars": 4983,
    "preview": "{% if user_feedback %}You're currently working on a frontend of an app that has the following description:\n{% else %}Cre"
  },
  {
    "path": "core/prompts/frontend/create_rag_query.prompt",
    "chars": 363,
    "preview": "{{ file_content }}\n\nI have external documentation in Swagger Open API format.\nCreate a comma separated list of short wor"
  },
  {
    "path": "core/prompts/frontend/is_relevant_for_docs_search.prompt",
    "chars": 235,
    "preview": "{{ user_feedback }}\n\nDoes this prompt require taking a look at the API documentation? For example, you would look at API"
  },
  {
    "path": "core/prompts/frontend/iterate_frontend.prompt",
    "chars": 4766,
    "preview": "{% if user_feedback %}You're currently working on a frontend of an app that has the following description:\n{% else %}Cre"
  },
  {
    "path": "core/prompts/frontend/remove_mock.prompt",
    "chars": 2664,
    "preview": "Now you need to remove mocked data from the file and replace it with real API requests.\nReplace only mocked data, do not"
  },
  {
    "path": "core/prompts/frontend/system.prompt",
    "chars": 4744,
    "preview": "You are a world class frontend software developer.You have vast knowledge across multiple programming languages, framewo"
  },
  {
    "path": "core/prompts/frontend/system_relace.prompt",
    "chars": 5512,
    "preview": "You are a world class frontend software developer.You have vast knowledge across multiple programming languages, framewo"
  },
  {
    "path": "core/prompts/importer/analyze_project.prompt",
    "chars": 1191,
    "preview": "You're given an existing project you need to analyze and continue developing. To do this, you'll need to determine the p"
  },
  {
    "path": "core/prompts/importer/get_entrypoints.prompt",
    "chars": 954,
    "preview": "You're given an existing project you need to analyze and continue developing. To do this, you'll need to determine the p"
  },
  {
    "path": "core/prompts/partials/breakdown_code_instructions.prompt",
    "chars": 513,
    "preview": "Make sure that the user doesn't have to test anything with commands but that all features are reflected in the frontend "
  },
  {
    "path": "core/prompts/partials/coding_rules.prompt",
    "chars": 5642,
    "preview": "# RULES FOR IMPLEMENTING CODE CHANGES\n~~~START_OF_CODING_RULES~~~\n\n## Rule 1: Scope of your coding task\nYou must impleme"
  },
  {
    "path": "core/prompts/partials/doc_snippets.prompt",
    "chars": 372,
    "preview": "{% if docs is defined and docs %}\nWe have some some documentation snippets that might be helpful while working on this t"
  },
  {
    "path": "core/prompts/partials/execution_order.prompt",
    "chars": 294,
    "preview": "All the steps will be executed in order in which you give them, so it is very important that you think about all steps b"
  },
  {
    "path": "core/prompts/partials/features_list.prompt",
    "chars": 603,
    "preview": "{% if state.epics|length > 3 and state.current_task and state.current_task.quick_implementation is not defined %}\n\nHere "
  },
  {
    "path": "core/prompts/partials/file_naming.prompt",
    "chars": 1293,
    "preview": "**IMPORTANT**: When creating and naming new files, ensure the file naming (camelCase, kebab-case, underscore_case, etc) "
  },
  {
    "path": "core/prompts/partials/file_size_limit.prompt",
    "chars": 186,
    "preview": "**IMPORTANT**\nWhen you think about in which file should the new code go to, always try to make files as small as possibl"
  },
  {
    "path": "core/prompts/partials/files_descriptions.prompt",
    "chars": 1611,
    "preview": "{% if dir_type is defined %}\n{% if dir_type == \"client\" %}\nNow you need to focus only on the frontend files. These files"
  },
  {
    "path": "core/prompts/partials/files_list.prompt",
    "chars": 1073,
    "preview": "{% if state.relevant_files %}\n~~FILE_DESCRIPTIONS_IN_THE_CODEBASE~~\n{% include \"partials/files_descriptions.prompt\" %}\n~"
  },
  {
    "path": "core/prompts/partials/files_list_relevant.prompt",
    "chars": 1020,
    "preview": "Here are the complete contents of files relevant to this task:\n{% if state.has_frontend() %}\n---START_OF_FRONTEND_API_FI"
  },
  {
    "path": "core/prompts/partials/human_intervention_explanation.prompt",
    "chars": 2784,
    "preview": "**IMPORTANT**\nYou must not tell me to run a command in the database or anything OS related - only if some dependencies n"
  },
  {
    "path": "core/prompts/partials/project_details.prompt",
    "chars": 342,
    "preview": "~~APP_DESCRIPTION~~\nHere is a high level description of \"{{ state.branch.project.name }}\":\n```\n{{ state.specification.de"
  },
  {
    "path": "core/prompts/partials/project_tasks.prompt",
    "chars": 3974,
    "preview": "{# This is actually creation of tasks and not epics. Reason why this prompt uses word \"epic\" instead of \"task\" is that L"
  },
  {
    "path": "core/prompts/partials/relative_paths.prompt",
    "chars": 286,
    "preview": "IMPORTANT: Pay attention to file paths: if the command or argument is a file or folder from the project, use paths relat"
  },
  {
    "path": "core/prompts/partials/user_feedback.prompt",
    "chars": 380,
    "preview": "{% if user_feedback %}\nUser who was using the app \"{{ state.branch.project.name }}\" sent you this feedback:\n```\n{{ user_"
  },
  {
    "path": "core/prompts/problem-solver/get_alternative_solutions.prompt",
    "chars": 3004,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you need to write code for the entire {% if state"
  },
  {
    "path": "core/prompts/problem-solver/iteration.prompt",
    "chars": 47,
    "preview": "{% extends \"troubleshooter/iteration.prompt\" %}"
  },
  {
    "path": "core/prompts/problem-solver/system.prompt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/prompts/pythagora/commit.prompt",
    "chars": 464,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you need to generate commit message for next \"git"
  },
  {
    "path": "core/prompts/spec-writer/add_new_feature.prompt",
    "chars": 984,
    "preview": "Your team has taken the client brief and turned it into a project specification.\nAfterwards the client added a descripti"
  },
  {
    "path": "core/prompts/spec-writer/add_to_specification.prompt",
    "chars": 217,
    "preview": "The human who described the app, told you this:\n```\n{{ user_message }}\n```\n\nRewrite the entire spec and incorporate user"
  },
  {
    "path": "core/prompts/spec-writer/ask_questions.prompt",
    "chars": 9037,
    "preview": "Your task is to talk to a new client and develop a detailed specification for a new application the client wants to buil"
  },
  {
    "path": "core/prompts/spec-writer/build_full_specification.prompt",
    "chars": 1066,
    "preview": "You need to build full specification for an app that a human described like this:\n```\n{{ initial_prompt }}\n```\n\nHere are"
  },
  {
    "path": "core/prompts/spec-writer/need_auth.prompt",
    "chars": 322,
    "preview": "Decide if the user wants to use authentication (login and register) for the app with the following description:\n```text\n"
  },
  {
    "path": "core/prompts/spec-writer/project_name.prompt",
    "chars": 271,
    "preview": "Generate a simple project name from the following description:\n```text\n{{description}}\n```\nUse a maximum of 2-3 words, n"
  },
  {
    "path": "core/prompts/spec-writer/prompt_complexity.prompt",
    "chars": 654,
    "preview": "{% if is_feature %}\nHere is the app description that is fully built already:\n```\n{{ state.specification.description }}\n`"
  },
  {
    "path": "core/prompts/spec-writer/review_spec.prompt",
    "chars": 1003,
    "preview": "Your team has taken the client brief and turned it into a project specification.\n\nYour job is to check the specification"
  },
  {
    "path": "core/prompts/spec-writer/system.prompt",
    "chars": 200,
    "preview": "You are a world class software architect.\nYou focus on creating architecture for Minimum Viable Product versions of apps"
  },
  {
    "path": "core/prompts/tech-lead/epic_breakdown.prompt",
    "chars": 2259,
    "preview": "Ok, great. Now, you need to take the epic #{{ epic_number }} (\"{{ epic_description }}\") and break it down into smaller t"
  },
  {
    "path": "core/prompts/tech-lead/filter_files.prompt",
    "chars": 109,
    "preview": "{# This is the same template as for Developer's filter files #}\n{% extends \"developer/filter_files.prompt\" %}"
  },
  {
    "path": "core/prompts/tech-lead/plan.prompt",
    "chars": 2865,
    "preview": "You are working in a software development agency and a project manager and software architect approach you telling you t"
  },
  {
    "path": "core/prompts/tech-lead/system.prompt",
    "chars": 195,
    "preview": "You are an experienced tech lead in a software development agency.\nYour main job is to break down the project into epics"
  },
  {
    "path": "core/prompts/tech-writer/create_readme.prompt",
    "chars": 1413,
    "preview": "You are working on a project called \"{{ state.branch.project.name }}\" and you need to create a detailed documentation fo"
  },
  {
    "path": "core/prompts/tech-writer/system.prompt",
    "chars": 636,
    "preview": "You are technical writer and as such, you excel in clear, concise communication, skillfully breaking down complex techni"
  },
  {
    "path": "core/prompts/troubleshooter/breakdown.prompt",
    "chars": 159,
    "preview": "{# This is the same template as for Developer's breakdown because Troubleshooter is reusing it in a conversation #}\n{% e"
  },
  {
    "path": "core/prompts/troubleshooter/bug_report.prompt",
    "chars": 1628,
    "preview": "You're working on an new app and the user has just been testing it.\n\n{% include \"partials/project_details.prompt\" %}\n{% "
  },
  {
    "path": "core/prompts/troubleshooter/define_user_review_goal.prompt",
    "chars": 5144,
    "preview": "How can a human user test if this task was completed successfully?\n\nPlease list actions, step by step, in order, that th"
  },
  {
    "path": "core/prompts/troubleshooter/filter_files.prompt",
    "chars": 109,
    "preview": "{# This is the same template as for Developer's filter files #}\n{% extends \"developer/filter_files.prompt\" %}"
  },
  {
    "path": "core/prompts/troubleshooter/get_route_files.prompt",
    "chars": 773,
    "preview": "{# eval-tool test ID: 74918173-59e4-4005-bf19-d28f3bc9f06c\n\nThis was added in June 2024, to improve the accuracy of user"
  },
  {
    "path": "core/prompts/troubleshooter/get_run_command.prompt",
    "chars": 474,
    "preview": "How can I run this app?\n**IMPORTANT**\nDo not reply with anything else but the command with which I can run this app with"
  },
  {
    "path": "core/prompts/troubleshooter/iteration.prompt",
    "chars": 2138,
    "preview": "You are working on an app called \"{{ state.branch.project.name }}\" and you need to write code for the entire application"
  },
  {
    "path": "core/prompts/troubleshooter/system.prompt",
    "chars": 374,
    "preview": "You are the Troubleshooter in a software development team.\n\nYour primary responsibility is to evaluate the application a"
  },
  {
    "path": "core/state/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/state/state_manager.py",
    "chars": 41630,
    "preview": "import asyncio\nimport os.path\nimport re\nimport traceback\nfrom contextlib import asynccontextmanager\nfrom typing import T"
  },
  {
    "path": "core/telemetry/__init__.py",
    "chars": 13438,
    "preview": "import sys\nimport time\nimport traceback\nfrom copy import deepcopy\nfrom os import getenv\nfrom pathlib import Path\nfrom ty"
  },
  {
    "path": "core/templates/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "core/templates/base.py",
    "chars": 4870,
    "preview": "import asyncio\nfrom json import loads\nfrom os.path import dirname, join\nfrom typing import TYPE_CHECKING, Any, Optional,"
  }
]

// ... and 310 more files (download for full content)

About this extraction

This page contains the full source code of the Pythagora-io/gpt-pilot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 510 files (1.4 MB), approximately 359.4k tokens, and a symbol index with 1255 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!