From 03d318fdd77faedffebb3e834b4dbf1b2058bd15 Mon Sep 17 00:00:00 2001 From: ChrisPan <39005916+szupzj18@users.noreply.github.com> Date: Thu, 13 Aug 2026 00:12:11 +0800 Subject: [PATCH] fix: scope plan step auto-completion --- .../plan/override/resolve_workflow.py | 2 +- .../app/commands/resolve/living_plan.py | 2 +- desloppify/engine/_plan/step_completion.py | 13 +++++++++- .../plan/test_plan_overrides_direct.py | 2 +- .../resolve/test_living_plan_direct.py | 20 ++++++++++++---- .../tests/plan/test_step_completion_direct.py | 24 +++++++++++++++++++ 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/desloppify/app/commands/plan/override/resolve_workflow.py b/desloppify/app/commands/plan/override/resolve_workflow.py index 152a2c501..4db9f0cf0 100644 --- a/desloppify/app/commands/plan/override/resolve_workflow.py +++ b/desloppify/app/commands/plan/override/resolve_workflow.py @@ -354,7 +354,7 @@ def _finalize_workflow_resolution( note: str | None, ) -> None: purge_ids(plan, synthetic_ids) - step_messages = auto_complete_steps(plan) + step_messages = auto_complete_steps(plan, resolved_ids=synthetic_ids) for message in step_messages: print(colorize(message, "green")) append_log_entry(plan, "done", issue_ids=synthetic_ids, actor="user", note=note) diff --git a/desloppify/app/commands/resolve/living_plan.py b/desloppify/app/commands/resolve/living_plan.py index 5219d35b1..f9b211e8c 100644 --- a/desloppify/app/commands/resolve/living_plan.py +++ b/desloppify/app/commands/resolve/living_plan.py @@ -115,7 +115,7 @@ def update_living_plan_after_resolve( completed_clusters = _completed_cluster_names(plan, all_resolved) phase_before = current_lifecycle_phase(plan) purged = purge_ids(plan, all_resolved) - step_messages = auto_complete_steps(plan) + step_messages = auto_complete_steps(plan, resolved_ids=all_resolved) for msg in step_messages: print(colorize(msg, "green")) append_log_entry( diff --git a/desloppify/engine/_plan/step_completion.py b/desloppify/engine/_plan/step_completion.py index 10822047a..cfdd8ae48 100644 --- a/desloppify/engine/_plan/step_completion.py +++ b/desloppify/engine/_plan/step_completion.py @@ -3,13 +3,18 @@ from __future__ import annotations -def auto_complete_steps(plan: dict) -> list[str]: +def auto_complete_steps( + plan: dict, resolved_ids: list[str] | None = None +) -> list[str]: """Mark steps done when all their issue_refs are no longer in the queue. + When ``resolved_ids`` is provided, only consider steps linked to this + resolution operation. Returns list of human-readable messages for completed steps. """ messages: list[str] = [] queue_set = set(plan.get("queue_order", [])) + resolved_set = set(resolved_ids or []) for name, cluster in plan.get("clusters", {}).items(): for i, step in enumerate(cluster.get("action_steps") or []): @@ -18,6 +23,12 @@ def auto_complete_steps(plan: dict) -> list[str]: refs = step.get("issue_refs", []) if not refs: continue + if resolved_ids is not None and not any( + resolved_id.endswith(ref) or resolved_id == ref + for resolved_id in resolved_set + for ref in refs + ): + continue # Match by suffix: ref "abc123" matches "review::path::abc123" all_gone = all( not any(qid.endswith(ref) or qid == ref for qid in queue_set) diff --git a/desloppify/tests/commands/plan/test_plan_overrides_direct.py b/desloppify/tests/commands/plan/test_plan_overrides_direct.py index d1b5e4c05..7f65eba2b 100644 --- a/desloppify/tests/commands/plan/test_plan_overrides_direct.py +++ b/desloppify/tests/commands/plan/test_plan_overrides_direct.py @@ -171,7 +171,7 @@ def test_override_resolve_cmd_handles_synthetic_only_resolution( monkeypatch.setattr( resolve_workflow_mod, "auto_complete_steps", - lambda _plan: ["step complete"], + lambda _plan, resolved_ids=None: ["step complete"], ) monkeypatch.setattr( resolve_workflow_mod, diff --git a/desloppify/tests/commands/resolve/test_living_plan_direct.py b/desloppify/tests/commands/resolve/test_living_plan_direct.py index 3a6e3eb90..bf9de8164 100644 --- a/desloppify/tests/commands/resolve/test_living_plan_direct.py +++ b/desloppify/tests/commands/resolve/test_living_plan_direct.py @@ -71,7 +71,9 @@ def test_update_living_plan_after_resolve_fixed_flow(monkeypatch, capsys) -> Non monkeypatch.setattr(living_plan_mod, "load_plan", lambda _p=None: plan) monkeypatch.setattr(living_plan_mod, "purge_ids", lambda _plan, _ids: 1) monkeypatch.setattr( - living_plan_mod, "auto_complete_steps", lambda _plan: ["step complete"] + living_plan_mod, + "auto_complete_steps", + lambda _plan, resolved_ids=None: ["step complete"], ) monkeypatch.setattr( living_plan_mod, "append_log_entry", lambda *_a, **_k: calls.append("log") @@ -123,7 +125,9 @@ def test_update_living_plan_after_resolve_marks_all_completed_clusters_done( monkeypatch.setattr(living_plan_mod, "has_living_plan", lambda _p=None: True) monkeypatch.setattr(living_plan_mod, "load_plan", lambda _p=None: plan) monkeypatch.setattr(living_plan_mod, "purge_ids", lambda _plan, _ids: 2) - monkeypatch.setattr(living_plan_mod, "auto_complete_steps", lambda _plan: []) + monkeypatch.setattr( + living_plan_mod, "auto_complete_steps", lambda _plan, resolved_ids=None: [] + ) monkeypatch.setattr( living_plan_mod, "append_log_entry", @@ -174,7 +178,9 @@ def _purge(_plan, _ids): return 1 monkeypatch.setattr(living_plan_mod, "purge_ids", _purge) - monkeypatch.setattr(living_plan_mod, "auto_complete_steps", lambda _plan: []) + monkeypatch.setattr( + living_plan_mod, "auto_complete_steps", lambda _plan, resolved_ids=None: [] + ) monkeypatch.setattr(living_plan_mod, "append_log_entry", lambda *_a, **_k: None) monkeypatch.setattr( living_plan_mod, "add_uncommitted_issues", lambda *_a, **_k: None @@ -226,7 +232,9 @@ def test_update_living_plan_after_resolve_skips_reconcile_without_state( monkeypatch.setattr(living_plan_mod, "has_living_plan", lambda _p=None: True) monkeypatch.setattr(living_plan_mod, "load_plan", lambda _p=None: plan) monkeypatch.setattr(living_plan_mod, "purge_ids", lambda _plan, _ids: 1) - monkeypatch.setattr(living_plan_mod, "auto_complete_steps", lambda _plan: []) + monkeypatch.setattr( + living_plan_mod, "auto_complete_steps", lambda _plan, resolved_ids=None: [] + ) monkeypatch.setattr(living_plan_mod, "append_log_entry", lambda *_a, **_k: None) monkeypatch.setattr( living_plan_mod, "add_uncommitted_issues", lambda *_a, **_k: None @@ -270,7 +278,9 @@ def _purge(_plan, _ids): return 1 monkeypatch.setattr(living_plan_mod, "purge_ids", _purge) - monkeypatch.setattr(living_plan_mod, "auto_complete_steps", lambda _plan: []) + monkeypatch.setattr( + living_plan_mod, "auto_complete_steps", lambda _plan, resolved_ids=None: [] + ) monkeypatch.setattr(living_plan_mod, "append_log_entry", lambda *_a, **_k: None) monkeypatch.setattr( living_plan_mod, "add_uncommitted_issues", lambda *_a, **_k: None diff --git a/desloppify/tests/plan/test_step_completion_direct.py b/desloppify/tests/plan/test_step_completion_direct.py index 4ce32f068..456d4bb98 100644 --- a/desloppify/tests/plan/test_step_completion_direct.py +++ b/desloppify/tests/plan/test_step_completion_direct.py @@ -65,3 +65,27 @@ def test_auto_complete_steps_ignores_done_steps_and_invalid_step_shapes() -> Non assert messages == [] assert plan["clusters"]["epic/mixed"]["action_steps"][0]["done"] is True + + +def test_auto_complete_steps_ignores_unrelated_stale_refs() -> None: + plan = { + "queue_order": [], + "clusters": { + "target": { + "action_steps": [ + {"title": "Target step", "issue_refs": ["target-id"]}, + ] + }, + "unrelated": { + "action_steps": [ + {"title": "Unrelated step", "issue_refs": ["stale-id"]}, + ] + }, + }, + } + + messages = auto_complete_steps(plan, resolved_ids=["target-id"]) + + assert plan["clusters"]["target"]["action_steps"][0]["done"] is True + assert plan["clusters"]["unrelated"]["action_steps"][0].get("done") is not True + assert messages == [" Step 1 of 'target' auto-completed: Target step"]