From a9cdb17614d3efdaa9262a7677f3c3c86da1c321 Mon Sep 17 00:00:00 2001 From: dev-agent Date: Wed, 3 Jun 2026 22:42:53 +0300 Subject: [PATCH] feat(plane): analyst comment asks for Approved status + links docs The analyst ready-comment used the obsolete :approved: wording (comment-based approve was removed in PR #12). Rewrite it for the status-only model: ask the stakeholder to move the issue to Approved (reject = reason comment + Rejected), and add clickable Gitea links to the analyst docs that actually exist in the worktree. --- src/stage_engine.py | 60 ++++++++++++++++++++++++++++++++--- tests/test_analyst_comment.py | 47 +++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 tests/test_analyst_comment.py diff --git a/src/stage_engine.py b/src/stage_engine.py index bef17a7..3068039 100644 --- a/src/stage_engine.py +++ b/src/stage_engine.py @@ -257,6 +257,58 @@ def advance_stage( return result +def _build_analyst_ready_comment(repo: str, work_item_id: str, branch: str) -> str: + """BUG C: HTML comment posted when analyst artifacts are ready. + + Status-only model (PR #12): approval is the **Approved** status, NOT a + ``:approved:`` comment and NOT moving back to In Progress. The comment asks + the stakeholder to flip the status and links the documents the analyst + actually produced. + + Links point at the Gitea web view: + {gitea_url}/{owner}/{repo}/src/branch/{branch}/docs/work-items/{wid}/ + Only files that REALLY exist in the worktree are listed (no invented docs). + """ + text = ( + "\u2705 BRD/\u0422\u0417/AC \u0433\u043e\u0442\u043e\u0432\u044b. " + "\u0414\u043b\u044f \u043f\u0440\u043e\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f " + "\u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0438\u0442\u0435 \u0437\u0430\u0434\u0430\u0447\u0443 " + "\u0432 \u0441\u0442\u0430\u0442\u0443\u0441 Approved. " + "\u0414\u043b\u044f \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f \u2014 " + "\u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 \u043f\u0440\u0438\u0447\u0438\u043d\u0443 " + "\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u043e\u043c \u0438 \u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0438\u0442\u0435 " + "\u0432 Rejected." + ) + + # Candidate analyst artifacts (label -> filename). Only existing ones linked. + candidates = [ + ("Business request", "00-business-request.md"), + ("BRD", "01-brd.md"), + ("\u0422\u0417 (TRZ)", "02-trz.md"), + ("Acceptance Criteria", "03-acceptance-criteria.md"), + ("Test Plan", "04-test-plan.yaml"), + ("UI Test Cases", "04b-ui-test-cases.md"), + ] + rel_dir = f"docs/work-items/{work_item_id}" + try: + wt_dir = os.path.join(get_worktree_path(repo, branch), rel_dir) + except Exception: + wt_dir = None + + owner = getattr(settings, "gitea_owner", "admin") + base = settings.gitea_url.rstrip("/") + links = [] + for label, fname in candidates: + if wt_dir and not os.path.isfile(os.path.join(wt_dir, fname)): + continue + href = f"{base}/{owner}/{repo}/src/branch/{branch}/{rel_dir}/{fname}" + links.append(f'
  • {label}
  • ') + + if links: + text += "
    \u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b:" + return text + + def _handle_analysis_approved_flow( task_id, current_stage, repo, work_item_id, branch, agent, result: AdvanceResult ): @@ -279,19 +331,17 @@ def _handle_analysis_approved_flow( files_ok, _ = files_check(repo, work_item_id, branch) if files_ok: - # Full artifacts ready -> In Review, ask for :approved:. + # Full artifacts ready -> In Review, ask for the Approved STATUS (BUG C). set_issue_in_review(work_item_id) plane_add_comment( work_item_id, - "\U0001f4cb BRD/\u0422\u0417/AC/TestPlan \u0433\u043e\u0442\u043e\u0432\u044b. " - "\u041f\u0440\u043e\u0448\u0443 review \u0438 \u0440\u0435\u0430\u043a\u0446\u0438\u044e :approved: " - "\u0434\u043b\u044f \u043f\u0440\u043e\u0434\u0432\u0438\u0436\u0435\u043d\u0438\u044f \u0432 Architecture.", + _build_analyst_ready_comment(repo, work_item_id, branch), author="analyst", ) notify_approve_requested(task_id) result.note = "analysis-in-review" logger.info( - f"Task {task_id}: analyst finished, requested :approved: in Plane" + f"Task {task_id}: analyst finished, requested Approved status in Plane" ) return diff --git a/tests/test_analyst_comment.py b/tests/test_analyst_comment.py new file mode 100644 index 0000000..a4e5414 --- /dev/null +++ b/tests/test_analyst_comment.py @@ -0,0 +1,47 @@ +"""BUG C: analyst "artifacts ready" comment under the status-only model. + +The comment must ask for the **Approved** status (not the obsolete +":approved:" reaction, not moving back to "In Progress") and link only the +docs that actually exist in the worktree. +""" + +import os +import tempfile + +os.environ.setdefault("ORCH_GITEA_TOKEN", "test-token") +os.environ.setdefault("ORCH_PLANE_API_TOKEN", "test-token") + + +def test_analyst_comment_asks_approved_with_links(monkeypatch, tmp_path): + from src import stage_engine as SE + + # Worktree with only SOME of the candidate docs present. + wt = tmp_path / "wt" + docs = wt / "docs" / "work-items" / "ET-011" + docs.mkdir(parents=True) + for fname in ("00-business-request.md", "01-brd.md", "02-trz.md", + "03-acceptance-criteria.md", "04-test-plan.yaml"): + (docs / fname).write_text("x") + # 04b-ui-test-cases.md intentionally absent -> must NOT be linked + + monkeypatch.setattr(SE, "get_worktree_path", lambda repo, branch: str(wt)) + monkeypatch.setattr(SE.settings, "gitea_url", "https://git.mva154.duckdns.org") + monkeypatch.setattr(SE.settings, "gitea_owner", "admin") + + html = SE._build_analyst_ready_comment( + "enduro-trails", "ET-011", "feature/ET-011-gpx-upload-feature" + ) + + # text asks for the Approved STATUS, not the obsolete mechanisms + assert "Approved" in html + assert ":approved:" not in html + assert "In Progress" not in html + assert "Rejected" in html + # clickable links to docs that ACTUALLY exist + assert "