← refacto-feature-scout
"""Apply the owner's decisions — approve, reject, ship — wherever they are made.
The board in the browser and `feature-scout decide` both come through here, so there is one
set of rules about what may follow what.
"""
from __future__ import annotations
from typing import Any
from .store import Store
def _was_interrupted(store: Store, idea: dict[str, Any]) -> bool:
from .build import was_interrupted # imported here: build reads this module's rules
return was_interrupted(store, idea)
ALLOWED_FROM = {"proposed": {"approve", "reject"}, "approved": {"reject"}, "building": set(),
"built": {"ship", "reject"}, "blocked": {"approve", "reject"}, "failed": {"approve", "reject"},
"rejected": {"approve"}, "shipped": set()}
def apply_commands(store: Store, commands: list[tuple[str, list[int]]]) -> list[str]:
"""Apply decisions; returns plain-English lines describing what happened."""
lines: list[str] = []
for verb, ids in commands:
for idea_id in ids:
idea = store.idea(idea_id)
if not idea:
lines.append(f"#{idea_id}: I don't have an idea with that number.")
continue
allowed = ALLOWED_FROM.get(idea["status"], set())
# Nothing may follow a build that is running. One that was interrupted only looks
# like it is running, and has to be recoverable — from here or from the page.
stopped = verb in ("approve", "reject") and _was_interrupted(store, idea)
if stopped:
allowed = allowed | {"approve", "reject"}
if verb not in allowed:
lines.append(f"#{idea_id} {idea['title']}: can't {verb} it, it is currently {idea['status']}.")
continue
if stopped and verb == "approve":
store.set_status(idea_id, "approved", error=None)
lines.append(f"#{idea_id} {idea['title']}: back in the queue. It carries on from "
"where it stopped rather than starting over.")
continue
if verb == "approve":
store.set_status(idea_id, "approved", error=None)
lines.append(f"#{idea_id} {idea['title']}: approved — I'll build and test it next.")
elif verb == "reject":
store.set_status(idea_id, "rejected")
lines.append(f"#{idea_id} {idea['title']}: rejected.")
elif verb == "ship":
store.request_ship(idea_id)
lines.append(f"#{idea_id} {idea['title']}: will merge it into the app on the next pass.")
return lines
def ideas_by_status(store: Store) -> dict[str, list[int]]:
out: dict[str, list[int]] = {}
for i in store.ideas():
out.setdefault(i["status"], []).append(i["id"])
return out