feat(spawn): rename builder->aipass_framework, {{CITIZEN_CLASS}} placeholder, retire birthright, per-project registry targeting (TDPLAN-0010, FPLAN-0294)

This commit is contained in:
AIOSAI
2026-07-01 08:16:54 -07:00
parent 5a3d01efb1
commit 13463c0ce0
74 changed files with 261 additions and 1182 deletions
+36 -6
View File
@@ -57,14 +57,38 @@ from aipass.spawn.apps.handlers.class_registry import (
from aipass.spawn.apps.handlers.json import json_handler
# Default template location (relative to spawn package root)
DEFAULT_TEMPLATE = Path(__file__).parents[2] / "templates" / "builder"
DEFAULT_TEMPLATE = Path(__file__).parents[2] / "templates" / "aipass_framework"
_PROJECT_MARKERS = (".git", "pyproject.toml", "setup.py", "setup.cfg")
def _find_project_registry(target: Path) -> Path:
"""Walk up from target to find a project root, return its registry path.
Used when the default find_registry returned a registry outside the
target's project (e.g. AIPass's own registry for an external target).
"""
for parent in [target.parent] + list(target.parent.parents):
if any((parent / m).exists() for m in _PROJECT_MARKERS):
return parent / "AIPASS_REGISTRY.json"
if parent == parent.parent:
break
return target.parent / "AIPASS_REGISTRY.json"
_META_TAB_KEYS = {"TODOS_META", "KEY_LEARNINGS_META", "SESSIONS_META", "OBSERVATIONS_META"}
def _load_meta_tabs():
"""Load memory meta-tab values from @memory's renderer. Raises on failure."""
from aipass.memory.apps.handlers.tracking.tab_renderer import render_all_meta_tabs
"""Load memory meta-tab values from @memory's renderer.
Returns empty dict when @memory is unavailable (standalone project).
"""
try:
from aipass.memory.apps.handlers.tracking.tab_renderer import render_all_meta_tabs
except ImportError:
logger.info("[spawn] @memory not available — meta-tab placeholders will be empty")
return {}
tabs = render_all_meta_tabs()
missing = _META_TAB_KEYS - set(tabs or {})
@@ -170,7 +194,7 @@ def _spawn_agent(
profile=None,
template_dir=None,
registry_path=None,
citizen_class="builder",
citizen_class="aipass_framework",
):
"""
Create a new AIPass agent from template.
@@ -183,7 +207,7 @@ def _spawn_agent(
profile: AIPass profile override (default: auto-detect)
template_dir: Custom template directory (default: class-based lookup)
registry_path: Path to AIPASS_REGISTRY.json (default: auto-discover)
citizen_class: Citizen class name (default: "builder")
citizen_class: Citizen class name (default: "aipass_framework")
Returns:
Dict with creation results:
@@ -227,8 +251,13 @@ def _spawn_agent(
branch_lower = normalize_branch_name(folder_name, "lower")
detected_profile = profile or detect_profile(target)
# Determine citizen number from registry
# Determine registry — per-project, never borrow another project's
reg_path = Path(registry_path) if registry_path else find_registry(target.parent)
try:
target.relative_to(reg_path.parent)
except ValueError:
logger.info("[spawn] Target %s outside registry %s — resolving project-local registry", target, reg_path)
reg_path = _find_project_registry(target)
citizen_number = get_next_citizen_number(reg_path)
# Build placeholder replacements
@@ -241,6 +270,7 @@ def _spawn_agent(
purpose=purpose or "New agent - purpose TBD",
profile=detected_profile,
citizen_number=citizen_number,
citizen_class=citizen_class,
meta_tabs=meta_tabs,
)
@@ -74,9 +74,8 @@ def handle_regenerate_registry(args: list[str]) -> int:
"""Parse args and execute template registry regeneration.
Args patterns:
[] -> regenerate builder (default)
["builder"] -> regenerate builder template
["birthright"] -> regenerate birthright template
[] -> regenerate aipass_framework (default)
["aipass_framework"] -> regenerate aipass_framework template
["--all"] -> regenerate all template registries
["--help"] -> show help
@@ -115,9 +114,9 @@ def handle_regenerate_registry(args: list[str]) -> int:
json_handler.log_operation("regenerate_registry_all", data={"classes": list(classes)})
return 1 if had_error else 0
# Single class — default to builder
# Single class — default to aipass_framework
clean_args = [a for a in args if not a.startswith("--")]
class_name = clean_args[0] if clean_args else "builder"
class_name = clean_args[0] if clean_args else "aipass_framework"
available = get_available_classes()
if class_name not in available:
@@ -153,7 +152,7 @@ def _print_help() -> None:
"""Print usage help for regenerate-registry command."""
warning("Usage: drone @spawn regenerate-registry [class_name | --all]")
console.print()
console.print(" [green](no args)[/green] Regenerate builder template registry (default)")
console.print(" [green](no args)[/green] Regenerate aipass_framework template registry (default)")
console.print(" [green]<class>[/green] Regenerate registry for a specific template class")
console.print(" [green]--all[/green] Regenerate registries for all template classes")
console.print()
+3 -6
View File
@@ -79,8 +79,7 @@ def handle_update(args: list[str]) -> int:
Args patterns:
["@branch"] -> update single branch (uses passport's class)
["builder", "--all"] -> update all builder-class branches
["birthright", "--all"] -> update all birthright-class branches
["aipass_framework", "--all"] -> update all aipass_framework-class branches
["--all"] -> BLOCKED (must specify class)
["--dry-run", "@branch"] -> preview mode
["--dry-run", "--all"] -> BLOCKED
@@ -93,8 +92,7 @@ def handle_update(args: list[str]) -> int:
warning("Usage: drone @spawn update <@branch|class --all> [--apply] [--dry-run] [--trace]")
console.print()
console.print(" [green]@branch[/green] Update a single branch (uses its own class)")
console.print(" [green]builder --all[/green] Update all builder-class branches")
console.print(" [green]birthright --all[/green] Update all birthright-class branches")
console.print(" [green]aipass_framework --all[/green] Update all aipass_framework-class branches")
console.print(" [green]--apply[/green] Execute changes (default is preview-only)")
console.print(" [green]--dry-run[/green] Preview changes without modifying files [dim](default)[/dim]")
console.print(" [green]--trace[/green] Enable verbose logging")
@@ -104,8 +102,7 @@ def handle_update(args: list[str]) -> int:
warning("Usage: drone @spawn update <@branch|class --all> [--apply] [--dry-run] [--trace]")
console.print()
console.print(" [green]@branch[/green] Update a single branch (uses its own class)")
console.print(" [green]builder --all[/green] Update all builder-class branches")
console.print(" [green]birthright --all[/green] Update all birthright-class branches")
console.print(" [green]aipass_framework --all[/green] Update all aipass_framework-class branches")
console.print(" [green]--apply[/green] Execute changes (default is preview-only)")
console.print(" [green]--dry-run[/green] Preview changes without modifying files [dim](default)[/dim]")
console.print(" [green]--trace[/green] Enable verbose logging")