#!/usr/bin/env python3 """ AI_Moderator agent. Upgraded to use a persona-driven AI prompt to contribute meaningful comments and votes to discussions, rather than just simple reminders. """ from __future__ import annotations import argparse import subprocess import sys from pathlib import Path # --- Dynamic SDK Loader --- def load_agent_context(repo_root: Path): vendor_root = repo_root / ".cascadingdev" if (vendor_root / "cascadingdev" / "agent").exists(): sys.path.insert(0, str(vendor_root)) from cascadingdev.agent.sdk import AgentContext return AgentContext project_root = Path(__file__).resolve().parents[1] src_path = project_root / "src" if src_path.exists(): sys.path.insert(0, str(src_path)) from cascadingdev.agent.sdk import AgentContext return AgentContext from cascadingdev.agent.sdk import AgentContext return AgentContext def load_provider_client(repo_root: Path): vendor_root = repo_root / ".cascadingdev" if (vendor_root / "cascadingdev" / "agent").exists(): sys.path.insert(0, str(vendor_root)) from cascadingdev.agent.providers import ProviderClient return ProviderClient project_root = Path(__file__).resolve().parents[1] src_path = project_root / "src" if src_path.exists(): sys.path.insert(0, str(src_path)) from cascadingdev.agent.providers import ProviderClient return ProviderClient from cascadingdev.agent.providers import ProviderClient return ProviderClient # --- Agent Configuration --- PRIMER = """ You are AI_Moderator, the project steward for CascadingDev discussions. Responsibilities: - When a new discussion starts, post an introductory note outlining objectives for the stage. - Monitor progression, call out missing votes or unclear decisions, and suggest next steps. - Vote READY only when the goals are satisfied; otherwise vote CHANGES and explain what is missing. Instructions: - Read the current discussion content carefully. - Respond with a JSON object containing keys: "comment" (Markdown body only, no Name/VOTE lines), "vote" (READY/CHANGES/REJECT). - If no action is required, set "comment" to an empty string but still include "vote". - Keep the tone collaborative and concise. """ def build_prompt(discussion_text: str) -> str: return f"{PRIMER}\n\nCurrent discussion:\n\n{discussion_text}\n\nProvide your JSON response now." def scrub_comment(text: str) -> str: cleaned: list[str] = [] for line in text.splitlines(): stripped = line.strip() upper = stripped.upper() if upper.startswith("NAME:"): continue if upper.startswith("VOTE:"): continue cleaned.append(line) return "\n".join(cleaned).strip() def append_moderator_comment(context, comment: str, vote: str) -> None: lines = ["---", "", "Name: AI_Moderator"] comment = scrub_comment(comment) if comment: lines.append(comment) lines.append(f"VOTE: {vote}") lines.append("") context.append_block("\n".join(lines)) def ensure_staged(repo_root: Path, relative_path: Path) -> None: subprocess.run(["git", "add", relative_path.as_posix()], cwd=repo_root, check=False, capture_output=True) def main() -> int: parser = argparse.ArgumentParser(description="AI Moderator agent") parser.add_argument("--repo-root", required=True, help="Repository root path") parser.add_argument("--path", required=True, help="Relative path to discussion file") args = parser.parse_args() repo_root = Path(args.repo_root).resolve() discussion_rel = Path(args.path) AgentContext = load_agent_context(repo_root) ProviderClient = load_provider_client(repo_root) context = AgentContext(repo_root, discussion_rel) text = context.read_text() if not text.strip(): return 0 provider = ProviderClient(repo_root) try: response = provider.structured(build_prompt(text), model_hint="quality") except Exception: response = { "comment": "Gentle reminder: please keep the discussion moving and ensure votes reflect actual consensus.", "vote": "CHANGES", } comment = str(response.get("comment", "")).strip() vote = str(response.get("vote", "READY")).strip().upper() if vote not in {"READY", "CHANGES", "REJECT"}: vote = "CHANGES" # Do nothing if the AI returns an empty comment and a non-binding vote if not comment and vote == "READY": return 0 append_moderator_comment(context, comment, vote) ensure_staged(repo_root, discussion_rel) return 0 if __name__ == "__main__": raise SystemExit(main())