Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions desloppify/app/commands/resolve/living_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,25 @@
from typing import NamedTuple

from desloppify.app.commands.helpers.transition_messages import emit_transition_message
from desloppify.app.commands.resolve.plan_load import warn_plan_load_degraded_once
from desloppify.base.config import target_strict_score_from_config
from desloppify.base.exception_sets import PLAN_LOAD_EXCEPTIONS
from desloppify.base.output.terminal import colorize
from desloppify.app.commands.resolve.plan_load import warn_plan_load_degraded_once
from desloppify.engine._plan.sync import live_planned_queue_empty, reconcile_plan
from desloppify.engine._plan.cluster_semantics import EXECUTION_STATUS_DONE
from desloppify.engine.plan_ops import (
append_log_entry,
auto_complete_steps,
purge_ids,
)
from desloppify.engine._plan.refresh_lifecycle import (
current_lifecycle_phase,
invalidate_postflight_scan,
)
from desloppify.engine._plan.sync import live_planned_queue_empty, reconcile_plan
from desloppify.engine._state.progression import (
maybe_append_entered_planning,
maybe_append_execution_drain,
)
from desloppify.engine.plan_ops import (
append_log_entry,
auto_complete_steps,
purge_ids,
)
from desloppify.engine.plan_state import (
add_uncommitted_issues,
has_living_plan,
Expand Down Expand Up @@ -93,6 +93,24 @@ def capture_cluster_context(plan: dict, resolved_ids: list[str]) -> ClusterConte
)


def _cluster_has_open_member(
plan: dict, state: dict | None, cluster_name: str
) -> bool:
"""Return whether a cluster still contains an open state work item."""
if state is None:
return False
cluster = (plan.get("clusters") or {}).get(cluster_name)
if not isinstance(cluster, dict):
return False
issues = state.get("work_items") or state.get("issues", {})
if not isinstance(issues, dict):
return False
return any(
issues.get(issue_id, {}).get("status") == "open"
for issue_id in cluster.get("issue_ids") or []
)


def update_living_plan_after_resolve(
*,
args: argparse.Namespace,
Expand Down Expand Up @@ -142,8 +160,12 @@ def update_living_plan_after_resolve(
# Clear focus when the active cluster is done
if plan.get("active_cluster") in set(completed_clusters):
plan["active_cluster"] = None
elif ctx.cluster_name and ctx.cluster_remaining > 0:
# Auto-focus on the cluster while there's still work in it
elif (
ctx.cluster_name
and ctx.cluster_remaining > 0
and _cluster_has_open_member(plan, state, ctx.cluster_name)
):
# Auto-focus only while a state-open member remains in the cluster.
plan["active_cluster"] = ctx.cluster_name
if args.status == "fixed":
add_uncommitted_issues(plan, all_resolved)
Expand Down
88 changes: 88 additions & 0 deletions desloppify/tests/commands/resolve/test_living_plan_direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,94 @@ def test_update_living_plan_after_resolve_fixed_flow(monkeypatch, capsys) -> Non
assert "add" in calls and "clear" in calls and "save" in calls


def test_update_living_plan_after_resolve_does_not_focus_stale_fixed_member(
monkeypatch,
) -> None:
plan = {
"queue_order": ["a"],
"overrides": {
"a": {"cluster": "epic/a"},
"stale-b": {"cluster": "epic/a"},
},
"clusters": {"epic/a": {"issue_ids": ["a", "stale-b"]}},
}
state = {
"work_items": {
"a": {"id": "a", "status": "fixed"},
"stale-b": {"id": "stale-b", "status": "fixed"},
}
}

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, "auto_complete_steps", lambda _plan: [])
monkeypatch.setattr(living_plan_mod, "append_log_entry", lambda *_a, **_k: None)
monkeypatch.setattr(
living_plan_mod, "add_uncommitted_issues", lambda *_a, **_k: None
)
monkeypatch.setattr(
living_plan_mod, "invalidate_postflight_scan", lambda *_a, **_k: None
)
monkeypatch.setattr(living_plan_mod, "live_planned_queue_empty", lambda _plan: False)
monkeypatch.setattr(living_plan_mod, "save_plan", lambda _plan, _p=None: None)

updated_plan, ctx = living_plan_mod.update_living_plan_after_resolve(
args=_args(status="fixed"),
all_resolved=["a"],
attestation="attest",
state=state,
)

assert ctx.cluster_remaining == 1
assert updated_plan is plan
assert updated_plan["clusters"]["epic/a"]["issue_ids"] == ["stale-b"]
assert updated_plan.get("active_cluster") is None


def test_update_living_plan_after_resolve_focuses_open_remaining_member(
monkeypatch,
) -> None:
plan = {
"queue_order": ["a", "live-b"],
"overrides": {
"a": {"cluster": "epic/a"},
"live-b": {"cluster": "epic/a"},
},
"clusters": {"epic/a": {"issue_ids": ["a", "live-b"]}},
}
state = {
"work_items": {
"a": {"id": "a", "status": "fixed"},
"live-b": {"id": "live-b", "status": "open"},
}
}

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, "auto_complete_steps", lambda _plan: [])
monkeypatch.setattr(living_plan_mod, "append_log_entry", lambda *_a, **_k: None)
monkeypatch.setattr(
living_plan_mod, "add_uncommitted_issues", lambda *_a, **_k: None
)
monkeypatch.setattr(
living_plan_mod, "invalidate_postflight_scan", lambda *_a, **_k: None
)
monkeypatch.setattr(living_plan_mod, "live_planned_queue_empty", lambda _plan: False)
monkeypatch.setattr(living_plan_mod, "save_plan", lambda _plan, _p=None: None)

updated_plan, ctx = living_plan_mod.update_living_plan_after_resolve(
args=_args(status="fixed"),
all_resolved=["a"],
attestation="attest",
state=state,
)

assert ctx.cluster_remaining == 1
assert updated_plan is plan
assert updated_plan["clusters"]["epic/a"]["issue_ids"] == ["live-b"]
assert updated_plan["active_cluster"] == "epic/a"


def test_update_living_plan_after_resolve_marks_all_completed_clusters_done(
monkeypatch,
) -> None:
Expand Down