Complete tasks 3-7: harden verdict parsing, outputs retention, base branch, linux schedule parity, claim loop task
CI / build (push) Has been cancelled
CI / build (push) Has been cancelled
This commit is contained in:
+119
-44
@@ -75,12 +75,14 @@ def scan_all_tasks(project_dir: Path) -> list[dict]:
|
||||
tasks = []
|
||||
for name, path in _all_tasks(project_dir):
|
||||
phase = _read_state(path)
|
||||
tasks.append({
|
||||
"name": name,
|
||||
"path": str(path),
|
||||
"phase": phase,
|
||||
"base_phase": _base_phase(phase) if phase else None,
|
||||
})
|
||||
tasks.append(
|
||||
{
|
||||
"name": name,
|
||||
"path": str(path),
|
||||
"phase": phase,
|
||||
"base_phase": _base_phase(phase) if phase else None,
|
||||
}
|
||||
)
|
||||
return tasks
|
||||
|
||||
|
||||
@@ -104,14 +106,20 @@ def needs_user_input(task: dict) -> bool:
|
||||
if verdict_file.exists():
|
||||
content = verdict_file.read_text()
|
||||
first_line = content.split("\n")[0] if content else ""
|
||||
if any(kw in first_line.upper() for kw in ("FAIL", "NEEDS_REVIEW", "TIE-BREAK")):
|
||||
if any(
|
||||
kw in first_line.upper() for kw in ("FAIL", "NEEDS_REVIEW", "TIE-BREAK")
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def sort_by_advancement(tasks: list[dict]) -> list[dict]:
|
||||
"""Sort tasks by how close they are to completion (most advanced first)."""
|
||||
return sorted(tasks, key=lambda t: PHASE_PRIORITY.get(t.get("base_phase", ""), 0), reverse=True)
|
||||
return sorted(
|
||||
tasks,
|
||||
key=lambda t: PHASE_PRIORITY.get(t.get("base_phase", ""), 0),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def detect_stuck_tasks(project_dir: Path, threshold_minutes: int = 60) -> list[dict]:
|
||||
@@ -128,12 +136,14 @@ def detect_stuck_tasks(project_dir: Path, threshold_minutes: int = 60) -> list[d
|
||||
mtime = state_file.stat().st_mtime
|
||||
age_minutes = (now - mtime) / 60
|
||||
if age_minutes > threshold_minutes:
|
||||
stuck.append({
|
||||
"name": name,
|
||||
"phase": phase,
|
||||
"age_minutes": round(age_minutes),
|
||||
"path": str(path),
|
||||
})
|
||||
stuck.append(
|
||||
{
|
||||
"name": name,
|
||||
"phase": phase,
|
||||
"age_minutes": round(age_minutes),
|
||||
"path": str(path),
|
||||
}
|
||||
)
|
||||
return sorted(stuck, key=lambda t: t["age_minutes"], reverse=True)
|
||||
|
||||
|
||||
@@ -144,7 +154,10 @@ def cmd_summary(args):
|
||||
|
||||
if not all_tasks:
|
||||
print("No tasks found. Create one with:")
|
||||
print(" python ~/.automaton/scripts/status.py --create-task <name> --project", project_dir)
|
||||
print(
|
||||
" python ~/.automaton/scripts/status.py --create-task <name> --project",
|
||||
project_dir,
|
||||
)
|
||||
return 0
|
||||
|
||||
terminal = [t for t in all_tasks if is_terminal(t)]
|
||||
@@ -154,7 +167,9 @@ def cmd_summary(args):
|
||||
stuck = detect_stuck_tasks(project_dir)
|
||||
|
||||
print(f"Project: {project_dir}")
|
||||
print(f"Tasks: {len(all_tasks)} total | {len(terminal)} done | {len(non_terminal)} in progress")
|
||||
print(
|
||||
f"Tasks: {len(all_tasks)} total | {len(terminal)} done | {len(non_terminal)} in progress"
|
||||
)
|
||||
print(f" Blocked (awaiting user): {len(blocked)}")
|
||||
print(f" Unblocked (ready to drive): {len(unblocked)}")
|
||||
print(f" Stuck (>60 min): {len(stuck)}")
|
||||
@@ -169,20 +184,27 @@ def cmd_summary(args):
|
||||
if unblocked:
|
||||
print("=== READY TO DRIVE ===")
|
||||
for t in sort_by_advancement(unblocked):
|
||||
print(f" {t['name']}: {t['phase']} (priority: {PHASE_PRIORITY.get(t.get('base_phase', ''), 0)})")
|
||||
print(
|
||||
f" {t['name']}: {t['phase']} (priority: {PHASE_PRIORITY.get(t.get('base_phase', ''), 0)})"
|
||||
)
|
||||
print()
|
||||
|
||||
if blocked:
|
||||
print("=== AWAITING USER ===")
|
||||
for t in blocked:
|
||||
approval = t["phase"].endswith(":awaiting_approval")
|
||||
print(f" {t['name']}: {t['phase']}{' (needs --approve)' if approval else ' (needs VERDICT review)'}")
|
||||
print(
|
||||
f" {t['name']}: {t['phase']}{' (needs --approve)' if approval else ' (needs VERDICT review)'}"
|
||||
)
|
||||
print()
|
||||
|
||||
if not unblocked and not non_terminal:
|
||||
print("ALL TASKS TERMINAL — nothing to drive.")
|
||||
print("Create a new task to continue:")
|
||||
print(" python ~/.automaton/scripts/status.py --create-task <name> --project", project_dir)
|
||||
print(
|
||||
" python ~/.automaton/scripts/status.py --create-task <name> --project",
|
||||
project_dir,
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -194,7 +216,10 @@ def cmd_drive(args):
|
||||
|
||||
if not all_tasks:
|
||||
print("NO_TASKS: No tasks found. Create one with:")
|
||||
print(" python ~/.automaton/scripts/status.py --create-task <name> --project", project_dir)
|
||||
print(
|
||||
" python ~/.automaton/scripts/status.py --create-task <name> --project",
|
||||
project_dir,
|
||||
)
|
||||
return 1
|
||||
|
||||
non_terminal = [t for t in all_tasks if not is_terminal(t)]
|
||||
@@ -202,7 +227,10 @@ def cmd_drive(args):
|
||||
if not non_terminal:
|
||||
print("ORCHESTRATION_COMPLETE: all tasks done.")
|
||||
print("Nothing to drive. Create a new task:")
|
||||
print(" python ~/.automaton/scripts/status.py --create-task <name> --project", project_dir)
|
||||
print(
|
||||
" python ~/.automaton/scripts/status.py --create-task <name> --project",
|
||||
project_dir,
|
||||
)
|
||||
return 0
|
||||
|
||||
unblocked = [t for t in non_terminal if not needs_user_input(t)]
|
||||
@@ -215,7 +243,9 @@ def cmd_drive(args):
|
||||
print("To proceed:")
|
||||
for t in non_terminal:
|
||||
if t["phase"].endswith(":awaiting_approval"):
|
||||
print(f" python ~/.automaton/scripts/status.py --approve --task {t['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --approve --task {t['name']} --project {project_dir}"
|
||||
)
|
||||
else:
|
||||
print(f" Review {t['name']}/VERDICT.md and take action")
|
||||
return 0
|
||||
@@ -233,53 +263,85 @@ def cmd_drive(args):
|
||||
|
||||
if base == "new":
|
||||
print("→ Transition to research:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition research --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition research --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "research":
|
||||
if phase == "research":
|
||||
print("→ Generate SPEC.md, then transition to awaiting_approval:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition research:awaiting_approval --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition research:awaiting_approval --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif phase == "research:approved":
|
||||
print("→ Transition to next phase (decomposition/design/implement):")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition decomposition --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition decomposition --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "decomposition":
|
||||
if phase == "decomposition":
|
||||
print("→ Generate DECOMPOSITION.md, then transition to awaiting_approval:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition decomposition:awaiting_approval --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition decomposition:awaiting_approval --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif phase == "decomposition:approved":
|
||||
print("→ Create sub-tasks from DECOMPOSITION.md, then complete parent:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition complete --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition complete --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "design":
|
||||
if phase == "design":
|
||||
print("→ Generate DESIGN.md, then transition to awaiting_approval:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition design:awaiting_approval --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition design:awaiting_approval --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif phase == "design:approved":
|
||||
print("→ Transition to test_design or implement:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition test_design --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition test_design --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "test_design":
|
||||
if phase == "test_design":
|
||||
print("→ Generate TEST_PLAN.md, then transition to awaiting_approval:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition test_design:awaiting_approval --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition test_design:awaiting_approval --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif phase == "test_design:approved":
|
||||
print("→ Transition to implement:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition implement --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition implement --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "implement":
|
||||
print("→ Write implementation, generate IMPLEMENTATION.md, then transition:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition bug_find --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition bug_find --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "bug_find":
|
||||
print("→ Generate BUG_REPORT.md, then transition:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition adversarial_bug_find --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition adversarial_bug_find --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "adversarial_bug_find":
|
||||
print("→ Generate ADVERSARIAL_BUG_REPORT.md, then transition:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition doc_review --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition doc_review --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "doc_review":
|
||||
print("→ Generate DOC_REVIEW.md, then transition:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition referee --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition referee --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "referee":
|
||||
print("→ Generate VERDICT.md, then transition to complete:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition complete --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition complete --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
elif base == "human_intervention":
|
||||
print("→ Task needs human intervention. Review and transition to referee or complete:")
|
||||
print(f" python ~/.automaton/scripts/status.py --transition referee --task {task['name']} --project {project_dir}")
|
||||
print(
|
||||
"→ Task needs human intervention. Review and transition to referee or complete:"
|
||||
)
|
||||
print(
|
||||
f" python ~/.automaton/scripts/status.py --transition referee --task {task['name']} --project {project_dir}"
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
@@ -294,6 +356,7 @@ def cmd_loop(args):
|
||||
return result
|
||||
if args.delay:
|
||||
import time as tm
|
||||
|
||||
tm.sleep(args.delay)
|
||||
print(f"Reached max iterations ({max_iterations}).")
|
||||
return 0
|
||||
@@ -313,7 +376,7 @@ def cmd_stuck(args):
|
||||
for t in stuck:
|
||||
print(f" {t['name']}: stuck at '{t['phase']}' for {t['age_minutes']} min")
|
||||
print(f" Path: {t['path']}")
|
||||
print(f" Action: Review and transition manually or mark complete")
|
||||
print(" Action: Review and transition manually or mark complete")
|
||||
print()
|
||||
print(f"Total stuck: {len(stuck)}")
|
||||
return 0
|
||||
@@ -322,13 +385,25 @@ def cmd_stuck(args):
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Automaton autopilot runtime")
|
||||
parser.add_argument("--project", help="Project root directory")
|
||||
parser.add_argument("--drive", action="store_true", help="Drive one step forward (default)")
|
||||
parser.add_argument(
|
||||
"--drive", action="store_true", help="Drive one step forward (default)"
|
||||
)
|
||||
parser.add_argument("--summary", action="store_true", help="Show autopilot summary")
|
||||
parser.add_argument("--stuck", action="store_true", dest="detect_stuck", help="Detect stuck tasks")
|
||||
parser.add_argument(
|
||||
"--stuck", action="store_true", dest="detect_stuck", help="Detect stuck tasks"
|
||||
)
|
||||
parser.add_argument("--loop", action="store_true", help="Run continuous drive loop")
|
||||
parser.add_argument("--max-iterations", type=int, help="Max iterations for --loop (default: 100)")
|
||||
parser.add_argument("--delay", type=int, help="Delay seconds between iterations for --loop")
|
||||
parser.add_argument("--threshold", type=int, help="Stuck detection threshold in minutes (default: 60)")
|
||||
parser.add_argument(
|
||||
"--max-iterations", type=int, help="Max iterations for --loop (default: 100)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--delay", type=int, help="Delay seconds between iterations for --loop"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--threshold",
|
||||
type=int,
|
||||
help="Stuck detection threshold in minutes (default: 60)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user