feat(prompter): board-review → redraft loop for MegaTask batches (#411)

Batch parity with the single-draft keep-alive redraft loop. A first
board-route confirm-batch parks the intake session against the umbrella
(instead of the unconditional reap), so the existing board-completion
injection reaches the still-live chat — now with a batch-aware brief
(compose_batch_redraft_message: live root-subtask snapshots + board
notes + a one-propose_batch re-proposal instruction). The re-confirm
carries BatchConfirmRequest.task_id and routes to the new
PrompterService.update_live_batch: in-place umbrella + root-subtask
update (positional patch of live children, cancel+recreate on scope
change, create/cancel on count change, dependency edges rewired to the
fresh wave plan) gated by the same _validate_batch_scope as create.
Readers use the CANCELLED-excluding get_live_subtasks view so
multi-round redrafts survive earlier cancels.

Cold path: re-interview now handles a branchless umbrella by recovering
its multi-repo scope from live children (distinct_projects_for_batch)
and returning project_ids — fixes the live 400 behind the task-detail
redraft button on umbrellas. Panel: confirmBatch board branch keeps the
chat open, threads batchRedraftTaskIdRef (persisted) into the
re-confirm, treats a redraft re-confirm as terminal on both routes, and
surfaces the server's real validation message on confirm failure.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-10 09:38:10 +02:00
committed by GitHub
co-authored by Renn F
parent 3674a1e002
commit bba20a3917
13 changed files with 1569 additions and 69 deletions
@@ -12,6 +12,7 @@ from roboco.models.base import TaskNature, TaskStatus, TaskType
from roboco.services.base import NotFoundError
from roboco.services.prompter import (
PrompterService,
compose_batch_redraft_message,
compose_redraft_message,
format_board_briefing,
)
@@ -63,6 +64,60 @@ def test_compose_redraft_message_includes_draft_and_brief() -> None:
assert "z" in msg
def test_compose_batch_redraft_message_includes_every_child_and_brief() -> None:
umbrella = SimpleNamespace(
title="MegaTask: Ship the thing",
description="Coordinate 2 sequenced tasks as one MegaTask.",
)
children = [
SimpleNamespace(
title="Backend piece",
description="Backend description.",
acceptance_criteria=["backend ac"],
project=SimpleNamespace(name="roboco-api"),
cell_projects=[],
),
SimpleNamespace(
title="Frontend piece",
description="Frontend description.",
acceptance_criteria=["frontend ac"],
project=None,
cell_projects=[
SimpleNamespace(
team=Team.FRONTEND, project=SimpleNamespace(name="panel-repo")
)
],
),
]
entries = [
{
"author_role": "product_owner",
"author": "po",
"title": "PO",
"content": "board feedback",
}
]
msg = compose_batch_redraft_message(
cast("TaskTable", umbrella), cast("list[TaskTable]", children), entries
)
assert "Ship the thing" in msg
assert "Backend piece" in msg
assert "roboco-api" in msg
assert "backend ac" in msg
assert "Frontend piece" in msg
assert "panel-repo" in msg
assert "Product Owner" in msg
assert "board feedback" in msg
assert "propose_batch" in msg
def test_compose_batch_redraft_message_no_children_is_still_valid() -> None:
umbrella = SimpleNamespace(title="MegaTask: Empty", description="Nothing yet.")
msg = compose_batch_redraft_message(cast("TaskTable", umbrella), [], [])
assert "Empty" in msg
assert "propose_batch" in msg
# --------------------------------------------------------------------------- #
# update_live_draft (DB)
# --------------------------------------------------------------------------- #