canina/.ai_agency/orchestrate.py
parsa aghaei f437f46e2e feat: AI Software Agency v3 — complete overhaul
- Add AGENCY.md: master orchestration protocol (universal AI editor entry point)
- Add 00_intake.md: user requirements & intent detection agent
- Add 10_deploy.md: production deployment agent
- Add 11_seo_content.md: SEO specialist & content writer agent (dual mode)
- Add specs/reviews/: directory for specialist review reports

REVIEW PIPELINE (new):
- Each specialist reviews ONLY their own domain:
  - 04_dev_backend: API, services, DB, auth, DTOs
  - 05_dev_frontend: components, state, UX code, performance
  - 07_visual_qa: UX patterns, a11y, responsive, design system
  - 08_devops_security: secrets, Docker, CVEs, CI/CD
  - 11_seo_content: meta tags, content quality, structured data
- 02_product_manager: synthesis mode reads all findings, deduplicates,
  creates unified prioritized backlog

All agents now support dual modes (REVIEW + IMPLEMENT/ENFORCE/CREATE/INSPECT)
state.json v3: adds project_intent, review_phase tracking, resume_context
backlog.json: fixed structure {tasks: [...]}, added sub_steps per task
orchestrate.py: simplified to state management utility (no fake AI calls)
2026-07-26 17:30:05 +03:30

198 lines
6.6 KiB
Python

#!/usr/bin/env python3
"""
AI Agency State Management Utility
====================================
This script is a HELPER TOOL only — it does NOT run the AI.
The AI orchestration logic lives in .ai_agency/AGENCY.md
Use these utilities to:
- View current project state
- Manually reset state
- Fix stuck/blocked states
- Inspect backlog progress
Usage:
python orchestrate.py status # Show current state summary
python orchestrate.py reset # Reset state to IDLE (start over)
python orchestrate.py unblock # Clear BLOCKED_NEEDS_HUMAN status
python orchestrate.py next-task # Show next pending task
python orchestrate.py progress # Show backlog completion progress
"""
import os
import sys
import json
from datetime import datetime
AGENCY_DIR = ".ai_agency"
STATE_FILE = os.path.join(AGENCY_DIR, "memory/state.json")
BACKLOG_FILE = os.path.join(AGENCY_DIR, "memory/backlog.json")
def load_json(path):
if not os.path.exists(path):
return None
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def save_json(path, data):
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def cmd_status():
state = load_json(STATE_FILE)
if not state:
print("❌ No state.json found. Agency has not been initialized yet.")
return
print(f"\n{'='*50}")
print(f"🏢 AI Agency Status")
print(f"{'='*50}")
print(f"Project : {state.get('project_name', 'Unknown')}")
print(f"Mode : {state.get('project_mode', 'Unknown')}")
print(f"Status : {state.get('status', 'Unknown')}")
cp = state.get("checkpoint", {})
print(f"\n📍 Checkpoint:")
print(f" Active Agent : {cp.get('active_agent', 'None')}")
print(f" Active Ticket : {cp.get('current_ticket_id', 'None')}")
sub = cp.get("sub_step", {})
if sub:
print(f" Sub-step : {sub.get('index')}/{sub.get('total')}{sub.get('description', '')}")
rc = state.get("resume_context", {})
if rc.get("last_completed_action"):
print(f"\n💾 Resume Context:")
print(f" Last done : {rc.get('last_completed_action', '')}")
print(f" Next todo : {rc.get('next_action', '')}")
guards = state.get("execution_guards", {})
if guards.get("blocking_reason"):
print(f"\n🚫 BLOCKED: {guards['blocking_reason']}")
print(f"\n🕐 Last Updated: {state.get('last_updated', 'Unknown')}")
def cmd_progress():
backlog = load_json(BACKLOG_FILE)
if not backlog:
print("❌ No backlog.json found.")
return
tasks = backlog.get("tasks", [])
if not tasks:
print("❌ No tasks in backlog.")
return
total = len(tasks)
completed = sum(1 for t in tasks if t.get("status") == "completed")
pending = sum(1 for t in tasks if t.get("status") == "pending")
blocked = sum(1 for t in tasks if t.get("status") in ["BLOCKED", "SECURITY_FAILED"])
print(f"\n{'='*50}")
print(f"📋 Backlog Progress")
print(f"{'='*50}")
print(f"Total : {total}")
print(f"✅ Done : {completed}")
print(f"⏳ Pending: {pending}")
print(f"❌ Blocked: {blocked}")
print(f"\nProgress : [{'' * completed}{'' * pending}] {int(completed/total*100)}%")
print(f"\n📝 Task List:")
for task in tasks:
icon = {"completed": "", "pending": "", "COMPLETED_PENDING_QA": "🔍"}.get(task.get("status", ""), "")
print(f" {icon} [{task['priority']}] {task['id']}: {task['title'][:60]}")
def cmd_next_task():
backlog = load_json(BACKLOG_FILE)
if not backlog:
print("❌ No backlog.json found.")
return
tasks = backlog.get("tasks", [])
completed_ids = [t["id"] for t in tasks if t.get("status") == "completed"]
for task in tasks:
if task.get("status") == "pending":
deps = task.get("dependency_task_ids", [])
if all(dep in completed_ids for dep in deps):
print(f"\n🎯 Next Task Ready:")
print(f" ID : {task['id']}")
print(f" Title : {task['title']}")
print(f" Role : {task['assigned_role']}")
print(f" Priority : {task['priority']}")
print(f" Est. : {task['estimated_minutes']} minutes")
return
print("✅ No pending tasks with satisfied dependencies found.")
def cmd_unblock():
state = load_json(STATE_FILE)
if not state:
print("❌ No state.json found.")
return
state["status"] = "IN_PROGRESS"
state["execution_guards"]["retry_count"] = 0
state["execution_guards"]["blocking_reason"] = None
state["last_updated"] = datetime.now().isoformat()
save_json(STATE_FILE, state)
print(f"✅ Status reset to IN_PROGRESS. Blocking reason cleared.")
print(f" Active agent: {state['checkpoint'].get('active_agent')}")
print(f" Resume the AI and say: 'continue agency work'")
def cmd_reset():
confirm = input("⚠️ This will reset state to IDLE. All checkpoint progress will be lost. Type 'yes' to confirm: ")
if confirm.strip().lower() != "yes":
print("Aborted.")
return
state = {
"schema_version": "2.0",
"project_name": "UNINITIALIZED",
"project_root": ".",
"project_mode": "UNKNOWN",
"status": "IDLE",
"checkpoint": {
"active_agent": "00_intake",
"current_ticket_id": None,
"sub_step": { "index": 0, "total": 0, "name": "", "description": "" }
},
"resume_context": {
"last_completed_action": None,
"next_action": "Start from 00_intake — gather requirements",
"files_modified_this_session": [],
"files_pending": [],
"notes": "Fresh start. Tell the AI: 'use the agency' to begin."
},
"tech_stack": {},
"execution_guards": {
"retry_count": 0,
"max_retry_attempts": 3,
"blocking_reason": None,
"agent_visit_counts": {}
},
"agent_call_log": [],
"last_updated": datetime.now().isoformat()
}
save_json(STATE_FILE, state)
print("✅ State reset to IDLE. Start fresh by telling the AI: 'use the agency'")
def main():
commands = {
"status": cmd_status,
"progress": cmd_progress,
"next-task": cmd_next_task,
"unblock": cmd_unblock,
"reset": cmd_reset,
}
if len(sys.argv) < 2 or sys.argv[1] not in commands:
print(__doc__)
print("Available commands:", ", ".join(commands.keys()))
sys.exit(1)
commands[sys.argv[1]]()
if __name__ == "__main__":
main()