forked from mengyxu/noob-components
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
6.6 KiB
253 lines
6.6 KiB
#!/usr/bin/env python3 |
|
""" |
|
Phase Management Utilities. |
|
|
|
Centralized phase tracking for multi-agent pipeline. |
|
|
|
Provides: |
|
get_current_phase - Returns current phase number |
|
get_total_phases - Returns total phase count |
|
get_phase_action - Returns action name for phase |
|
get_phase_info - Returns "N/M (action)" format |
|
set_phase - Sets current_phase |
|
advance_phase - Advances to next phase |
|
get_phase_for_action - Returns phase number for action |
|
map_subagent_to_action - Map subagent type to action name |
|
is_phase_completed - Check if phase is completed |
|
is_current_action - Check if at specific action |
|
""" |
|
|
|
from __future__ import annotations |
|
|
|
import json |
|
from pathlib import Path |
|
|
|
|
|
def _read_json_file(path: Path) -> dict | None: |
|
"""Read and parse a JSON file.""" |
|
try: |
|
return json.loads(path.read_text(encoding="utf-8")) |
|
except (FileNotFoundError, json.JSONDecodeError, OSError): |
|
return None |
|
|
|
|
|
def _write_json_file(path: Path, data: dict) -> bool: |
|
"""Write dict to JSON file.""" |
|
try: |
|
path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") |
|
return True |
|
except (OSError, IOError): |
|
return False |
|
|
|
|
|
# ============================================================================= |
|
# Phase Functions |
|
# ============================================================================= |
|
|
|
def get_current_phase(task_json: Path) -> int: |
|
"""Get current phase number. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
|
|
Returns: |
|
Current phase number, or 0 if not found. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return 0 |
|
return data.get("current_phase", 0) or 0 |
|
|
|
|
|
def get_total_phases(task_json: Path) -> int: |
|
"""Get total number of phases. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
|
|
Returns: |
|
Total phase count, or 0 if not found. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return 0 |
|
|
|
next_action = data.get("next_action", []) |
|
if isinstance(next_action, list): |
|
return len(next_action) |
|
return 0 |
|
|
|
|
|
def get_phase_action(task_json: Path, phase: int) -> str: |
|
"""Get action name for a specific phase. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
phase: Phase number. |
|
|
|
Returns: |
|
Action name, or "unknown" if not found. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return "unknown" |
|
|
|
next_action = data.get("next_action", []) |
|
if isinstance(next_action, list): |
|
for item in next_action: |
|
if isinstance(item, dict) and item.get("phase") == phase: |
|
return item.get("action", "unknown") |
|
return "unknown" |
|
|
|
|
|
def get_phase_info(task_json: Path) -> str: |
|
"""Get formatted phase info: "N/M (action)". |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
|
|
Returns: |
|
Formatted string like "1/4 (implement)". |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return "N/A" |
|
|
|
current_phase = data.get("current_phase", 0) or 0 |
|
total_phases = get_total_phases(task_json) |
|
action_name = get_phase_action(task_json, current_phase) |
|
|
|
if current_phase == 0 or current_phase is None: |
|
return f"0/{total_phases} (pending)" |
|
else: |
|
return f"{current_phase}/{total_phases} ({action_name})" |
|
|
|
|
|
def set_phase(task_json: Path, phase: int) -> bool: |
|
"""Set current phase to a specific value. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
phase: Phase number to set. |
|
|
|
Returns: |
|
True on success, False on error. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return False |
|
|
|
data["current_phase"] = phase |
|
return _write_json_file(task_json, data) |
|
|
|
|
|
def advance_phase(task_json: Path) -> bool: |
|
"""Advance to next phase. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
|
|
Returns: |
|
True on success, False on error or at final phase. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return False |
|
|
|
current = data.get("current_phase", 0) or 0 |
|
total = get_total_phases(task_json) |
|
next_phase = current + 1 |
|
|
|
if next_phase > total: |
|
return False # Already at final phase |
|
|
|
data["current_phase"] = next_phase |
|
return _write_json_file(task_json, data) |
|
|
|
|
|
def get_phase_for_action(task_json: Path, action: str) -> int: |
|
"""Get phase number for a specific action name. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
action: Action name. |
|
|
|
Returns: |
|
Phase number, or 0 if not found. |
|
""" |
|
data = _read_json_file(task_json) |
|
if not data: |
|
return 0 |
|
|
|
next_action = data.get("next_action", []) |
|
if isinstance(next_action, list): |
|
for item in next_action: |
|
if isinstance(item, dict) and item.get("action") == action: |
|
return item.get("phase", 0) |
|
return 0 |
|
|
|
|
|
def map_subagent_to_action(subagent_type: str) -> str: |
|
"""Map subagent type to action name. |
|
|
|
Used by hooks to determine which action a subagent corresponds to. |
|
|
|
Args: |
|
subagent_type: Subagent type string. |
|
|
|
Returns: |
|
Corresponding action name. |
|
""" |
|
mapping = { |
|
"implement": "implement", |
|
"check": "check", |
|
"debug": "debug", |
|
"research": "research", |
|
} |
|
return mapping.get(subagent_type, subagent_type) |
|
|
|
|
|
def is_phase_completed(task_json: Path, phase: int) -> bool: |
|
"""Check if a phase is completed (current_phase > phase). |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
phase: Phase number to check. |
|
|
|
Returns: |
|
True if phase is completed. |
|
""" |
|
current = get_current_phase(task_json) |
|
return current > phase |
|
|
|
|
|
def is_current_action(task_json: Path, action: str) -> bool: |
|
"""Check if we're at a specific action. |
|
|
|
Args: |
|
task_json: Path to task.json file. |
|
action: Action name to check. |
|
|
|
Returns: |
|
True if current phase matches the action. |
|
""" |
|
current = get_current_phase(task_json) |
|
action_phase = get_phase_for_action(task_json, action) |
|
return current == action_phase |
|
|
|
|
|
# ============================================================================= |
|
# Main Entry (for testing) |
|
# ============================================================================= |
|
|
|
if __name__ == "__main__": |
|
import sys |
|
|
|
if len(sys.argv) > 1: |
|
path = Path(sys.argv[1]) |
|
print(f"Task JSON: {path}") |
|
print(f"Phase info: {get_phase_info(path)}") |
|
print(f"Current phase: {get_current_phase(path)}") |
|
print(f"Total phases: {get_total_phases(path)}") |
|
else: |
|
print("Usage: python3 phase.py <task.json>")
|
|
|