feat(system): S86: APLAN fix sweep — 11 CRITICALs resolved, 35+ BUGs fixed, catch-all loggers, README update
Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
+1
-15
@@ -1,24 +1,10 @@
|
||||
# Pre-commit hooks for AIPass
|
||||
# Install: pip install pre-commit && pre-commit install
|
||||
<<<<<<< HEAD
|
||||
# Manual run: pre-commit run --all-files
|
||||
|
||||
repos:
|
||||
# Gitleaks — secret detection
|
||||
=======
|
||||
# Run manually: pre-commit run --all-files
|
||||
|
||||
repos:
|
||||
>>>>>>> 190d7a4 (feat(system): SECURITY: gitleaks config + pre-commit for API key leak prevention)
|
||||
# Gitleaks — secret detection
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.21.2
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
name: gitleaks (secret detection)
|
||||
description: Detect hardcoded secrets using gitleaks
|
||||
entry: gitleaks protect --verbose --redact --staged
|
||||
language: golang
|
||||
pass_filenames: false
|
||||
>>>>>>> 190d7a4 (feat(system): SECURITY: gitleaks config + pre-commit for API key leak prevention)
|
||||
|
||||
@@ -206,39 +206,43 @@ def route_command(command: str, args: List[str], modules: List[Any]) -> bool:
|
||||
|
||||
def main():
|
||||
"""Main entry point - routes commands to modules"""
|
||||
try:
|
||||
# Parse arguments
|
||||
args = sys.argv[1:]
|
||||
|
||||
# Parse arguments
|
||||
args = sys.argv[1:]
|
||||
# Show introspection when run without arguments
|
||||
if len(args) == 0:
|
||||
print_introspection()
|
||||
return 0
|
||||
|
||||
# Show introspection when run without arguments
|
||||
if len(args) == 0:
|
||||
print_introspection()
|
||||
return 0
|
||||
# Show version
|
||||
if args[0] in ['--version', '-V']:
|
||||
console.print("AI_MAIL v1.0.0")
|
||||
return 0
|
||||
|
||||
# Show version
|
||||
if args[0] in ['--version', '-V']:
|
||||
console.print("AI_MAIL v1.0.0")
|
||||
return 0
|
||||
# Show help for explicit help flags
|
||||
if args[0] in ['--help', '-h', 'help']:
|
||||
print_help()
|
||||
return 0
|
||||
|
||||
# Show help for explicit help flags
|
||||
if args[0] in ['--help', '-h', 'help']:
|
||||
print_help()
|
||||
return 0
|
||||
# Command provided - try to route to modules
|
||||
modules = discover_modules()
|
||||
command = args[0]
|
||||
remaining_args = args[1:] if len(args) > 1 else []
|
||||
|
||||
# Command provided - try to route to modules
|
||||
modules = discover_modules()
|
||||
command = args[0]
|
||||
remaining_args = args[1:] if len(args) > 1 else []
|
||||
if not modules:
|
||||
error("No modules found")
|
||||
return 1
|
||||
|
||||
if not modules:
|
||||
error("No modules found")
|
||||
return 1
|
||||
# Route command
|
||||
if route_command(command, remaining_args, modules):
|
||||
return 0
|
||||
else:
|
||||
error(f"Unknown command: {command}")
|
||||
return 1
|
||||
|
||||
# Route command
|
||||
if route_command(command, remaining_args, modules):
|
||||
return 0
|
||||
else:
|
||||
error(f"Unknown command: {command}")
|
||||
except Exception as exc:
|
||||
logger.error('[ai_mail] Unhandled error in main: %s', exc)
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -123,8 +123,6 @@ api/
|
||||
|
||||
### Provides To
|
||||
- All branches -- authenticated external API clients
|
||||
- `@backup` -- Google Drive service (migration from self-contained auth)
|
||||
- `@skills` -- future external API integrations (Telegram, Google services)
|
||||
- System-wide API key management and credential validation
|
||||
|
||||
### Credentials
|
||||
|
||||
@@ -42,7 +42,6 @@ MODULE_VERSION = "1.0.0"
|
||||
CALLER_PATTERNS = {
|
||||
"flow": "flow_json",
|
||||
"prax": "prax_json",
|
||||
"skills": "{category}_json",
|
||||
}
|
||||
|
||||
# =============================================
|
||||
@@ -70,11 +69,6 @@ def get_caller_info() -> Optional[Dict[str, Any]]:
|
||||
result = _detect_prax_caller(frame_path)
|
||||
json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "prax"})
|
||||
return result
|
||||
elif any("skills" in part for part in frame_path.parts):
|
||||
result = _detect_skills_caller(frame_path)
|
||||
json_handler.log_operation("caller_detected", {"caller": result.get("caller_name"), "category": "skills"})
|
||||
return result
|
||||
|
||||
logger.info(f"[{MODULE_NAME}] Could not detect caller from stack trace")
|
||||
return None
|
||||
|
||||
@@ -105,8 +99,6 @@ def detect_caller_category(caller_path: Path) -> str:
|
||||
return "flow"
|
||||
elif "prax" in path_parts:
|
||||
return "prax"
|
||||
elif any("skills" in part for part in path_parts):
|
||||
return "skills"
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
@@ -165,38 +157,6 @@ def _detect_prax_caller(frame_path: Path) -> Dict[str, Any]:
|
||||
return _create_fallback_info(frame_path)
|
||||
|
||||
|
||||
def _detect_skills_caller(frame_path: Path) -> Dict[str, Any]:
|
||||
"""
|
||||
Detect skills module caller from stack frame path.
|
||||
Skills have category subdirectories (e.g., /skills/skills_api/skill.py)
|
||||
"""
|
||||
try:
|
||||
for i, part in enumerate(frame_path.parts):
|
||||
if "skills" in part:
|
||||
skills_path = Path(*frame_path.parts[:i + 2])
|
||||
category = frame_path.parts[i + 1] if i + 1 < len(frame_path.parts) else "skills_api"
|
||||
json_folder_path = skills_path / f"{category}_json"
|
||||
caller_name = frame_path.stem
|
||||
|
||||
logger.info(f"[{MODULE_NAME}] Detected skills caller: {caller_name} (category: {category})")
|
||||
|
||||
return {
|
||||
"caller_name": caller_name,
|
||||
"caller_path": frame_path,
|
||||
"json_folder": json_folder_path,
|
||||
"category": "skills",
|
||||
"skills_category": category,
|
||||
"detection_method": "stack"
|
||||
}
|
||||
|
||||
logger.info(f"[{MODULE_NAME}] Could not find skills directory in path: {frame_path}")
|
||||
return _create_fallback_info(frame_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to detect skills caller: {e}")
|
||||
return _create_fallback_info(frame_path)
|
||||
|
||||
|
||||
def _create_fallback_info(frame_path: Path) -> Dict[str, Any]:
|
||||
"""Create fallback caller info when detection fails."""
|
||||
caller_name = frame_path.stem
|
||||
|
||||
@@ -256,13 +256,17 @@ def extract_response(response: Any) -> Optional[Dict[str, Any]]:
|
||||
return None
|
||||
|
||||
# Extract metadata
|
||||
finish_reason = response.choices[0].finish_reason if hasattr(response.choices[0], 'finish_reason') else None
|
||||
result = {
|
||||
"content": content,
|
||||
"id": response.id if hasattr(response, 'id') else None,
|
||||
"model": response.model if hasattr(response, 'model') else None,
|
||||
"finish_reason": response.choices[0].finish_reason if hasattr(response.choices[0], 'finish_reason') else None
|
||||
"finish_reason": finish_reason
|
||||
}
|
||||
|
||||
if finish_reason == "content_filter":
|
||||
logger.warning("Response was truncated or blocked by content filter")
|
||||
|
||||
logger.info(f"Extracted response - length: {len(content)} chars, id: {result['id']}")
|
||||
return result
|
||||
|
||||
|
||||
@@ -172,9 +172,9 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
Create new caller configuration with defaults
|
||||
|
||||
Creates complete 3-file JSON structure:
|
||||
- openrouter_skill_config.json (API settings)
|
||||
- openrouter_skill_data.json (usage tracking)
|
||||
- openrouter_skill_log.json (operation log)
|
||||
- openrouter_config.json (API settings)
|
||||
- openrouter_data.json (usage tracking)
|
||||
- openrouter_log.json (operation log)
|
||||
|
||||
Args:
|
||||
caller: Name of calling module
|
||||
@@ -189,7 +189,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
# Create config file
|
||||
config_file = json_folder / "openrouter_skill_config.json"
|
||||
config_file = json_folder / "openrouter_config.json"
|
||||
config = get_default_caller_config()
|
||||
|
||||
if not write_json(config_file, config):
|
||||
@@ -199,7 +199,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
logger.info(f"Created config: {config_file.name}")
|
||||
|
||||
# Create data file
|
||||
data_file = json_folder / "openrouter_skill_data.json"
|
||||
data_file = json_folder / "openrouter_data.json"
|
||||
data = get_default_caller_data()
|
||||
|
||||
if write_json(data_file, data):
|
||||
@@ -207,7 +207,7 @@ def create_caller_config(caller: str, json_folder: Path) -> Dict[str, Any]:
|
||||
logger.info(f"Created data: {data_file.name}")
|
||||
|
||||
# Create log file
|
||||
log_file = json_folder / "openrouter_skill_log.json"
|
||||
log_file = json_folder / "openrouter_log.json"
|
||||
log_data = get_default_caller_log()
|
||||
|
||||
if write_json(log_file, log_data):
|
||||
@@ -258,7 +258,7 @@ def ensure_caller_config(caller: str | None = None) -> Dict[str, Any]:
|
||||
return {}
|
||||
|
||||
# Check if config already exists
|
||||
config_file = json_folder / "openrouter_skill_config.json"
|
||||
config_file = json_folder / "openrouter_config.json"
|
||||
|
||||
if config_file.exists():
|
||||
config = read_json(config_file)
|
||||
|
||||
@@ -101,7 +101,7 @@ def cleanup_old_data(data_file_path: Path, retention_days: int = DEFAULT_RETENTI
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Cleanup failed: {e}")
|
||||
raise
|
||||
return 0
|
||||
|
||||
|
||||
def _identify_old_generations(generation_tracking: Dict, cutoff_date: datetime) -> List[str]:
|
||||
|
||||
@@ -49,6 +49,7 @@ GENERATION_ENDPOINT = f"{OPENROUTER_BASE_URL}/generation"
|
||||
# Default configuration values
|
||||
DEFAULT_GENERATION_CHECK_DELAY = 2 # seconds to wait before querying metrics
|
||||
DEFAULT_REQUEST_TIMEOUT = 30 # seconds for HTTP request timeout
|
||||
MAX_GENERATION_TRACKING = 500 # Maximum entries in generation_tracking before trimming
|
||||
|
||||
|
||||
# =============================================
|
||||
@@ -161,15 +162,15 @@ def get_generation_metrics(generation_id: str, api_key: str) -> Optional[Dict[st
|
||||
logger.warning(f"[{MODULE_NAME}] Invalid response structure from OpenRouter for generation {generation_id}")
|
||||
return None
|
||||
|
||||
# Extract metrics from response
|
||||
# Extract metrics from response (use `or 0` to handle explicit None values)
|
||||
metrics = data["data"]
|
||||
result = {
|
||||
"total_cost": float(metrics.get("total_cost", 0)),
|
||||
"tokens_prompt": int(metrics.get("tokens_prompt", 0)),
|
||||
"tokens_completion": int(metrics.get("tokens_completion", 0)),
|
||||
"generation_time": int(metrics.get("generation_time", 0)),
|
||||
"latency": int(metrics.get("latency", 0)),
|
||||
"provider_name": metrics.get("provider_name", "unknown")
|
||||
"total_cost": float(metrics.get("total_cost") or 0),
|
||||
"tokens_prompt": int(metrics.get("tokens_prompt") or 0),
|
||||
"tokens_completion": int(metrics.get("tokens_completion") or 0),
|
||||
"generation_time": int(metrics.get("generation_time") or 0),
|
||||
"latency": int(metrics.get("latency") or 0),
|
||||
"provider_name": metrics.get("provider_name") or "unknown"
|
||||
}
|
||||
|
||||
# Retrieved metrics for generation_id
|
||||
@@ -281,6 +282,20 @@ def store_usage_data(caller: str, model: str, generation_id: str, metrics: Dict[
|
||||
current_data["daily_totals"][today]["cost"] += metrics["total_cost"]
|
||||
current_data["daily_totals"][today]["tokens"] += total_tokens
|
||||
|
||||
# Update monthly totals
|
||||
month = today[:7] # YYYY-MM
|
||||
if "monthly_totals" not in current_data:
|
||||
current_data["monthly_totals"] = {}
|
||||
if month not in current_data["monthly_totals"]:
|
||||
current_data["monthly_totals"][month] = {
|
||||
"requests": 0,
|
||||
"cost": 0.0,
|
||||
"tokens": 0
|
||||
}
|
||||
current_data["monthly_totals"][month]["requests"] += 1
|
||||
current_data["monthly_totals"][month]["cost"] += metrics["total_cost"]
|
||||
current_data["monthly_totals"][month]["tokens"] += total_tokens
|
||||
|
||||
# Store generation details with newest-first ordering
|
||||
new_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
@@ -293,6 +308,12 @@ def store_usage_data(caller: str, model: str, generation_id: str, metrics: Dict[
|
||||
current_tracking = current_data["generation_tracking"]
|
||||
current_data["generation_tracking"] = {generation_id: new_entry, **current_tracking}
|
||||
|
||||
# Trim oldest entries if over the size cap
|
||||
if len(current_data["generation_tracking"]) > MAX_GENERATION_TRACKING:
|
||||
keys = list(current_data["generation_tracking"].keys())
|
||||
for old_key in keys[MAX_GENERATION_TRACKING:]:
|
||||
del current_data["generation_tracking"][old_key]
|
||||
|
||||
# Save updated data with proper wrapper structure
|
||||
data_wrapper = {
|
||||
"module_name": "api_usage",
|
||||
|
||||
@@ -12,9 +12,7 @@ Tests for openrouter.caller — caller detection handler.
|
||||
Tests:
|
||||
- detect_caller_category for flow paths
|
||||
- detect_caller_category for prax paths
|
||||
- detect_caller_category for skills paths
|
||||
- detect_caller_category for unknown paths
|
||||
- detect_caller_category with 'skills' as substring in a part
|
||||
- detect_caller_category for unknown paths (including former skills paths)
|
||||
"""
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
@@ -43,18 +41,13 @@ class TestDetectCallerCategory:
|
||||
path = Path("/home/user/projects/aipass/src/aipass/prax/monitor.py")
|
||||
assert detect_caller_category(path) == "prax"
|
||||
|
||||
def test_skills_exact_part_returns_skills(self):
|
||||
"""Path with exact 'skills' directory should return 'skills'."""
|
||||
def test_skills_path_returns_unknown(self):
|
||||
"""Skills branch was removed — skills paths now return 'unknown'."""
|
||||
path = Path("/home/user/projects/aipass/src/aipass/skills/skills_api/tool.py")
|
||||
assert detect_caller_category(path) == "skills"
|
||||
|
||||
def test_skills_substring_returns_skills(self):
|
||||
"""Path with 'skills' as substring in a part (e.g., 'skills_api') should return 'skills'."""
|
||||
path = Path("/home/user/projects/aipass/src/aipass/modules/skills_custom/handler.py")
|
||||
assert detect_caller_category(path) == "skills"
|
||||
assert detect_caller_category(path) == "unknown"
|
||||
|
||||
def test_unknown_path_returns_unknown(self):
|
||||
"""Path without flow, prax, or skills should return 'unknown'."""
|
||||
"""Path without flow or prax should return 'unknown'."""
|
||||
path = Path("/home/user/projects/aipass/src/aipass/api/apps/handler.py")
|
||||
assert detect_caller_category(path) == "unknown"
|
||||
|
||||
@@ -63,9 +56,9 @@ class TestDetectCallerCategory:
|
||||
path = Path("/home/user/flow/prax/script.py")
|
||||
assert detect_caller_category(path) == "flow"
|
||||
|
||||
def test_prax_takes_priority_over_skills(self):
|
||||
"""If 'prax' appears before a skills part, should return 'prax'."""
|
||||
path = Path("/home/user/prax/skills_module/script.py")
|
||||
def test_prax_in_mixed_path(self):
|
||||
"""'prax' in path should return 'prax' regardless of other parts."""
|
||||
path = Path("/home/user/prax/other_module/script.py")
|
||||
assert detect_caller_category(path) == "prax"
|
||||
|
||||
def test_root_path_returns_unknown(self):
|
||||
|
||||
@@ -46,9 +46,9 @@ def test_create_caller_config_creates_three_files(tmp_path: Path):
|
||||
result = create_caller_config("test_caller", json_folder)
|
||||
|
||||
assert result != {}
|
||||
assert (json_folder / "openrouter_skill_config.json").exists()
|
||||
assert (json_folder / "openrouter_skill_data.json").exists()
|
||||
assert (json_folder / "openrouter_skill_log.json").exists()
|
||||
assert (json_folder / "openrouter_config.json").exists()
|
||||
assert (json_folder / "openrouter_data.json").exists()
|
||||
assert (json_folder / "openrouter_log.json").exists()
|
||||
|
||||
|
||||
def test_create_caller_config_defaults(tmp_path: Path):
|
||||
@@ -57,7 +57,7 @@ def test_create_caller_config_defaults(tmp_path: Path):
|
||||
|
||||
create_caller_config("test_caller", json_folder)
|
||||
|
||||
config = read_json(json_folder / "openrouter_skill_config.json")
|
||||
config = read_json(json_folder / "openrouter_config.json")
|
||||
assert config is not None
|
||||
assert config["config"]["ai_temperature"] == 0.7
|
||||
assert config["config"]["ai_max_tokens"] == 4000
|
||||
@@ -72,7 +72,7 @@ def test_create_caller_config_data_defaults(tmp_path: Path):
|
||||
|
||||
create_caller_config("test_caller", json_folder)
|
||||
|
||||
data = read_json(json_folder / "openrouter_skill_data.json")
|
||||
data = read_json(json_folder / "openrouter_data.json")
|
||||
assert data is not None
|
||||
assert data["data"]["total_requests"] == 0
|
||||
assert data["data"]["successful_requests"] == 0
|
||||
@@ -87,7 +87,7 @@ def test_create_caller_config_log_defaults(tmp_path: Path):
|
||||
|
||||
create_caller_config("test_caller", json_folder)
|
||||
|
||||
log = read_json(json_folder / "openrouter_skill_log.json")
|
||||
log = read_json(json_folder / "openrouter_log.json")
|
||||
assert log is not None
|
||||
assert log["logs"] == []
|
||||
assert log["module_name"] == "openrouter"
|
||||
@@ -143,9 +143,9 @@ def test_ensure_caller_config_provisions_new(mock_detect, tmp_path: Path):
|
||||
result = ensure_caller_config("test_caller")
|
||||
|
||||
assert result != {}
|
||||
assert (json_folder / "openrouter_skill_config.json").exists()
|
||||
assert (json_folder / "openrouter_skill_data.json").exists()
|
||||
assert (json_folder / "openrouter_skill_log.json").exists()
|
||||
assert (json_folder / "openrouter_config.json").exists()
|
||||
assert (json_folder / "openrouter_data.json").exists()
|
||||
assert (json_folder / "openrouter_log.json").exists()
|
||||
|
||||
|
||||
@patch("aipass.api.apps.handlers.openrouter.provision.detect_caller_from_stack")
|
||||
@@ -159,7 +159,7 @@ def test_ensure_caller_config_returns_existing(mock_detect, tmp_path: Path):
|
||||
assert first_result != {}
|
||||
|
||||
# Read the created config and modify it to detect overwrites
|
||||
config_path = json_folder / "openrouter_skill_config.json"
|
||||
config_path = json_folder / "openrouter_config.json"
|
||||
config = read_json(config_path)
|
||||
assert config is not None
|
||||
config["config"]["ai_model"] = "test/modified-model"
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
**Purpose:** Display and output formatting service for AIPass modules. Provides consistent terminal output — headers, success/error/warning messages, section breaks, and operation templates — so every module looks the same without duplicating Rich formatting code.
|
||||
**Module:** `aipass.cli`
|
||||
**Seedgo:** 100%
|
||||
**Tests:** 138 passing (6 files, 5/5 modules covered)
|
||||
**Last Updated:** 2026-04-07
|
||||
**Tests:** 142 passing (6 files, 5/5 modules covered)
|
||||
**Last Updated:** 2026-04-10
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -66,7 +66,7 @@ cli/
|
||||
├── cli_json/ # Auto-created JSON output (three-file pattern)
|
||||
├── dropbox/ # Inbound file drop
|
||||
├── logs/ # Branch-level logs
|
||||
├── tests/ # 138 tests across 6 files
|
||||
├── tests/ # 142 tests across 6 files
|
||||
│ ├── test_bootstrap.py # bootstrap.py handler tests
|
||||
│ ├── test_json_handler.py # json_handler tests
|
||||
│ ├── test_display.py # display module tests
|
||||
@@ -90,6 +90,7 @@ drone @cli aipass # Show aipass subcommands
|
||||
drone @cli aipass init # Bootstrap AIPass project in current dir
|
||||
drone @cli aipass init /path # Bootstrap in target directory
|
||||
drone @cli aipass init /path MyProject # Bootstrap with custom name
|
||||
drone @cli aipass init agent <name> # Create agent in project (routes to spawn)
|
||||
drone @cli aipass init --help # Detailed init usage
|
||||
drone @cli display # Display module introspection
|
||||
drone @cli display demo # Run display function showcase
|
||||
@@ -119,7 +120,7 @@ aipass --help # Via console_scripts entry point
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-04-07*
|
||||
*Last Updated: 2026-04-10*
|
||||
|
||||
---
|
||||
[← Back to AIPass](../../../README.md)
|
||||
|
||||
@@ -14,6 +14,7 @@ Provides display functions for all branches:
|
||||
- success() - Green checkmark + message
|
||||
- error() - Red X + error message
|
||||
- warning() - Yellow warning + message
|
||||
- fatal() - Error + sys.exit(1)
|
||||
- section() - Visual section breaks
|
||||
|
||||
Uses Rich library for beautiful terminal output.
|
||||
@@ -97,7 +98,7 @@ def print_help():
|
||||
CONSOLE.print("Display is the [bold]CLI's universal output service[/bold] that provides:")
|
||||
# RICH FORMATTING TIP: Use [green]✓[/green] for checkmarks in lists
|
||||
CONSOLE.print(" [green]✓[/green] Consistent Rich-formatted output across all branches")
|
||||
CONSOLE.print(" [green]✓[/green] Five core display functions ([green]header, success, error, warning, section[/green])")
|
||||
CONSOLE.print(" [green]✓[/green] Six core display functions ([green]header, success, error, warning, fatal, section[/green])")
|
||||
CONSOLE.print(" [green]✓[/green] Beautiful terminal output with colors, panels, and formatting")
|
||||
CONSOLE.print(" [green]✓[/green] Integration with CLI error handler for advanced error display")
|
||||
CONSOLE.print()
|
||||
@@ -106,7 +107,7 @@ def print_help():
|
||||
# RICH FORMATTING TIP: Tables are powerful for structured data
|
||||
# Create with Table(), add columns, add rows, then print
|
||||
# =========================================================================
|
||||
CONSOLE.print("[bold cyan]PUBLIC API FUNCTIONS (5 total):[/bold cyan]")
|
||||
CONSOLE.print("[bold cyan]PUBLIC API FUNCTIONS (6 total):[/bold cyan]")
|
||||
CONSOLE.print()
|
||||
|
||||
# RICH FORMATTING TIP: Table styling - show_header, header_style, border_style
|
||||
@@ -119,6 +120,7 @@ def print_help():
|
||||
table.add_row("success()", "message, **kwargs", "Success messages with green checkmark + optional details")
|
||||
table.add_row("error()", "message, suggestion=None", "Error messages with red X + optional suggestion")
|
||||
table.add_row("warning()", "message, details=None", "Warning messages with yellow symbol + optional details")
|
||||
table.add_row("fatal()", "message, suggestion=None", "Error message + sys.exit(1) for unrecoverable failures")
|
||||
table.add_row("section()", "title", "Visual section separators with title and line")
|
||||
|
||||
# RICH FORMATTING TIP: Print the table after adding all rows
|
||||
|
||||
@@ -201,7 +201,12 @@ def _handle_init(args: List[str]) -> bool:
|
||||
|
||||
# Parse positional args: [target_dir] [project_name]
|
||||
caller_cwd = os.environ.get("AIPASS_CALLER_CWD", os.getcwd())
|
||||
target = Path(args[0]) if args else Path(caller_cwd)
|
||||
if args:
|
||||
target = Path(args[0])
|
||||
if not target.is_absolute():
|
||||
target = Path(caller_cwd) / target
|
||||
else:
|
||||
target = Path(caller_cwd)
|
||||
project_name = args[1] if len(args) > 1 else None
|
||||
|
||||
try:
|
||||
@@ -210,10 +215,6 @@ def _handle_init(args: List[str]) -> bool:
|
||||
logger.warning("Init validation error: %s", exc)
|
||||
error(str(exc), suggestion="Pass a project name explicitly")
|
||||
sys.exit(1)
|
||||
except FileExistsError as exc:
|
||||
logger.warning("Init target already exists: %s", exc)
|
||||
error(str(exc))
|
||||
sys.exit(1)
|
||||
except OSError as exc:
|
||||
logger.error("Init filesystem error: %s", exc)
|
||||
error(f"Filesystem error: {exc}")
|
||||
@@ -283,11 +284,12 @@ def _handle_init_agent(args: List[str]) -> bool:
|
||||
|
||||
agent_name = args[0]
|
||||
agent_path = f"src/{agent_name}"
|
||||
extra_flags = args[1:]
|
||||
logger.info("Routing 'init agent %s' to drone @spawn create %s", agent_name, agent_path)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["drone", "@spawn", "create", agent_path],
|
||||
["drone", "@spawn", "create", agent_path] + extra_flags,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
|
||||
@@ -60,7 +60,7 @@ def print_help():
|
||||
CONSOLE.print(" Standard operation header with Rich styling")
|
||||
CONSOLE.print(" Example: operation_start('Creating branch', target='/path', type='module')")
|
||||
CONSOLE.print()
|
||||
CONSOLE.print(" [yellow]operation_complete(success=None, results=None, **summary)[/yellow]")
|
||||
CONSOLE.print(" [yellow]operation_complete(**summary)[/yellow]")
|
||||
CONSOLE.print(" Standard completion summary with Rich styling")
|
||||
CONSOLE.print(" Example: operation_complete(created=5, skipped=2, time='3.2s')")
|
||||
CONSOLE.print()
|
||||
@@ -174,16 +174,15 @@ def operation_start(operation: str, **details) -> None:
|
||||
CONSOLE.print()
|
||||
|
||||
|
||||
def operation_complete(success: bool | None = None, **summary) -> None:
|
||||
def operation_complete(**summary) -> None:
|
||||
"""
|
||||
Display standard operation completion template with Rich styling
|
||||
|
||||
Args:
|
||||
success: True if successful, False if errors
|
||||
**summary: Summary statistics
|
||||
|
||||
Example:
|
||||
operation_complete(True, created=5, skipped=2, time='3.2s')
|
||||
operation_complete(created=5, skipped=2, time='3.2s')
|
||||
"""
|
||||
CONSOLE.print()
|
||||
CONSOLE.print("─" * 50)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
**Purpose:** Orchestration hub for the AIPass ecosystem
|
||||
**Module:** `aipass.devpulse`
|
||||
**Status:** Active
|
||||
**Last Updated:** 2026-03-22
|
||||
**Last Updated:** 2026-04-10
|
||||
|
||||
---
|
||||
|
||||
@@ -56,21 +56,25 @@ devpulse/
|
||||
├── .aipass/ # AI context
|
||||
│ └── aipass_local_prompt.md
|
||||
├── .spawn/ # Spawn metadata
|
||||
├── tools/ # Diagnostic scanner suite (20 tools)
|
||||
├── apps/ # Entry point scaffold (minimal — devpulse.py + stubs)
|
||||
├── tools/ # Diagnostic scanner suite (26 tools)
|
||||
├── branch_audits _only/ # Living audit plans for all 11 branches + patrick
|
||||
├── dropbox/ # Incoming files from other branches/users
|
||||
├── docs/ # Tracked documentation
|
||||
├── docs.local/ # Working files (gitignored)
|
||||
├── tests/
|
||||
├── DPLAN-*.md # Active design plans (8-10 at any time)
|
||||
├── CLOSED_PLANS.local.json # Archive of completed plans
|
||||
├── STATUS.local.md # Current work, issues, todos
|
||||
└── README.md
|
||||
```
|
||||
|
||||
DevPulse has no `apps/` directory — it's a **manager** branch, not a builder. It coordinates via dispatch and sub-agents rather than implementing code.
|
||||
DevPulse has a minimal `apps/` scaffold but is primarily a **manager** branch, not a builder. It coordinates via dispatch and sub-agents rather than implementing code.
|
||||
|
||||
---
|
||||
|
||||
## Diagnostic Tools
|
||||
|
||||
DevPulse maintains a suite of 20 standalone diagnostic scanners in `tools/`. Each follows the `{concern}_scanner_v1.py` naming convention and supports `@branch`, `--all`, and `--summary` flags.
|
||||
DevPulse maintains a suite of 26 standalone diagnostic scanners in `tools/`. Each follows the `{concern}_scanner_v1.py` naming convention and supports `@branch`, `--all`, and `--summary` flags.
|
||||
|
||||
### Code Quality
|
||||
| Tool | What it checks |
|
||||
@@ -104,16 +108,53 @@ DevPulse maintains a suite of 20 standalone diagnostic scanners in `tools/`. Eac
|
||||
| `magic_number_scanner_v1.py` | Hardcoded numbers (cross-file consistency) |
|
||||
| `stale_scanner_v1.py` | Outdated terminology (17 tracked keywords) |
|
||||
|
||||
### Infrastructure
|
||||
| Tool | What it checks |
|
||||
|------|---------------|
|
||||
| `log_scanner_v1.py` | Prax log files for errors/warnings |
|
||||
| `key_exposure_scanner_v1.py` | API key exposure patterns in code |
|
||||
| `meta_header_scanner_v1.py` | Module metadata headers compliance |
|
||||
| `scanner_v1.py` | Base scanner framework |
|
||||
|
||||
### Utilities
|
||||
| Tool | What it does |
|
||||
|------|-------------|
|
||||
| `dev_central_to_devpulse.py` | Rename stale terms across files |
|
||||
| `verify_branch.py` | Verify branch structure compliance |
|
||||
| `git_lock_tool.py` | Manage git lock state for PRs |
|
||||
|
||||
**Tool classification:** Hard checks (pass/fail) vs Advisory (flag for investigation). See DPLAN-0030 for full details.
|
||||
|
||||
---
|
||||
|
||||
## Branch Audits
|
||||
|
||||
DevPulse maintains living audit plans for every branch in `branch_audits _only/`. These are comprehensive health assessments created during adversarial audit sessions (S83 Wave 1-3) and continuously updated.
|
||||
|
||||
Each audit tracks:
|
||||
- **CRITICAL** — Must-fix issues (security, data loss, broken core features)
|
||||
- **BUG** — Functional defects
|
||||
- **STALE/DEAD** — Outdated references, dead code
|
||||
- **QUALITY** — Performance, thread safety, architectural debt
|
||||
- **DOCS** — Documentation gaps and inaccuracies
|
||||
|
||||
| Branch | Audit File | Health |
|
||||
|--------|-----------|--------|
|
||||
| drone | DPLAN-0053 | YELLOW (23 BUG) |
|
||||
| seedgo | DPLAN-0084 | GREEN |
|
||||
| prax | DPLAN-0039 | GREEN |
|
||||
| cli | DPLAN-0074 | YELLOW (2 CRITICAL) |
|
||||
| flow | DPLAN-0082 | YELLOW (5 CRITICAL) |
|
||||
| ai_mail | DPLAN-0036 | GREEN |
|
||||
| api | DPLAN-0029 | YELLOW (3 CRITICAL) |
|
||||
| trigger | DPLAN-0075 | YELLOW (1 CRITICAL) |
|
||||
| spawn | DPLAN-0035 | YELLOW (12 BUG) |
|
||||
| memory | DPLAN-0038 | GREEN |
|
||||
| devpulse | DPLAN-0037 | RED |
|
||||
| patrick | DPLAN-0086 | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
@@ -160,7 +201,9 @@ drone @spawn delete @branch # Archive + deregister branch
|
||||
|
||||
DevPulse is a **manager** branch, not a builder. It delegates code tasks to sub-agents and branch agents. Its context window is reserved for coordination, planning, and architecture — not for reading and editing files across the codebase.
|
||||
|
||||
The `tools/` directory is DevPulse's "tool shed" — standalone diagnostic scripts for investigating code quality across all branches. These tools surface patterns and create conversations. They're built for AI consumption: run a scanner, get instant visibility, decide what matters.
|
||||
The `tools/` directory is DevPulse's "tool shed" — 26 standalone diagnostic scripts for investigating code quality across all branches. These tools surface patterns and create conversations. They're built for AI consumption: run a scanner, get instant visibility, decide what matters.
|
||||
|
||||
The `branch_audits _only/` directory is DevPulse's health dashboard — living audit documents for every branch, updated during adversarial audit sessions and fix sweeps. Each audit tracks CRITICALs, BUGs, quality debt, and documentation gaps with current health ratings.
|
||||
|
||||
---
|
||||
[← Back to AIPass](../../../README.md)
|
||||
|
||||
@@ -136,10 +136,12 @@ def show_introspection() -> None:
|
||||
# COMMAND HANDLERS
|
||||
# =============================================================================
|
||||
|
||||
def _cwd_has_registry() -> bool:
|
||||
def _cwd_has_registry(max_depth: int = 10) -> bool:
|
||||
"""Check if CWD is within a project that has a *_REGISTRY.json."""
|
||||
cwd = Path.cwd()
|
||||
for parent in [cwd] + list(cwd.parents):
|
||||
for i, parent in enumerate([cwd] + list(cwd.parents)):
|
||||
if i >= max_depth:
|
||||
break
|
||||
if list(parent.glob("*_REGISTRY.json")):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -18,11 +18,14 @@ from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
import threading
|
||||
from io import StringIO
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.drone.apps.handlers.json import json_handler
|
||||
|
||||
_capture_lock = threading.Lock()
|
||||
|
||||
|
||||
def capture_main(
|
||||
entry_point_module: str,
|
||||
@@ -54,33 +57,35 @@ def capture_main(
|
||||
argv_parts.append(command)
|
||||
argv_parts.extend(args)
|
||||
|
||||
original_argv = sys.argv
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
captured_out = StringIO()
|
||||
captured_err = StringIO()
|
||||
|
||||
try:
|
||||
sys.argv = argv_parts
|
||||
sys.stdout = captured_out
|
||||
sys.stderr = captured_err
|
||||
with _capture_lock:
|
||||
original_argv = sys.argv
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
|
||||
mod = importlib.import_module(entry_point_module)
|
||||
main_fn = getattr(mod, "main")
|
||||
exit_code = main_fn()
|
||||
except SystemExit as exc:
|
||||
exit_code = exc.code if exc.code is not None else 0
|
||||
logger.info("capture_main: SystemExit(%s) from '%s'", exit_code, entry_point_module)
|
||||
except Exception as exc:
|
||||
captured_err.write(str(exc))
|
||||
exit_code = 1
|
||||
logger.warning(
|
||||
"capture_main: exception from '%s': %s", entry_point_module, exc
|
||||
)
|
||||
finally:
|
||||
sys.argv = original_argv
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
try:
|
||||
sys.argv = argv_parts
|
||||
sys.stdout = captured_out
|
||||
sys.stderr = captured_err
|
||||
|
||||
mod = importlib.import_module(entry_point_module)
|
||||
main_fn = getattr(mod, "main")
|
||||
exit_code = main_fn()
|
||||
except SystemExit as exc:
|
||||
exit_code = exc.code if exc.code is not None else 0
|
||||
logger.info("capture_main: SystemExit(%s) from '%s'", exit_code, entry_point_module)
|
||||
except Exception as exc:
|
||||
captured_err.write(str(exc))
|
||||
exit_code = 1
|
||||
logger.warning(
|
||||
"capture_main: exception from '%s': %s", entry_point_module, exc
|
||||
)
|
||||
finally:
|
||||
sys.argv = original_argv
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
result = {
|
||||
"stdout": captured_out.getvalue(),
|
||||
|
||||
@@ -82,6 +82,15 @@ def _load_external_modules() -> dict[str, _ExternalModuleConfig]:
|
||||
_EXTERNAL_MODULES: dict[str, _ExternalModuleConfig] = _load_external_modules()
|
||||
|
||||
|
||||
def refresh_external_modules() -> None:
|
||||
"""Reload external module declarations from routing_config.json.
|
||||
|
||||
Call after modifying routing_config.json at runtime.
|
||||
"""
|
||||
global _EXTERNAL_MODULES
|
||||
_EXTERNAL_MODULES = _load_external_modules()
|
||||
|
||||
|
||||
@dataclass
|
||||
class ModuleInfo:
|
||||
"""Metadata about a registered module."""
|
||||
@@ -163,6 +172,9 @@ def route_module_command(
|
||||
mod = importlib.import_module(adapter_path)
|
||||
handler = getattr(mod, "handle_command")
|
||||
result = handler(command, args)
|
||||
# Internal modules may return bool (standard) instead of dict (adapter)
|
||||
if isinstance(result, bool):
|
||||
result = {"stdout": "", "stderr": "", "exit_code": 0 if result else 1}
|
||||
json_handler.log_operation(
|
||||
"route_module_command", {"module": name, "command": command}
|
||||
)
|
||||
|
||||
@@ -225,4 +225,8 @@ Examples:
|
||||
return 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
try:
|
||||
sys.exit(main())
|
||||
except Exception as exc:
|
||||
logger.error("Unhandled error in main: %s", exc)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -226,32 +226,36 @@ def print_help() -> None:
|
||||
|
||||
def main() -> int:
|
||||
"""Main entry point - routes to modules."""
|
||||
modules = discover_modules()
|
||||
args = sys.argv[1:]
|
||||
try:
|
||||
modules = discover_modules()
|
||||
args = sys.argv[1:]
|
||||
|
||||
# No args → introspection (discovery mode)
|
||||
if not args:
|
||||
print_introspection()
|
||||
return 0
|
||||
# No args → introspection (discovery mode)
|
||||
if not args:
|
||||
print_introspection()
|
||||
return 0
|
||||
|
||||
# Help flag → full help with usage
|
||||
if args[0] in ["--help", "-h", "help"]:
|
||||
print_help()
|
||||
return 0
|
||||
# Help flag → full help with usage
|
||||
if args[0] in ["--help", "-h", "help"]:
|
||||
print_help()
|
||||
return 0
|
||||
|
||||
if args[0] in ["--version", "-V"]:
|
||||
console.print(f"seedgo v{VERSION}")
|
||||
return 0
|
||||
if args[0] in ["--version", "-V"]:
|
||||
console.print(f"seedgo v{VERSION}")
|
||||
return 0
|
||||
|
||||
command = args[0]
|
||||
remaining = args[1:] if len(args) > 1 else []
|
||||
command = args[0]
|
||||
remaining = args[1:] if len(args) > 1 else []
|
||||
|
||||
# Route to modules
|
||||
if route_command(command, remaining, modules):
|
||||
return 0
|
||||
# Route to modules
|
||||
if route_command(command, remaining, modules):
|
||||
return 0
|
||||
|
||||
error(f"Unknown command: {command}", suggestion="Run 'seedgo --help' for usage")
|
||||
return 1
|
||||
error(f"Unknown command: {command}", suggestion="Run 'seedgo --help' for usage")
|
||||
return 1
|
||||
except Exception as exc:
|
||||
logger.error('[seedgo] Unhandled error in main: %s', exc)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -26,9 +26,6 @@ from aipass.spawn.apps.handlers.registry import (
|
||||
)
|
||||
from aipass.spawn.apps.handlers.json import json_handler
|
||||
|
||||
# Repo root — resolved from spawn package location
|
||||
_REPO_ROOT = Path(__file__).parents[5] # handlers/apps/spawn/aipass/src/AIPass
|
||||
|
||||
# Branches that cannot be deleted (critical infrastructure)
|
||||
_PROTECTED_BRANCHES = {"spawn", "devpulse", "drone"}
|
||||
|
||||
@@ -78,6 +75,7 @@ def delete_branch(
|
||||
|
||||
# 1. Resolve branch path from registry
|
||||
registry_path = find_registry()
|
||||
project_root = registry_path.parent
|
||||
registry = load_registry(registry_path)
|
||||
branch_entry = None
|
||||
branch_dir = None
|
||||
@@ -87,7 +85,7 @@ def delete_branch(
|
||||
branch_entry = entry
|
||||
rel_path = entry.get("path", "")
|
||||
if rel_path:
|
||||
branch_dir = (_REPO_ROOT / rel_path).resolve()
|
||||
branch_dir = (project_root / rel_path).resolve()
|
||||
break
|
||||
|
||||
if branch_entry is None:
|
||||
@@ -131,7 +129,7 @@ def delete_branch(
|
||||
|
||||
# 4. Dry run — report and return
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
archive_dir = _REPO_ROOT / ".archive" / "deleted_branches" / f"{branch_name}_{timestamp}"
|
||||
archive_dir = project_root / ".archive" / "deleted_branches" / f"{branch_name}_{timestamp}"
|
||||
|
||||
if dry_run:
|
||||
logger.info(f"[delete] Dry run: would archive {branch_name} to {archive_dir}")
|
||||
@@ -151,6 +149,12 @@ def delete_branch(
|
||||
except Exception as exc:
|
||||
msg = f"Failed to create archive: {exc}"
|
||||
logger.error(f"[delete] {msg}")
|
||||
# Clean up partial archive to avoid stale leftovers
|
||||
if archive_dir.exists():
|
||||
try:
|
||||
shutil.rmtree(str(archive_dir))
|
||||
except Exception as cleanup_exc:
|
||||
logger.warning("[delete] Failed to clean up partial archive %s: %s", archive_dir, cleanup_exc)
|
||||
return {
|
||||
"branch": branch_name,
|
||||
"success": False,
|
||||
|
||||
@@ -23,6 +23,8 @@ SKIP_NAMES = {"__pycache__", ".git", ".template_registry.json"}
|
||||
|
||||
def ensure_directory(path):
|
||||
"""Create directory and parents if they don't exist."""
|
||||
if path is None:
|
||||
raise ValueError("ensure_directory received None path — caller passed invalid target")
|
||||
Path(path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
|
||||
|
||||
@@ -98,9 +98,14 @@ def grant_passport(
|
||||
# Regenerate template registry
|
||||
regenerate_template_registry(target)
|
||||
|
||||
# Register in AIPASS_REGISTRY.json
|
||||
# Register in AIPASS_REGISTRY.json (store relative path for portability)
|
||||
try:
|
||||
registry_branch_path = str(target.relative_to(reg_path.parent))
|
||||
except ValueError:
|
||||
logger.warning("[passport] Cannot relativize path %s to registry %s, storing absolute", target, reg_path.parent)
|
||||
registry_branch_path = str(target)
|
||||
registry_updated = add_to_registry(
|
||||
reg_path, branch_upper, str(target), detected_profile,
|
||||
reg_path, branch_upper, registry_branch_path, detected_profile,
|
||||
f"@{branch_lower}", purpose or "Birthright citizen - purpose TBD",
|
||||
)
|
||||
|
||||
|
||||
@@ -36,8 +36,6 @@ from aipass.spawn.apps.handlers.placeholders import build_replacements_dict, rep
|
||||
from aipass.spawn.apps.handlers.registry import find_registry, load_registry, _branches_as_list
|
||||
from aipass.spawn.apps.handlers.json import json_handler
|
||||
|
||||
# Repo root — resolved from spawn package location
|
||||
_REPO_ROOT = Path(__file__).parents[5] # handlers/apps/spawn/aipass/src/AIPass
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -347,9 +345,10 @@ def _resolve_branch_path(branch_name: str) -> Path | None:
|
||||
"""Resolve a branch name to its absolute directory path via the registry.
|
||||
|
||||
Tries both the exact name and common case variants.
|
||||
Registry paths are relative to repo root.
|
||||
Registry paths are relative to registry parent (project root).
|
||||
"""
|
||||
registry_path = find_registry()
|
||||
project_root = registry_path.parent
|
||||
registry = load_registry(registry_path)
|
||||
|
||||
for branch in _branches_as_list(registry.get("branches", [])):
|
||||
@@ -357,7 +356,7 @@ def _resolve_branch_path(branch_name: str) -> Path | None:
|
||||
if reg_name.lower() == branch_name.lower():
|
||||
rel_path = branch.get("path", "")
|
||||
if rel_path:
|
||||
return (_REPO_ROOT / rel_path).resolve()
|
||||
return (project_root / rel_path).resolve()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -1,350 +1,390 @@
|
||||
{
|
||||
"metadata": {
|
||||
"description": "Template registry for tracking files",
|
||||
"generated": true
|
||||
"version": "1.0.0",
|
||||
"last_updated": "2026-04-10",
|
||||
"description": "Template file tracking registry for ID-based updates"
|
||||
},
|
||||
"files": {
|
||||
"f001": {
|
||||
"path": ".ai_mail.local/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "49299c242a018d73",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "49299c242a01",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f002": {
|
||||
"path": ".ai_mail.local/inbox.json",
|
||||
"name": "inbox.json",
|
||||
"content_hash": "c9702fe2cc21b748",
|
||||
"content_hash": "c9702fe2cc21",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f003": {
|
||||
"path": ".aipass/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "f42d87684fdfb1af",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "f42d87684fdf",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f004": {
|
||||
"path": ".aipass/aipass_local_prompt.md",
|
||||
"name": "aipass_local_prompt.md",
|
||||
"content_hash": "5f8e2a3859a99906",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "5f8e2a3859a9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f005": {
|
||||
"path": ".archive/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "93d3fcb74f234b1d",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "93d3fcb74f23",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f006": {
|
||||
"path": ".claude/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "adb0ce8c53c1da2d",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "adb0ce8c53c1",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f007": {
|
||||
"path": ".claude/settings.local.json",
|
||||
"name": "settings.local.json",
|
||||
"content_hash": "eacf065629cd9c03",
|
||||
"content_hash": "eacf065629cd",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f008": {
|
||||
"path": ".gitignore",
|
||||
"name": ".gitignore",
|
||||
"content_hash": "841dedb922da7ddb",
|
||||
"content_hash": "841dedb922da",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f009": {
|
||||
"path": ".seedgo/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "ea03468bbf16f248",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "ea03468bbf16",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f010": {
|
||||
"path": ".seedgo/bypass.json",
|
||||
"name": "bypass.json",
|
||||
"content_hash": "0ac90a35515b35c2",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "0ac90a35515b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f024": {
|
||||
"path": ".spawn/.registry_ignore.json",
|
||||
"name": ".registry_ignore.json",
|
||||
"content_hash": "34f5e7ff7e01",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f043": {
|
||||
"path": ".spawn/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e22ad5337efd",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f011": {
|
||||
"path": ".trinity/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "f461c9b16fc566a0",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "f461c9b16fc5",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f012": {
|
||||
"path": ".trinity/local.json",
|
||||
"name": "local.json",
|
||||
"content_hash": "f4e62d2d12bada2e",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "f4e62d2d12ba",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f013": {
|
||||
"path": ".trinity/observations.json",
|
||||
"name": "observations.json",
|
||||
"content_hash": "25b957960a5939a3",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "25b957960a59",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f014": {
|
||||
"path": ".trinity/passport.json",
|
||||
"name": "passport.json",
|
||||
"content_hash": "04c2ead2a4284027",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "9b1d3a691272",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f015": {
|
||||
"path": "CLAUDE.md",
|
||||
"name": "CLAUDE.md",
|
||||
"content_hash": "e24c75348c4faa57",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "e24c75348c4f",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f016": {
|
||||
"path": "DASHBOARD.local.json",
|
||||
"name": "DASHBOARD.local.json",
|
||||
"content_hash": "88360d943d4bacb6",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "88360d943d4b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f017": {
|
||||
"path": "README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "ad99517a50f96ba7",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "ad99517a50f9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f018": {
|
||||
"path": "STATUS.local.md",
|
||||
"name": "STATUS.local.md",
|
||||
"content_hash": "059295ad4d4e62eb",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "059295ad4d4e",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f019": {
|
||||
"path": "apps/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "92a956009e0e22dc",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "92a956009e0e",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f020": {
|
||||
"path": "apps/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "41b011f487af14dc",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "41b011f487af",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f021": {
|
||||
"path": "apps/handlers/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "2e4f4a0c1b474ffa",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "2e4f4a0c1b47",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f022": {
|
||||
"path": "apps/handlers/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "55afc5eddbe04134",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "55afc5eddbe0",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f023": {
|
||||
"path": "apps/modules/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "a4cf0a8e3b4ff993",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "a4cf0a8e3b4f",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f024": {
|
||||
"f044": {
|
||||
"path": "apps/modules/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc1c14",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f025": {
|
||||
"path": "apps/plugins/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "d1e4e2b98c384ca2",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f026": {
|
||||
"path": "apps/plugins/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc1c14",
|
||||
"content_hash": "d1e4e2b98c38",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f027": {
|
||||
"path": "apps/{{BRANCH}}.py",
|
||||
"name": "{{BRANCH}}.py",
|
||||
"content_hash": "fc71b424c10bb172",
|
||||
"content_hash": "e3893ff95aa5",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f028": {
|
||||
"path": "artifacts/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "de20d11e5cfd3586",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"f029": {
|
||||
"path": "artifacts/birth_certificate.json",
|
||||
"name": "birth_certificate.json",
|
||||
"content_hash": "1ec401f4e397eec7",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "de20d11e5cfd",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f030": {
|
||||
"path": "docs/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "2434da568727499d",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "2434da568727",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f031": {
|
||||
"path": "docs.local/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "c19d8872ea2c2c25",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "c19d8872ea2c",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f032": {
|
||||
"path": "docs.local/sub_agent_drops/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e3e5a6b9c9c51817",
|
||||
"content_hash": "e3e5a6b9c9c5",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f033": {
|
||||
"path": "dropbox/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "9e1e9b71f93b4cde",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "9e1e9b71f93b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f034": {
|
||||
"path": "logs/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "4ca207af6bd3a648",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "4ca207af6bd3",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f035": {
|
||||
"path": "pytest.ini",
|
||||
"name": "pytest.ini",
|
||||
"content_hash": "7b39ba7bca4025c5",
|
||||
"content_hash": "7b39ba7bca40",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f036": {
|
||||
"path": "templates/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "73b020b003f900e5",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "73b020b003f9",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f037": {
|
||||
"path": "tests/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "c157895c9b27d3ba",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "c157895c9b27",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f038": {
|
||||
"path": "tests/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "881f06bb6574d2f7",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "881f06bb6574",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f039": {
|
||||
"path": "tests/conftest.py",
|
||||
"name": "conftest.py",
|
||||
"content_hash": "5d98b049957b24a4",
|
||||
"content_hash": "5d98b049957b",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f040": {
|
||||
"path": "tools/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "3c7eaedb16ac2695",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "3c7eaedb16ac",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f041": {
|
||||
"path": "{{BRANCH}}_json/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "e64fa555e7b8e20f",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "e64fa555e7b8",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f042": {
|
||||
"path": "{{BRANCH}}_json/custom_config/README.md",
|
||||
"name": "README.md",
|
||||
"content_hash": "28e9ae3735636611",
|
||||
"has_branch_placeholder": true
|
||||
"content_hash": "28e9ae373563",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f029": {
|
||||
"path": "artifacts/birth_certificate.json",
|
||||
"name": "birth_certificate.json",
|
||||
"content_hash": "0b6e4319781e",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"f026": {
|
||||
"path": "apps/plugins/__init__.py",
|
||||
"name": "__init__.py",
|
||||
"content_hash": "e3b0c44298fc",
|
||||
"has_branch_placeholder": false
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"d001": {
|
||||
"path": ".ai_mail.local",
|
||||
"name": ".ai_mail.local"
|
||||
"name": ".ai_mail.local",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d002": {
|
||||
"path": ".ai_mail.local/sent",
|
||||
"name": "sent"
|
||||
"name": "sent",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d003": {
|
||||
"path": ".aipass",
|
||||
"name": ".aipass"
|
||||
"name": ".aipass",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d004": {
|
||||
"path": ".archive",
|
||||
"name": ".archive"
|
||||
"name": ".archive",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d005": {
|
||||
"path": ".claude",
|
||||
"name": ".claude"
|
||||
"name": ".claude",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d006": {
|
||||
"path": ".seedgo",
|
||||
"name": ".seedgo"
|
||||
"name": ".seedgo",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d023": {
|
||||
"path": ".spawn",
|
||||
"name": ".spawn",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d007": {
|
||||
"path": ".trinity",
|
||||
"name": ".trinity"
|
||||
"name": ".trinity",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d008": {
|
||||
"path": "apps",
|
||||
"name": "apps"
|
||||
"name": "apps",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d009": {
|
||||
"path": "apps/handlers",
|
||||
"name": "handlers"
|
||||
"name": "handlers",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d010": {
|
||||
"path": "apps/modules",
|
||||
"name": "modules"
|
||||
"name": "modules",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d011": {
|
||||
"path": "apps/plugins",
|
||||
"name": "plugins"
|
||||
"name": "plugins",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d012": {
|
||||
"path": "artifacts",
|
||||
"name": "artifacts"
|
||||
"name": "artifacts",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d013": {
|
||||
"path": "docs",
|
||||
"name": "docs"
|
||||
"name": "docs",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d014": {
|
||||
"path": "docs.local",
|
||||
"name": "docs.local"
|
||||
"name": "docs.local",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d015": {
|
||||
"path": "docs.local/sub_agent_drops",
|
||||
"name": "sub_agent_drops"
|
||||
"name": "sub_agent_drops",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d016": {
|
||||
"path": "dropbox",
|
||||
"name": "dropbox"
|
||||
"name": "dropbox",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d017": {
|
||||
"path": "logs",
|
||||
"name": "logs"
|
||||
"name": "logs",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d018": {
|
||||
"path": "templates",
|
||||
"name": "templates"
|
||||
"name": "templates",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d019": {
|
||||
"path": "tests",
|
||||
"name": "tests"
|
||||
"name": "tests",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d020": {
|
||||
"path": "tools",
|
||||
"name": "tools"
|
||||
"name": "tools",
|
||||
"has_branch_placeholder": false
|
||||
},
|
||||
"d021": {
|
||||
"path": "{{BRANCH}}_json",
|
||||
"name": "{{BRANCH}}_json"
|
||||
"name": "{{BRANCH}}_json",
|
||||
"has_branch_placeholder": true
|
||||
},
|
||||
"d022": {
|
||||
"path": "{{BRANCH}}_json/custom_config",
|
||||
"name": "custom_config"
|
||||
"name": "custom_config",
|
||||
"has_branch_placeholder": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"identity": {
|
||||
"citizen_class": "builder",
|
||||
"role": "{{ROLE}}",
|
||||
"purpose": "",
|
||||
"purpose": "{{PURPOSE_BRIEF}}",
|
||||
"what_i_do": [],
|
||||
"what_i_dont_do": []
|
||||
},
|
||||
|
||||
@@ -32,7 +32,7 @@ def discover_modules() -> List[Any]:
|
||||
if file_path.name.startswith("_"):
|
||||
continue
|
||||
|
||||
module_name = f"apps.modules.{file_path.stem}"
|
||||
module_name = f"aipass.{{BRANCH}}.apps.modules.{file_path.stem}"
|
||||
|
||||
try:
|
||||
module = importlib.import_module(module_name)
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"creator": "SYSTEM",
|
||||
"owner": "{{BRANCHNAME}}",
|
||||
"rarity": "unique",
|
||||
"description": "Official birth certificate for {{BRANCHNAME}}. Citizen #{{CITIZEN_NUMBER}}, registered using '{{PROFILE}}' template. Purpose: {{PURPOSE_BRIEF}}",
|
||||
"description": "Official birth certificate for {{BRANCHNAME}}. Citizen #{{CITIZEN_NUMBER}}, builder class. Purpose: {{PURPOSE_BRIEF}}",
|
||||
"metadata": {
|
||||
"citizen_number": "{{CITIZEN_NUMBER}}",
|
||||
"template": "{{PROFILE}}",
|
||||
"citizen_class": "builder",
|
||||
"purpose": "{{PURPOSE_BRIEF}}"
|
||||
},
|
||||
"created_at": "{{DATE}}"
|
||||
|
||||
@@ -119,8 +119,7 @@ class TestDeleteBranch:
|
||||
"""Successful delete should archive the branch and remove from registry."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("test_api", confirm=False)
|
||||
|
||||
@@ -146,8 +145,7 @@ class TestDeleteBranch:
|
||||
"""Cannot delete spawn (self-protection)."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("spawn", confirm=False)
|
||||
|
||||
@@ -158,8 +156,7 @@ class TestDeleteBranch:
|
||||
"""Cannot delete devpulse (orchestration hub protection)."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("devpulse", confirm=False)
|
||||
|
||||
@@ -170,8 +167,7 @@ class TestDeleteBranch:
|
||||
"""Cannot delete drone (routing infrastructure protection)."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("drone", confirm=False)
|
||||
|
||||
@@ -182,8 +178,7 @@ class TestDeleteBranch:
|
||||
"""Dry run should NOT delete or archive anything."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("test_api", confirm=False, dry_run=True)
|
||||
|
||||
@@ -202,8 +197,7 @@ class TestDeleteBranch:
|
||||
"""Deleting a branch not in registry should fail gracefully."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = delete_branch("nonexistent", confirm=False)
|
||||
|
||||
@@ -214,8 +208,7 @@ class TestDeleteBranch:
|
||||
"""Cancelling confirmation should not delete."""
|
||||
from aipass.spawn.apps.handlers.delete_ops import delete_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry), \
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry), \
|
||||
patch("builtins.input", return_value="n"):
|
||||
|
||||
result = delete_branch("test_api", confirm=True)
|
||||
@@ -736,8 +729,7 @@ class TestHandleDelete:
|
||||
"""handle_delete should reject protected branches."""
|
||||
from aipass.spawn.apps.modules.delete import handle_delete
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops._REPO_ROOT", repo_root), \
|
||||
patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
with patch("aipass.spawn.apps.handlers.delete_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = handle_delete(["--yes", "@spawn"])
|
||||
|
||||
|
||||
@@ -201,8 +201,7 @@ class TestUpdateBranch:
|
||||
assert not meta_path.exists()
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch")
|
||||
|
||||
@@ -220,8 +219,7 @@ class TestUpdateBranch:
|
||||
dashboard_before = (branch_dir / "DASHBOARD.local.json").read_text()
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch", dry_run=True)
|
||||
|
||||
@@ -248,8 +246,7 @@ class TestUpdateBranch:
|
||||
original_content = branch_py_content
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch")
|
||||
|
||||
@@ -282,8 +279,7 @@ class TestUpdateBranch:
|
||||
reg_path.write_text(json.dumps(reg, indent=2) + "\n")
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch")
|
||||
|
||||
@@ -302,8 +298,7 @@ class TestUpdateBranch:
|
||||
from aipass.spawn.apps.handlers.update_ops import update_branch
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("nonexistent_branch")
|
||||
|
||||
@@ -329,8 +324,7 @@ class TestUpdateBranch:
|
||||
reg_path.write_text(json.dumps(reg, indent=2) + "\n")
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch")
|
||||
|
||||
@@ -373,8 +367,7 @@ class TestUpdateBranch:
|
||||
save_branch_meta(branch_dir, meta)
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = update_branch("test_branch")
|
||||
|
||||
@@ -398,8 +391,7 @@ class TestUpdateAll:
|
||||
from aipass.spawn.apps.handlers.update_ops import update_all
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
results = update_all()
|
||||
|
||||
@@ -434,8 +426,7 @@ class TestUpdateAll:
|
||||
mock_registry.write_text(json.dumps(reg, indent=2) + "\n")
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
results = update_all()
|
||||
|
||||
@@ -459,8 +450,7 @@ class TestHandleUpdate:
|
||||
from aipass.spawn.apps.modules.update import handle_update
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = handle_update(["@test_branch"])
|
||||
|
||||
@@ -471,8 +461,7 @@ class TestHandleUpdate:
|
||||
from aipass.spawn.apps.modules.update import handle_update
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = handle_update(["--dry-run", "@test_branch"])
|
||||
|
||||
@@ -493,8 +482,7 @@ class TestHandleUpdate:
|
||||
from aipass.spawn.apps.modules.update import handle_update
|
||||
|
||||
with patch("aipass.spawn.apps.handlers.update_ops.get_template_dir", return_value=template_dir), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry), \
|
||||
patch("aipass.spawn.apps.handlers.update_ops._REPO_ROOT", tmp_path):
|
||||
patch("aipass.spawn.apps.handlers.update_ops.find_registry", return_value=mock_registry):
|
||||
|
||||
result = handle_update(["builder", "--all"])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user