feat(gateway): carry intake technical depth down the chain + widen review coherence scope (#491)

Two structural issues flagged by the CEO:

1. Task technical-depth dilution — intake's rich analysis (file:line
   targets, code examples, rationale) was getting lost as it traveled
   umbrella -> root-subtask -> cell -> dev. The detail IS preserved in
   Task.description; the dilution was in delegation (PMs re-authoring)
   and the intake prompt not demanding depth.

   Fixes:
   - evidence_repo: ancestor_context_for_task walks the parent chain
     (cycle-guarded, depth-capped 16, desc-clipped 1500) and surfaces it
     as parent_context in the evidence payload, so a leaf dev finally
     sees the upstream intake analysis instead of a bare title.
   - evidence_builder: Task.description now rides in the payload;
     EvidencePayload gains description + parent_context (omit-when-empty
     so no null noise).
   - orchestrator: _description_body (capped 4000) injects the
     description into the dev spawn prompt + SessionStart briefing.
   - role prompts (main_pm/cell_pm/developer/prompter): teach pass-the-
     torch, don't-dim-it; prompter now demands file:line/code-examples
     in the_work/notes (reconciled with the no-code-level-ACs-on-roots
     rule). main_pm's brief-not-a-spec scoped: not-a-spec applies to the
     solution only, facts forward verbatim.

2. PR-review/QA scope too narrow — they only checked the AC checklist,
   not whether the change is coherent with project structure/intent.

   Fixes:
   - qa.md + pr_reviewer.md: Coherence & intent rule (intent via
     description+parent_context, coherence with project patterns,
     standards). Criterion-less major findings allowed for intent drift
     (Finding.criterion is optional).
   - parent_context + description wired into the gate/QA/inbound-PR
     evidence builders (fail-open, logged).

Skipped per YAGNI: a technical_spec JSONB column (detail is already in
description) and a criterion_kind enum (criterion is already optional).

All gates green: ruff, mypy (1152), pytest (12883 passed, 94.82% cov),
xenon, vulture, bandit, pip-audit, deptry, alembic, import-linter,
foundation-check.

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-13 08:06:57 +02:00
committed by GitHub
co-authored by Renn F
parent 192524265c
commit ba7135ba50
19 changed files with 536 additions and 34 deletions
@@ -444,3 +444,68 @@ class TestExtractPmReview:
digest = build_task_handoff(t, [])
assert digest is not None
assert "pm_review" not in digest
class TestDescriptionAndParentContext:
"""The ask (description + upstream parent_context) now rides the evidence
payload and the task handoff so a freshly-claimed leaf receives the intake's
analysis instead of an empty handoff."""
def test_payload_omits_description_when_none(self) -> None:
"""A descriptionless task must not emit ``description: null`` in every
evidence payload — omitted like the other noise-when-empty fields."""
t = _task()
ev = build_evidence_for_task(t, journal_highlights=[], files_changed=[])
assert "description" not in ev.as_dict()
def test_payload_carries_description_when_set(self) -> None:
t = _task()
t.description = "edit prompter.py:412 to thread task_id through"
ev = build_evidence_for_task(t, journal_highlights=[], files_changed=[])
assert (
ev.as_dict()["description"]
== "edit prompter.py:412 to thread task_id through"
)
def test_payload_carries_parent_context(self) -> None:
t = _task()
chain = [
{
"task_id": "p",
"depth": 1,
"title": "Root",
"description": "intake analysis",
}
]
ev = build_evidence_for_task(
t, journal_highlights=[], files_changed=[], parent_context=chain
)
assert ev.as_dict()["parent_context"] == chain
def test_payload_omits_parent_context_when_empty(self) -> None:
t = _task()
ev = build_evidence_for_task(t, journal_highlights=[], files_changed=[])
assert "parent_context" not in ev.as_dict()
def test_handoff_carries_description_and_parent_context(self) -> None:
"""A leaf with no commits/PR yet still gets a handoff when the spec
(description) or the upstream chain is present — the intake analysis
reaches the dev at claim time."""
t = _task(pr_number=None, pr_url=None, commits=[], dev_notes="")
t.description = "the ask: edit foo.py:10"
chain = [
{"task_id": "p", "depth": 1, "title": "Root", "description": "upstream"}
]
handoff = build_task_handoff(t, [], parent_context=chain)
assert handoff is not None
assert handoff["description"] == "the ask: edit foo.py:10"
assert handoff["parent_context"] == chain
def test_handoff_none_when_no_spec_and_no_prior_work(self) -> None:
"""A bare task with no spec, no commits, no PR, no findings, no upstream
chain still collapses to None — no empty handoff payload."""
t = _task(pr_number=None, pr_url=None, commits=[], dev_notes="")
t.commits = [] # _task's `commits or [...]` default would re-seed one
t.acceptance_criteria_status = []
# MagicMock auto-attrs make description a non-str -> _typed -> None.
assert build_task_handoff(t, []) is None