Merge pull request #213 from Pythagora-io/bugfix/ENG-989-more-v2-fixes

(ENG-989) More v2 fixes
This commit is contained in:
LeonOstrez
2025-06-18 08:57:52 +01:00
committed by GitHub
7 changed files with 85 additions and 113 deletions

View File

@@ -109,14 +109,12 @@ class Architect(BaseAgent):
self.next_state.specification = spec
telemetry.set("templates", spec.templates)
self.next_state.action = ARCHITECTURE_STEP_NAME
await self.ui.clear_main_logs()
await self.ui.send_front_logs_headers("be_0", ["E2 / T3", "done"], "Setting up backend")
await self.ui.send_back_logs(
[
{
"title": "Setting up backend",
"project_state_id": self.current_state.id,
"labels": ["E2 / T3", "Backend setup", "done"],
"project_state_id": "be_0",
"labels": ["E2 / T2", "Backend setup", "done"],
}
]
)

View File

@@ -1,7 +1,7 @@
import json
from enum import Enum
from typing import Annotated, Literal, Union
from uuid import uuid4
from uuid import UUID, uuid4
from pydantic import BaseModel, Field
@@ -24,7 +24,7 @@ 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
from core.ui.base import ProjectStage, pythagora_source
log = get_logger(__name__)
@@ -340,13 +340,36 @@ class Developer(ChatWithBreakdownMixin, RelevantFilesMixin, BaseAgent):
"task_index": task_index,
}
)
await self.ui.clear_main_logs()
await self.ui.send_front_logs_headers(
f"be_{task_index}_{task_index + 1}",
[f"E{epic_index} / T{task_index}", "Backend", "working"],
description,
self.current_state.current_task.get("id"),
)
# 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:
task_convo = await self.state_manager.get_task_conversation_project_states(UUID(previous_task["id"]))
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].id) if task_convo else "be_0",
"labels": [f"E{epic_index} / T{task_index - 1}", "Backend", "done"],
}
]
)
await self.ui.send_front_logs_headers(
f"be_{epic_index}_{task_index}",
[f"E{epic_index} / T{task_index}", "Backend", "working"],
previous_task["description"],
self.current_state.current_task.get("id"),
)
await self.ui.send_back_logs(
[
{
@@ -373,6 +396,14 @@ class Developer(ChatWithBreakdownMixin, RelevantFilesMixin, BaseAgent):
if self.current_state.current_task.get("hardcoded", False):
return True
await self.ui.clear_main_logs()
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,

View File

@@ -54,28 +54,6 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
await self.set_app_details()
finished = await self.iterate_frontend()
if finished is None:
await self.ui.clear_main_logs()
await self.ui.send_front_logs_headers("fe_0", ["E2 / T1", "done"], "Building frontend")
await self.ui.send_back_logs(
[
{
"title": "Building frontend",
"project_state_id": self.current_state.id,
"labels": ["E2 / T1", "Frontend", "done"],
}
]
)
await self.ui.send_front_logs_headers("be_0", ["E2 / T2", "working"], "Setting up backend")
await self.ui.send_back_logs(
[
{
"title": "Setting up backend",
"project_state_id": self.current_state.id,
"labels": ["E2 / T2", "Backend setup", "working"],
}
]
)
return AgentResponse.exit(self)
return await self.end_frontend_iteration(finished)
@@ -195,13 +173,20 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
)
if answer.button == "yes":
fe_states = await self.state_manager.get_fe_states()
first_fe_state_id = fe_states[0].prev_state_id if fe_states else None
await self.ui.clear_main_logs()
await self.ui.send_front_logs_headers("fe_0", ["E2 / T1", "done"], "Building frontend")
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": self.current_state.id,
"project_state_id": str(first_fe_state_id) if first_fe_state_id else "fe_0",
"labels": ["E2 / T1", "Frontend", "done"],
}
]
@@ -210,7 +195,7 @@ class Frontend(FileDiffMixin, GitMixin, BaseAgent):
[
{
"title": "Setting up backend",
"project_state_id": self.current_state.id,
"project_state_id": "be_0",
"labels": ["E2 / T2", "Backend setup", "working"],
}
]

View File

@@ -209,7 +209,7 @@ class SpecWriter(BaseAgent):
convo = convo.assistant(llm_assisted_description)
await self.ui.clear_main_logs()
await self.ui.send_front_logs_headers("fe_0", ["E2 / T1", "working"], "Building frontend")
await self.ui.send_front_logs_headers(str(self.next_state.id), ["E2 / T1", "working"], "Building frontend")
await self.ui.send_back_logs(
[
{
@@ -224,7 +224,7 @@ class SpecWriter(BaseAgent):
[
{
"title": "Building frontend",
"project_state_id": self.current_state.id,
"project_state_id": str(self.next_state.id),
"labels": ["E2 / T1", "Frontend", "working"],
}
]

View File

@@ -1,5 +1,4 @@
import asyncio
import json
from uuid import uuid4
from pydantic import BaseModel, Field
@@ -342,22 +341,7 @@ class TechLead(RelevantFilesMixin, BaseAgent):
self.next_state.tasks,
)
# await self.ui.send_project_stage({"stage": ProjectStage.OPEN_PLAN})
# response = await self.ask_question(
# TL_EDIT_DEV_PLAN,
# buttons={"done_editing": "I'm done editing, the plan looks good"},
# default="done_editing",
# buttons_only=True,
# extra_info={"edit_plan": True},
# )
#
# self.update_epics_and_tasks(response.text)
if self.next_state.current_task and self.next_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,
)
self.update_epics_and_tasks()
await self.ui.send_epics_and_tasks(
self.next_state.current_epic["sub_epics"],
@@ -384,46 +368,14 @@ class TechLead(RelevantFilesMixin, BaseAgent):
file_content = file_content.replace(line + "\n", "")
await self.state_manager.save_file(file.path, file_content)
def update_epics_and_tasks(self, edited_plan_string):
edited_plan = json.loads(edited_plan_string)
updated_tasks = []
existing_tasks_map = {task["description"]: task for task in self.next_state.tasks}
self.next_state.current_epic["sub_epics"] = []
for sub_epic_number, sub_epic in enumerate(edited_plan, start=1):
self.next_state.current_epic["sub_epics"].append(
{
"id": sub_epic_number,
"description": sub_epic["description"],
}
)
for task in sub_epic["tasks"]:
original_task = existing_tasks_map.get(task["description"])
if original_task and task == original_task:
updated_task = original_task.copy()
updated_task["sub_epic_id"] = sub_epic_number
updated_tasks.append(updated_task)
else:
updated_tasks.append(
{
"id": uuid4().hex,
"description": task["description"],
"instructions": None,
"pre_breakdown_testing_instructions": None,
"status": TaskStatus.TODO,
"sub_epic_id": sub_epic_number,
}
)
def update_epics_and_tasks(self):
if (
self.current_state.current_epic
and self.current_state.current_epic.get("source", "") == "app"
and self.current_state.knowledge_base.user_options.get("auth", False)
):
log.debug("Adding auth task to the beginning of the task list")
updated_tasks.insert(
self.next_state.tasks.insert(
0,
{
"id": uuid4().hex,
@@ -473,6 +425,5 @@ class TechLead(RelevantFilesMixin, BaseAgent):
"iteration_index": 0,
}
]
self.next_state.tasks = updated_tasks
self.next_state.flag_tasks_as_modified()
self.next_state.flag_epics_as_modified()

View File

@@ -761,31 +761,34 @@ class ProjectState(Base):
result = await session.execute(query)
states = result.scalars().all()
log.debug(f"Found {len(states)} states with task start action")
log.debug(f"Found {len(states)} states with custom action")
start = -1
end = -1
# for the FIRST task, it is todo in the same state as Create a development plan, while other tasks are "Task #N start" (action)
# this is done solely to be able to reload to the first task, due to the fact that we need the same project_state_id for the send_back_logs
# for the first task, we need to start from the FIRST state that has that task in TODO status
# for all other tasks, we need to start from LAST state that has that task in TODO status
for i, state in enumerate(states):
if start != -1 and end != -1:
break
if (
start == -1
and state.current_task
and UUID(state.current_task["id"]) == task_id
and state.current_task["status"] in [TaskStatus.TODO, TaskStatus.IN_PROGRESS]
):
start = i
if end == -1:
for task in state.tasks:
if UUID(task["id"]) == task_id and task.get("status") in [
TaskStatus.SKIPPED,
TaskStatus.DOCUMENTED,
TaskStatus.REVIEWED,
TaskStatus.DONE,
]:
end = i
break
for task in state.tasks:
if UUID(task["id"]) == task_id and task.get("status", "") == TaskStatus.TODO:
if UUID(task["id"]) == UUID(state.tasks[0]["id"]):
# First task: set start only once (first occurrence)
if start == -1:
start = i
else:
# Other tasks: update start every time (last occurrence)
start = i
if UUID(task["id"]) == task_id and task.get("status", "") in [
TaskStatus.SKIPPED,
TaskStatus.DOCUMENTED,
TaskStatus.REVIEWED,
TaskStatus.DONE,
]:
end = i
if end == -1:
query = select(ProjectState).where(
@@ -807,14 +810,15 @@ class ProjectState(Base):
# Remove the last state from the list because that state is not yet committed in the database!
results = results[:-1]
# only return sublist of states, first state should have action like "Task #<task_number> start"
index = -1
for i, state in enumerate(results):
if state.action and "Task #" in state.action and "start" in state.action:
index = i
break
return results[index:]
# index = -1
# for i, state in enumerate(results):
# if state.action and "Task #" in state.action and "start" in state.action:
# index = i
# break
#
# return results[index:]
return results
@staticmethod
async def get_fe_states(session: "AsyncSession", branch_id: UUID) -> Optional["ProjectState"]:

View File

@@ -0,0 +1,3 @@
You are the Troubleshooter in a software development team.
Your primary responsibility is to evaluate the application after each task is implemented, identify any bugs or user-requested changes, and propose an appropriate next step. You act as a QA analyst, bug hunter, and bug fixer all in one. You never assume correctness—you verify it through testing and user feedback.