feat(system): registry glob discovery, drone stderr fix, pyright OOM fix (#38)
Registry discovery (drone, spawn, seedgo) now globs for *_REGISTRY.json with CWD-first walk-up — enabling project isolation without hardcoded filenames. Drone stderr crash fixed (Rich Console). Pyright OOM fixed via pyrightconfig excludes. AIPASS.md simplified for universal use. Global prompt updated with flow commands. DPLAN-003 added for AIPass-as-OS design vision. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
4f87b5c044
commit
8ab43a2cd0
@@ -3,6 +3,8 @@
|
||||
|
||||
**This prompt is your guide.** The patterns shown here are exact. Don't guess command syntax — the examples ARE the API.
|
||||
|
||||
**If a command or workflow seems obvious but isn't documented here, flag it.** Don't silently guess — ask or investigate with `--help`. Missing instructions are a prompt bug, not a knowledge gap.
|
||||
|
||||
**USER NAME:** Patrick
|
||||
|
||||
If no user name is set above, ask on first interaction.
|
||||
@@ -52,6 +54,9 @@ drone systems # List all registered branches
|
||||
drone @seedgo audit aipass # Run standards audit on all branches
|
||||
drone @seedgo standards_query aipass_standards # List all standards (then query by name)
|
||||
drone @prax monitor # Real-time monitoring (interactive)
|
||||
drone @flow create . "Subject" # Create FPLAN (execution plan)
|
||||
drone @flow create . "Subject" dplan # Create DPLAN (design/planning doc)
|
||||
drone @flow list # List active plans
|
||||
```
|
||||
|
||||
## Dispatch — Wake a Branch
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
# AIPass — Project Prompt
|
||||
|
||||
Project-level instructions loaded by Claude Code. Persists in context for the entire conversation.
|
||||
Details live in README, --help, .trinity/ memories, and the Global Prompt (.aipass/aipass_global_prompt.md).
|
||||
Project-level instructions. Persists in context for the entire conversation.
|
||||
|
||||
## Startup
|
||||
|
||||
On any greeting, silently read these files from CWD and run the commands — no narration, no announcing steps. Just do it and respond with the status.
|
||||
|
||||
**Read:** `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `STATUS.local.md`, `README.md`
|
||||
**Run:** `git status`, `drone systems`
|
||||
|
||||
## Navigation
|
||||
|
||||
- 15 branches under `src/aipass/` (+ commons at `src/commons/`, skills at `src/skills/`)
|
||||
- `drone @branch --help` for commands. `drone systems` for branch list. README.md for architecture.
|
||||
**Read:** `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`
|
||||
**Run:** `git status`
|
||||
|
||||
## Memories
|
||||
|
||||
Update `.trinity/` at natural breakpoints, after milestones, and on `/memo`. If compaction hits before you save, it's gone. Details in your branch prompt.
|
||||
|
||||
|
||||
Update `.trinity/` at natural breakpoints, after milestones, and on `/memo`. If compaction hits before you save, it's gone.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Registry Discovery Map — FPLAN-0029 Phase 1
|
||||
|
||||
## Primary Implementations (5)
|
||||
|
||||
| File | Function | Strategy |
|
||||
|------|----------|----------|
|
||||
| `src/aipass/spawn/apps/handlers/registry.py:34-79` | `find_registry(start_path)` | Env var → project markers (.git/pyproject.toml) → walk-up → cwd fallback |
|
||||
| `src/aipass/drone/apps/handlers/registry_handler.py:36-61` | `find_registry()` | Walk-up from __file__ → walk-up from cwd → parents[4] fallback |
|
||||
| `src/aipass/drone/apps/handlers/registry_handler.py:64-81` | `get_registry_path()` | Global override → env var → find_registry() |
|
||||
| `src/commons/apps/handlers/database/db.py:343-378` | `_find_branch_registry()` | Env var AIPASS_ROOT → walk-up (10 limit) → ~/.aipass/ fallback |
|
||||
| `src/commons/apps/handlers/identity/identity_ops.py:33-67` | `_find_branch_registry_path()` | Same as db.py |
|
||||
|
||||
## Local Reimplementations (23+)
|
||||
|
||||
All do walk-up from `__file__` looking for `AIPASS_REGISTRY.json`:
|
||||
|
||||
### ai_mail (4 files)
|
||||
- `apps/handlers/users/branch_detection.py:26-38`
|
||||
- `apps/handlers/registry/read.py:31-41`
|
||||
- `apps/handlers/email/format.py:21-31`
|
||||
- `apps/handlers/dispatch/daemon.py:36-55`
|
||||
|
||||
### memory (3 files)
|
||||
- `apps/handlers/dashboard_push.py:44-54`
|
||||
- `apps/handlers/monitor/detector.py:36-83`
|
||||
- `apps/handlers/monitor/memory_watcher.py:363-385`
|
||||
|
||||
### prax (2 files)
|
||||
- `apps/handlers/registry/reader.py:24-39`
|
||||
- `apps/handlers/dashboard/agent_status_writer.py:33-44`
|
||||
|
||||
### seedgo (3 files)
|
||||
- `apps/handlers/audit/discovery.py:54-62`
|
||||
- `apps/handlers/diagnostics/discovery.py:21-29`
|
||||
- `apps/handlers/readme/readme_ops.py:29-37`
|
||||
|
||||
### + 11 more across other branches
|
||||
|
||||
## Hardcoded String Count
|
||||
100+ references to `"AIPASS_REGISTRY.json"` across codebase.
|
||||
|
||||
## Phase 1 Plan
|
||||
1. Create `commons.registry.find_registry()` — one function, glob for `*_REGISTRY.json`
|
||||
2. All 23+ modules import from commons
|
||||
3. Drone/spawn wrappers call commons internally
|
||||
4. Test: temp dir with TEST_REGISTRY.json → drone systems finds it
|
||||
+7
-1
@@ -2,5 +2,11 @@
|
||||
"extraPaths": ["src"],
|
||||
"pythonVersion": "3.10",
|
||||
"reportMissingImports": "error",
|
||||
"reportAttributeAccessIssue": "error"
|
||||
"reportAttributeAccessIssue": "error",
|
||||
"exclude": [
|
||||
"**/.venv",
|
||||
"**/node_modules",
|
||||
"**/.archive",
|
||||
"**/.sorting_unprocessed"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ When a task belongs to a specialist, send it there. Don't burn context doing the
|
||||
drone @ai_mail send @target "Subject" "Body" --dispatch # Task email
|
||||
drone @ai_mail dispatch wake @target # Wake branch
|
||||
drone @flow create . "Subject" # Create FPLAN
|
||||
drone @flow create . "Subject" dplan # Create DPLAN (dplan template)
|
||||
drone @flow list # Active plans
|
||||
drone systems # All branches
|
||||
```
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# DPLAN-003 Working Directory
|
||||
|
||||
Research, mapping, and planning files for "AIPass as Operating System."
|
||||
|
||||
Parent plan: `AIPass/DPLAN-003_aipass_as_operating_system_2026-03-13.md`
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose | Status |
|
||||
|------|---------|--------|
|
||||
| `registry_discovery_map.md` | Every find_registry() call, file, line number | Pending |
|
||||
| `registry_refactor_plan.md` | Shared commons function design, migration steps | Pending |
|
||||
| `aipass_init_spec.md` | What `aipass init` creates, CLI design, templates | Pending |
|
||||
| `drone_help_spec.md` | `drone aipass help` module design | Pending |
|
||||
| `portability_audit.md` | Full investigation results (session 24) | Done |
|
||||
@@ -0,0 +1,31 @@
|
||||
# Portability Audit — Session 24 Results
|
||||
|
||||
## Summary
|
||||
|
||||
| Tool | Registry Discovery | CWD-Aware | Portable | Hardcoded |
|
||||
|------|-------------------|-----------|----------|-----------|
|
||||
| Drone | Walk-up + env var | No (uses registry) | Yes | Registry filename |
|
||||
| Spawn | Walk-up + env var | No (uses registry) | Partial | Template location |
|
||||
| Prax | Walk-up (no env) | No (sys logs at repo) | Partial | System logs dir |
|
||||
| AI_Mail | Walk-up (no env) | No (inbox per branch) | Yes | Inbox location |
|
||||
| Flow | Walk-up (no env) | Yes (plan creation) | Hybrid | Plan registry |
|
||||
|
||||
## Key Findings
|
||||
|
||||
- All tools use walk-up strategy to find `AIPASS_REGISTRY.json`
|
||||
- Registry-relative path resolution already works (move registry + dirs = works)
|
||||
- `AIPASS_REGISTRY` env var supported by drone and spawn
|
||||
- System logs hardcoded to `{repo_root}/system_logs/`
|
||||
- Spawn templates hardcoded to `{spawn_package}/templates/`
|
||||
- Walk-up doesn't stop at project boundaries — finds nearest registry up the tree
|
||||
|
||||
## The Core Fix
|
||||
|
||||
Change `find_registry()` to:
|
||||
1. Walk up from CWD looking for `*_REGISTRY.json` (glob, not hardcoded name)
|
||||
2. Stop at first match — that's the project boundary
|
||||
3. If none found, return error ("No AIPass project. Run `aipass init`")
|
||||
|
||||
## Source
|
||||
|
||||
Full investigation transcript: background agent session 24, 42 tool calls across drone/spawn/ai_mail/flow/prax.
|
||||
@@ -16,8 +16,12 @@ Standard branch entry point (apps/drone.py pattern).
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from rich.console import Console
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console
|
||||
|
||||
err_console = Console(stderr=True)
|
||||
from aipass.drone.apps.modules import BranchNotFoundError, CommandExecutionError
|
||||
from aipass.drone.apps.modules.discovery import get_help
|
||||
from aipass.drone.apps.modules.resolver import list_branches
|
||||
@@ -159,7 +163,7 @@ def _handle_module(name: str, args: List[str]) -> int:
|
||||
try:
|
||||
result = route_module_command(name, command, cmd_args)
|
||||
except (ImportError, AttributeError) as exc:
|
||||
console.print(f"drone: module @{name} is registered but not available: {exc}", stderr=True)
|
||||
err_console.print(f"drone: module @{name} is registered but not available: {exc}")
|
||||
return 1
|
||||
|
||||
if result.get("stdout"):
|
||||
@@ -184,15 +188,15 @@ def _handle_target(args: List[str]) -> int:
|
||||
try:
|
||||
result = route_command(target)
|
||||
except BranchNotFoundError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
except CommandExecutionError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
if result.stdout:
|
||||
console.print(result.stdout, end="", highlight=False)
|
||||
if result.stderr:
|
||||
console.print(result.stderr, end="", highlight=False, stderr=True)
|
||||
err_console.print(result.stderr, end="", highlight=False)
|
||||
return result.exit_code
|
||||
|
||||
# --help = show help
|
||||
@@ -204,10 +208,10 @@ def _handle_target(args: List[str]) -> int:
|
||||
else:
|
||||
console.print(f"No help available for {target}.")
|
||||
except BranchNotFoundError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
except CommandExecutionError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@@ -225,16 +229,16 @@ def _handle_target(args: List[str]) -> int:
|
||||
interactive=interactive,
|
||||
)
|
||||
except BranchNotFoundError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
except CommandExecutionError as exc:
|
||||
console.print(f"drone: {exc}", stderr=True)
|
||||
err_console.print(f"drone: {exc}")
|
||||
return 1
|
||||
|
||||
if result.stdout:
|
||||
console.print(result.stdout, end="", highlight=False)
|
||||
if result.stderr:
|
||||
console.print(result.stderr, end="", highlight=False, stderr=True)
|
||||
err_console.print(result.stderr, end="", highlight=False)
|
||||
return result.exit_code
|
||||
|
||||
|
||||
@@ -272,8 +276,8 @@ def main() -> int:
|
||||
return _handle_target(args)
|
||||
|
||||
# Unknown command
|
||||
console.print(f"drone: unknown command '{command}'", stderr=True)
|
||||
console.print("Run 'drone --help' for usage.", stderr=True)
|
||||
err_console.print(f"drone: unknown command '{command}'")
|
||||
err_console.print("Run 'drone --help' for usage.")
|
||||
return 1
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"""
|
||||
Handler for registry file operations.
|
||||
|
||||
Handles loading, parsing, and normalizing AIPASS_REGISTRY.json.
|
||||
Handles loading, parsing, and normalizing *_REGISTRY.json files.
|
||||
All file I/O and data transformation for the registry lives here.
|
||||
"""
|
||||
|
||||
@@ -33,8 +33,18 @@ from .exceptions import (
|
||||
_registry_path: Optional[Path] = None
|
||||
|
||||
|
||||
def _first_registry_in(directory: Path) -> Optional[Path]:
|
||||
"""Return the first *_REGISTRY.json in *directory*, or None.
|
||||
|
||||
When multiple matches exist, the alphabetically-first name wins
|
||||
so the result is deterministic across platforms.
|
||||
"""
|
||||
matches = sorted(directory.glob("*_REGISTRY.json"))
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def find_registry() -> Path:
|
||||
"""Find AIPASS_REGISTRY.json by walking up from this file's location.
|
||||
"""Find a *_REGISTRY.json by walking up from this file's location.
|
||||
|
||||
Search order:
|
||||
1. Explicitly set path via set_registry_path()
|
||||
@@ -42,23 +52,33 @@ def find_registry() -> Path:
|
||||
3. Walk up from drone package location
|
||||
4. Walk up from cwd
|
||||
5. Default: package-relative path
|
||||
"""
|
||||
# Walk up from this file (works for pip editable installs)
|
||||
current = Path(__file__).resolve().parent
|
||||
for parent in [current] + list(current.parents):
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
|
||||
# Walk up from cwd (works for regular installs)
|
||||
The first directory that contains any *_REGISTRY.json is treated
|
||||
as the project boundary. If that directory holds more than one
|
||||
match, the alphabetically-first file is returned.
|
||||
"""
|
||||
# Walk up from cwd FIRST — this is where the user is working
|
||||
cwd = Path.cwd()
|
||||
for parent in [cwd] + list(cwd.parents):
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
hit = _first_registry_in(parent)
|
||||
if hit is not None:
|
||||
return hit
|
||||
|
||||
# Fallback — use package-relative path (no filesystem assumptions)
|
||||
return Path(__file__).resolve().parents[4] / "AIPASS_REGISTRY.json"
|
||||
# Walk up from this file (fallback for pip editable installs)
|
||||
current = Path(__file__).resolve().parent
|
||||
for parent in [current] + list(current.parents):
|
||||
hit = _first_registry_in(parent)
|
||||
if hit is not None:
|
||||
return hit
|
||||
|
||||
# Fallback — use package-relative path; glob there too
|
||||
fallback_dir = Path(__file__).resolve().parents[4]
|
||||
hit = _first_registry_in(fallback_dir)
|
||||
if hit is not None:
|
||||
return hit
|
||||
# Ultimate fallback: return a conventional name so the caller
|
||||
# gets a clear "not found" path in the error message.
|
||||
return fallback_dir / "AIPASS_REGISTRY.json"
|
||||
|
||||
|
||||
def get_registry_path() -> Path:
|
||||
@@ -113,7 +133,7 @@ def load_registry() -> Dict[str, Any]:
|
||||
if not registry_path.exists():
|
||||
raise RegistryNotFoundError(
|
||||
f"Registry not found at {registry_path}. "
|
||||
"Create an AIPASS_REGISTRY.json in your project root."
|
||||
"Create a *_REGISTRY.json file in your project root."
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -51,14 +51,20 @@ def _is_branch_private(branch_name: str) -> bool:
|
||||
|
||||
def _find_registry() -> Path:
|
||||
"""
|
||||
Find AIPASS_REGISTRY.json by walking up from this file's location.
|
||||
Works regardless of install path depth.
|
||||
Find *_REGISTRY.json by walking up from this file's location.
|
||||
Uses glob to match any registry file (aligned with drone's registry_handler).
|
||||
"""
|
||||
current = Path(__file__).resolve().parent
|
||||
for parent in [current] + list(current.parents):
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
matches = sorted(parent.glob("*_REGISTRY.json"))
|
||||
if matches:
|
||||
return matches[0]
|
||||
# Fallback: walk up from CWD (matches drone's search order)
|
||||
cwd = Path.cwd()
|
||||
for parent in [cwd] + list(cwd.parents):
|
||||
matches = sorted(parent.glob("*_REGISTRY.json"))
|
||||
if matches:
|
||||
return matches[0]
|
||||
return Path.cwd() / "AIPASS_REGISTRY.json"
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: registry.py
|
||||
# Description: AIPASS_REGISTRY.json CRUD operations
|
||||
# Description: *_REGISTRY.json discovery and CRUD operations
|
||||
# Version: 1.0.0
|
||||
# Created: 2026-03-05
|
||||
# Modified: 2026-03-07
|
||||
# =============================================
|
||||
|
||||
"""AIPASS_REGISTRY.json CRUD operations."""
|
||||
"""*_REGISTRY.json discovery and CRUD operations."""
|
||||
|
||||
import json
|
||||
import os
|
||||
@@ -31,49 +31,56 @@ def _branches_as_list(branches):
|
||||
return []
|
||||
|
||||
|
||||
def _glob_registry(directory):
|
||||
"""Find the first *_REGISTRY.json in a directory (sorted for consistency).
|
||||
|
||||
Args:
|
||||
directory: Path to search in
|
||||
|
||||
Returns:
|
||||
Path to the registry file, or None if not found
|
||||
"""
|
||||
matches = sorted(directory.glob("*_REGISTRY.json"))
|
||||
return matches[0] if matches else None
|
||||
|
||||
|
||||
def find_registry(start_path=None):
|
||||
"""
|
||||
Find AIPASS_REGISTRY.json — consistent with drone's resolution.
|
||||
Find *_REGISTRY.json — walks up from __file__ and start_path/cwd.
|
||||
|
||||
The first *_REGISTRY.json found while walking up IS the project boundary.
|
||||
If multiple exist in the same directory, picks the first alphabetically.
|
||||
|
||||
Priority:
|
||||
1. AIPASS_REGISTRY environment variable
|
||||
2. Project root (directory with pyproject.toml or .git) — walk up from __file__
|
||||
3. Project root — walk up from start_path/cwd
|
||||
4. Walk up from __file__ for any existing registry
|
||||
5. Last resort: cwd
|
||||
2. Walk up from __file__ — first dir containing *_REGISTRY.json
|
||||
3. Walk up from start_path/cwd — first dir containing *_REGISTRY.json
|
||||
4. Last resort: cwd / AIPASS_REGISTRY.json (backwards compat)
|
||||
|
||||
Args:
|
||||
start_path: Directory to start searching from
|
||||
|
||||
Returns:
|
||||
Path to AIPASS_REGISTRY.json
|
||||
Path to *_REGISTRY.json
|
||||
"""
|
||||
# Check environment variable first (same as drone's config.py)
|
||||
env_path = os.environ.get("AIPASS_REGISTRY")
|
||||
if env_path:
|
||||
return Path(env_path)
|
||||
|
||||
# Walk up from package location — find project root first
|
||||
pkg_dir = Path(__file__).resolve().parent
|
||||
for parent in [pkg_dir] + list(pkg_dir.parents):
|
||||
if (parent / "pyproject.toml").exists() or (parent / ".git").exists():
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
# Project root found but no registry — create here
|
||||
return candidate
|
||||
|
||||
# Walk up from start_path or cwd
|
||||
# Walk up from start_path or cwd FIRST — user's location takes priority
|
||||
current = Path(start_path).resolve() if start_path else Path.cwd()
|
||||
for parent in [current] + list(current.parents):
|
||||
if (parent / "pyproject.toml").exists() or (parent / ".git").exists():
|
||||
return parent / "AIPASS_REGISTRY.json"
|
||||
found = _glob_registry(parent)
|
||||
if found:
|
||||
return found
|
||||
|
||||
# Fallback: any existing registry walking up from package
|
||||
# Walk up from package location (fallback for editable installs)
|
||||
pkg_dir = Path(__file__).resolve().parent
|
||||
for parent in [pkg_dir] + list(pkg_dir.parents):
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
found = _glob_registry(parent)
|
||||
if found:
|
||||
return found
|
||||
|
||||
# Last resort: cwd
|
||||
return Path.cwd() / "AIPASS_REGISTRY.json"
|
||||
|
||||
Reference in New Issue
Block a user