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:
AIOSAI
2026-04-10 14:06:52 -07:00
co-authored by @devpulse
parent 119aaea9de
commit 16b33a6d36
30 changed files with 425 additions and 355 deletions
+1 -15
View File
@@ -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)
+30 -26
View File
@@ -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__":
-2
View File
@@ -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]:
+28 -7
View File
@@ -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",
+8 -15
View File
@@ -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):
+10 -10
View File
@@ -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 -4
View File
@@ -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)
+4 -2
View File
@@ -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
+8 -6
View File
@@ -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:
+3 -4
View File
@@ -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)
+49 -6
View File
@@ -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)
+4 -2
View File
@@ -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}
)
+5 -1
View File
@@ -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)
+24 -20
View File
@@ -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__":
+9 -5
View File
@@ -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",
)
+3 -4
View File
@@ -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}}"
+8 -16
View File
@@ -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"])
+12 -24
View File
@@ -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"])