temp
This commit is contained in:
1
scripts/lib/__init__.py
Normal file
1
scripts/lib/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Shared automation library for PlanB task tooling."""
|
||||
BIN
scripts/lib/__pycache__/task_context.cpython-314.pyc
Normal file
BIN
scripts/lib/__pycache__/task_context.cpython-314.pyc
Normal file
Binary file not shown.
52
scripts/lib/agents/__init__.py
Normal file
52
scripts/lib/agents/__init__.py
Normal file
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
from .base import BaseAgent
|
||||
from .antigravity import AntigravityAgent
|
||||
from .hermes import HermesAgent
|
||||
|
||||
_REGISTERED_AGENTS: List[BaseAgent] = [
|
||||
AntigravityAgent(),
|
||||
HermesAgent(),
|
||||
]
|
||||
|
||||
_LOOKUP_MAP: Dict[str, BaseAgent] = {}
|
||||
for agent in _REGISTERED_AGENTS:
|
||||
_LOOKUP_MAP[agent.name.lower()] = agent
|
||||
for alias in agent.aliases:
|
||||
_LOOKUP_MAP[alias.lower()] = agent
|
||||
|
||||
|
||||
def list_agents() -> List[BaseAgent]:
|
||||
"""Return list of all registered agent instances."""
|
||||
return list(_REGISTERED_AGENTS)
|
||||
|
||||
|
||||
def get_agent(name_or_alias: str) -> Optional[BaseAgent]:
|
||||
"""Look up an agent by its primary name or any alias."""
|
||||
return _LOOKUP_MAP.get(name_or_alias.strip().lower())
|
||||
|
||||
|
||||
def get_default_agent() -> BaseAgent:
|
||||
"""
|
||||
Resolve the default agent to use:
|
||||
1. Check TASK_AGENT environment variable.
|
||||
2. Fall back to the first available agent CLI found on PATH.
|
||||
3. If none installed, return the first registered agent (which will report not found on launch).
|
||||
"""
|
||||
env_agent = os.environ.get("TASK_AGENT")
|
||||
if env_agent:
|
||||
matched = get_agent(env_agent)
|
||||
if matched:
|
||||
return matched
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unknown agent specified in TASK_AGENT='{env_agent}'. "
|
||||
f"Available agents: {', '.join([a.name for a in _REGISTERED_AGENTS])}"
|
||||
)
|
||||
|
||||
# Auto-detect available agent
|
||||
for agent in _REGISTERED_AGENTS:
|
||||
if agent.is_available():
|
||||
return agent
|
||||
|
||||
return _REGISTERED_AGENTS[0]
|
||||
BIN
scripts/lib/agents/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
scripts/lib/agents/__pycache__/__init__.cpython-314.pyc
Normal file
Binary file not shown.
BIN
scripts/lib/agents/__pycache__/antigravity.cpython-314.pyc
Normal file
BIN
scripts/lib/agents/__pycache__/antigravity.cpython-314.pyc
Normal file
Binary file not shown.
BIN
scripts/lib/agents/__pycache__/base.cpython-314.pyc
Normal file
BIN
scripts/lib/agents/__pycache__/base.cpython-314.pyc
Normal file
Binary file not shown.
BIN
scripts/lib/agents/__pycache__/hermes.cpython-314.pyc
Normal file
BIN
scripts/lib/agents/__pycache__/hermes.cpython-314.pyc
Normal file
Binary file not shown.
25
scripts/lib/agents/antigravity.py
Normal file
25
scripts/lib/agents/antigravity.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import os
|
||||
import shutil
|
||||
from typing import List, Optional
|
||||
from .base import BaseAgent
|
||||
|
||||
|
||||
class AntigravityAgent(BaseAgent):
|
||||
"""Adapter for Google Antigravity CLI (agy)."""
|
||||
|
||||
name = "antigravity"
|
||||
aliases = ["agy"]
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return shutil.which("agy") is not None
|
||||
|
||||
def launch(self, prompt: str, extra_args: Optional[List[str]] = None) -> None:
|
||||
bin_path = shutil.which("agy")
|
||||
if not bin_path:
|
||||
raise FileNotFoundError("Antigravity CLI ('agy') is not found on PATH.")
|
||||
|
||||
cmd = [bin_path, "-i", prompt]
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
|
||||
os.execvp(bin_path, cmd)
|
||||
22
scripts/lib/agents/base.py
Normal file
22
scripts/lib/agents/base.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
class BaseAgent(ABC):
|
||||
"""Abstract base class for AI CLI agents."""
|
||||
|
||||
name: str = ""
|
||||
aliases: List[str] = []
|
||||
|
||||
@abstractmethod
|
||||
def is_available(self) -> bool:
|
||||
"""Check if the agent CLI binary is installed and reachable in PATH."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def launch(self, prompt: str, extra_args: Optional[List[str]] = None) -> None:
|
||||
"""Launch the agent interactively with the pre-seeded prompt."""
|
||||
pass
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__} name={self.name}>"
|
||||
25
scripts/lib/agents/hermes.py
Normal file
25
scripts/lib/agents/hermes.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import os
|
||||
import shutil
|
||||
from typing import List, Optional
|
||||
from .base import BaseAgent
|
||||
|
||||
|
||||
class HermesAgent(BaseAgent):
|
||||
"""Adapter for Hermes Agent CLI."""
|
||||
|
||||
name = "hermes"
|
||||
aliases = ["hermes-agent"]
|
||||
|
||||
def is_available(self) -> bool:
|
||||
return shutil.which("hermes") is not None
|
||||
|
||||
def launch(self, prompt: str, extra_args: Optional[List[str]] = None) -> None:
|
||||
bin_path = shutil.which("hermes")
|
||||
if not bin_path:
|
||||
raise FileNotFoundError("Hermes CLI ('hermes') is not found on PATH.")
|
||||
|
||||
cmd = [bin_path, "chat", "-q", prompt]
|
||||
if extra_args:
|
||||
cmd.extend(extra_args)
|
||||
|
||||
os.execvp(bin_path, cmd)
|
||||
36
scripts/lib/filter_predicates.py
Normal file
36
scripts/lib/filter_predicates.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import runpy
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def normalize_predicate(predicate, epics):
|
||||
column, separator, values = predicate.partition(":")
|
||||
if not separator or column.strip().lower() != "epic":
|
||||
return predicate
|
||||
labels = {}
|
||||
for epic in epics:
|
||||
value = epic.get("value", "").strip()
|
||||
label = epic.get("label", "").strip() or value.replace("-", " ").replace("_", " ").title()
|
||||
if value:
|
||||
labels[value.lower()] = label
|
||||
labels[label.lower()] = label
|
||||
normalized = [labels.get(value.strip().lower(), value.strip()) for value in values.split(",")]
|
||||
return f"Epic:{','.join(normalized)}"
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
update = runpy.run_path(str(Path(__file__).resolve().parents[1] / "update-tasks"))
|
||||
text = Path(sys.argv[1]).read_text(encoding="utf-8")
|
||||
epics = update["read_frontmatter"](text).get("epics", [])
|
||||
for predicate in sys.argv[2:]:
|
||||
print(normalize_predicate(predicate, epics))
|
||||
except (OSError, ValueError) as error:
|
||||
print(f"Error normalizing task filters: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
132
scripts/lib/task_context.py
Normal file
132
scripts/lib/task_context.py
Normal file
@@ -0,0 +1,132 @@
|
||||
import argparse
|
||||
import importlib.machinery
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
from agents import BaseAgent, get_agent, get_default_agent, list_agents
|
||||
|
||||
|
||||
def load_get_task_module():
|
||||
"""Dynamically load scripts/get-task."""
|
||||
repo_root = Path(__file__).resolve().parent.parent.parent
|
||||
get_task_script = repo_root / "scripts" / "get-task"
|
||||
if not get_task_script.exists():
|
||||
raise FileNotFoundError(f"Could not find get-task script at {get_task_script}")
|
||||
loader = importlib.machinery.SourceFileLoader("get_task_mod", str(get_task_script))
|
||||
spec = importlib.util.spec_from_loader("get_task_mod", loader)
|
||||
if spec is None:
|
||||
raise ImportError(f"Could not load spec for {get_task_script}")
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
loader.exec_module(mod)
|
||||
return mod
|
||||
|
||||
|
||||
def fetch_task(task_id: str, tasks_file: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""Retrieve task dictionary using get_task."""
|
||||
mod = load_get_task_module()
|
||||
return mod.get_task(task_id, tasks_file=tasks_file)
|
||||
|
||||
|
||||
def assert_task_status(task: Dict[str, Any], allowed_statuses: List[str], command_name: str) -> None:
|
||||
"""Validate that the task status matches one of the allowed statuses, otherwise error and exit."""
|
||||
current_status = task.get("status", "").strip().lower()
|
||||
allowed_lower = [s.lower() for s in allowed_statuses]
|
||||
|
||||
if current_status not in allowed_lower:
|
||||
if len(allowed_statuses) == 1:
|
||||
req_str = f"'{allowed_statuses[0]}'"
|
||||
else:
|
||||
req_str = " or ".join([f"'{s}'" for s in allowed_statuses])
|
||||
sys.stderr.write(
|
||||
f"Error: Task {task['id']} is in '{task['status']}' status. "
|
||||
f"{command_name.capitalize()} requires status to be {req_str}.\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def build_workflow_prompt(
|
||||
task: Dict[str, Any],
|
||||
workflow_name: str,
|
||||
workflow_objective: str,
|
||||
extra_context: str = "",
|
||||
) -> str:
|
||||
"""Construct a standardized, high-fidelity prompt for an AI agent."""
|
||||
desc = task["description"].strip() if task.get("description") else "(No description provided yet)"
|
||||
subproj_line = f"- Subproject: {task['subproject']}\n" if task.get("subproject") else ""
|
||||
epic_line = f"- Epic: {task.get('epic_label', task['epic'])} ({task['epic']})\n" if task.get("epic") else ""
|
||||
epic_arg = f" --epic={task['epic']}" if task.get("epic") else ""
|
||||
|
||||
prompt = f"""You are {workflow_name} task {task['id']} in the PlanBForGreatJustice repository.
|
||||
|
||||
### Target Task:
|
||||
- ID: {task['id']}
|
||||
- Title: {task['title']}
|
||||
- Project: {task['project']}
|
||||
{subproj_line}{epic_line}- Current Status: {task['status']}
|
||||
- Type: {task['type']}
|
||||
|
||||
### Current Description & Checklist:
|
||||
{desc}
|
||||
|
||||
### Repository Context & Guidelines:
|
||||
1. SSoT & Task Management (GEMINI.md):
|
||||
- You MUST use `./scripts/update-task {task['id']} ...` to update task metadata, description, title, or checklist items.
|
||||
- If this task needs to be split into subtasks, use `./scripts/add-task "Subtask Title" --project={task['project']} --type={task['type']}{epic_arg}`.
|
||||
- Do NOT edit docs/tasks.md manually.
|
||||
2. Architecture Overview:
|
||||
- C++ Story Engine: Engine/PlanB/Engine/ (Actions, Expressions, Sequences, GameState, StoryModel).
|
||||
- Desktop Editor: Editor/App/ (Tauri + Vite + React, Sequence Simulator, Expression Graph Canvas, XML converters).
|
||||
- Content: Content/Episode 1/Episode1/Episode.xml.
|
||||
- Specifications: docs/Schema/ (e.g., PBScript_Specification.md, grammar files).
|
||||
- Legacy Reference: Editor/Sources/ (WPF C# editor for comparison if applicable).
|
||||
3. Objectives for this session:
|
||||
{workflow_objective}
|
||||
"""
|
||||
if extra_context:
|
||||
prompt += f"\n### Additional Context:\n{extra_context}\n"
|
||||
|
||||
prompt += f"\nBegin by reviewing the task, inspecting the relevant code, and collaborating interactively with the user.\n"
|
||||
return prompt
|
||||
|
||||
|
||||
def add_common_agent_arguments(parser: argparse.ArgumentParser) -> None:
|
||||
"""Register reusable CLI flags for agent selection and tasks.md path."""
|
||||
registered = list_agents()
|
||||
agent_names = [a.name for a in registered]
|
||||
|
||||
parser.add_argument(
|
||||
"--agent",
|
||||
choices=agent_names,
|
||||
default=None,
|
||||
help=f"AI agent CLI to use ({', '.join(agent_names)}). Defaults to $TASK_AGENT or first available.",
|
||||
)
|
||||
parser.add_argument("--hermes", action="store_true", help="Shortcut for --agent hermes")
|
||||
parser.add_argument("--agy", action="store_true", help="Shortcut for --agent antigravity")
|
||||
parser.add_argument("-f", "--file", default=None, help="Path to tasks.md (optional)")
|
||||
|
||||
|
||||
def resolve_selected_agent(args: argparse.Namespace) -> BaseAgent:
|
||||
"""Resolve which agent was requested via flags or environment."""
|
||||
if getattr(args, "hermes", False):
|
||||
agent = get_agent("hermes")
|
||||
elif getattr(args, "agy", False):
|
||||
agent = get_agent("antigravity")
|
||||
elif getattr(args, "agent", None):
|
||||
agent = get_agent(args.agent)
|
||||
else:
|
||||
agent = get_default_agent()
|
||||
|
||||
if not agent:
|
||||
sys.stderr.write("Error: Could not resolve requested agent.\n")
|
||||
sys.exit(1)
|
||||
|
||||
if not agent.is_available():
|
||||
sys.stderr.write(
|
||||
f"Error: Selected agent '{agent.name}' is not available on PATH. "
|
||||
f"Please verify installation.\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
return agent
|
||||
Reference in New Issue
Block a user