ADR-254: Wire ReviewThread model through all pipeline steps - #7
Conversation
- ReviewStep: remove PR-creation block; parse reviewer JSON into ReviewThread objects with UUID assignment; save ctx.review_threads / ctx.review_notes; emit [DEV-TEAM] Review ready: <status> - FixPrStep: add context_path ctor; pass $REVIEW_THREADS (unresolved threads JSON); thread-merge preserving Reviewer's resolved value; ctx.save() after merge - SignoffStep: add context_path ctor; _run_reviewer_signoff returns 4-tuple (ok, error, thread_dicts, body); $BASE_BRANCH/$REVIEW_THREADS substitutions; sys.exit(1) guard in main thread; thread-merge appending comments / updating resolved / adding new threads with UUIDs; ctx.signoff_notes; approval driven by thread resolution; explicit save - ValidateStep: add context_path ctor; emit [DEV-TEAM] First push complete (save-before-marker pattern, skipped on resume) - ImplementStep: emit [DEV-TEAM] Implementation started / complete - DebugStep: emit [DEV-TEAM] Debug complete - FixStep: add $REVIEW_THREADS: "" substitution - PipelineContext: add signoff_notes field with save/load via <!-- section:Signoff Notes --> (mirrors review_notes pattern) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jodavis-claude
left a comment
There was a problem hiding this comment.
ADR-254 Review
One Priority 1 (correctness) issue found: deserializes reviewer-agent output with direct dict key access (, ) that will raise an unhandled if the agent returns malformed threads. All other exit criteria are met.
Exit criteria checklist:
- ✅ : PR-creation block removed; UUIDs assigned; / saved; emits
- ✅ : constructor arg; + ; thread-merge discards ;
- ✅ : / ; 4-tuple return; sign-off thread merge; ; ; in main thread on failure
- ✅ : False→True; before marker; marker not emitted on resume
- ✅ : at top; before both return paths
- ✅ : emitted; not emitted on early-return path
- ✅ : in substitutions dict
- ✅ : field with
<!-- section:Signoff Notes -->sentinel; unconditional Review Notes / Review Threads sections - ✅ New tests: , , ,
Known gap (not blocking): still contains the hardcoded PR URL literal that will not be substituted until ADR-255 — developer chose option (b) from Known Ambiguity #2, acceptable per spec.
jodavis-claude
left a comment
There was a problem hiding this comment.
ADR-254 Review
One Priority 1 issue: ReviewStep.run() uses t["filePath"] and t["lineNumber"] with direct key access -- unhandled KeyError if the reviewer agent returns malformed threads. All other exit criteria are met.
Exit criteria: ReviewStep (PR-creation removed, UUIDs, threads/notes saved, marker) [x], FixPrStep (context_path, REVIEW_THREADS, merge discards resolved, save) [x], SignoffStep (BASE_BRANCH/REVIEW_THREADS, 4-tuple, merge, signoff_notes, sys.exit in main thread) [x], ValidateStep (first_push_done, save-before-marker, no double emit) [x], ImplementStep (started/complete both paths) [x], DebugStep (Debug complete, not early) [x], FixStep (REVIEW_THREADS="") [x], PipelineContext (signoff_notes + section) [x], Tests (TestFixPrStepThreadMerge, TestSignoffStepThreadMerge, TestSignoffStepExitGuard, TestValidateStepFirstPush) [x]
Known gap (not blocking): reviewer-review.md hardcoded PR URL not substituted until ADR-255 -- option (b) from Known Ambiguity #2.
jodavis-claude
left a comment
There was a problem hiding this comment.
Sign-off review for ADR-254.
Prior threads (2): Both P1 correctness issues about direct key access (t["filePath"], t["lineNumber"]) in ReviewStep were addressed in commit 3d24db4. The fix correctly uses t.get("filePath", "") and t.get("lineNumber", 0), consistent with SignoffStep's new-thread path and PipelineContext.load(). Both threads resolved.
New issues in modified files: None. Commit 3d24db4 is a targeted, minimal fix with no regressions. All 52 tests pass.
Outcome: Approved — all review threads resolved, no new blocking issues found.
Work item
ADR-254: Update all pipeline step classes in
dev_team.pyto replace the PR-URL-based review handoff with theReviewThreaddata model — wiring UUID assignment, thread merging, and structured markers through every affected step.Changes
plugins/dev-team/scripts/dev_team.py— Pipeline step classes updated to useReviewThreadmodel throughout:PipelineContext: addedsignoff_notes: str = ""field with<!-- section:Signoff Notes -->save/loadReviewStep: removedprepare-pr-detailsblock; UUID assignment for parsed thread dicts; savesctx.review_threadsandctx.review_notes; emits[DEV-TEAM] Review ready: <status>FixPrStep: added__init__(self, context_path)constructor; passes unresolved threads as$REVIEW_THREADSand$ISSUES: ""; merges Developer's returned thread list discardingresolvedfield; callsctx.save()SignoffStep: added__init__(self, context_path)constructor; uses$BASE_BRANCH/$REVIEW_THREADSsubstitutions;_run_reviewer_signoff()returns 4-tuple; merges sign-off threads (append-not-replace); storesbodyinctx.signoff_notes; emits[DEV-TEAM] Signoff: <status>; callssys.exit(1)in main thread on failureValidateStep: added__init__(self, context_path)constructor; setsctx.first_push_done = True, callsctx.save(), emits[DEV-TEAM] First push complete— only whenfirst_push_donewasFalseImplementStep: emits[DEV-TEAM] Implementation startedat top ofrun()and[DEV-TEAM] Implementation completebefore every return (both on resume)DebugStep: emits[DEV-TEAM] Debug completeafter debugger agent returnsFixStep: added"$REVIEW_THREADS": ""to substitutions dictfrom uuid import uuid4plugins/dev-team/scripts/test_dev_team.py— 17 new unit tests across 4 new test classes:TestPipelineContextSignoffNotes(5 tests): default empty, save unconditional, round-trip empty/with-content, load missing keyTestFixPrStepThreadMerge(3 tests): comments replaced on match,resolvedpreserved from existing thread, unknown IDs silently discardedTestSignoffStepThreadMerge(4 tests): comments appended not replaced,resolvedupdated, empty comments only updatesresolved, new threads added with UUIDsTestSignoffStepExitGuard(2 tests): exits when reviewer agent raises, exits when reviewer returns empty threadsTestValidateStepFirstPush(3 tests): setsfirst_push_doneto True, marker emitted when False, marker not emitted when already TrueAll 52 tests pass (35 pre-existing + 17 new).
Design decisions
ReviewStep$PR_URLgap: Removed"$PR_URL": ctx.pr_urlfrom substitutions entirely (not bridged with""). Thereviewer-review.mdskill still contains$PR_URLuntil ADR-255 lands; unit tests don't invoke the skill so no test failures result.signoff_notesscope: This field is required bySignoffStepbut was absent from ADR-253's exit criteria. Added it as part of ADR-254 to unblock compilation, mirroring thereview_notespattern exactly.FixPrStepreplaces.commentswholesale (Developer echoes full history);SignoffStepappends new comments only (Reviewer returns delta). This matches the spec's ownership invariant — only Reviewer setsresolved: true.sys.exit(1)placement inSignoffStep: Called in the main thread (not in the worker future) per spec requirement. Guard triggers onreviewer_ok=FalseOR empty thread list, before thefailureslist check.SignoffStepinvocation:ctx.save()called once insideSignoffStepon both approval and changes_requested paths; the outerDevTeamPipeline.run()save is idempotent.