feat(system): S90: local prompt hook injection + prax trigger dead code removal (Vera BUG-4 fix)

Co-Authored-By: @devpulse <devpulse@aipass>
This commit is contained in:
AIOSAI
2026-04-11 18:34:45 -07:00
co-authored by @devpulse
parent b62e9784c1
commit 5931952c2b
4 changed files with 58 additions and 6 deletions
+2 -2
View File
@@ -5,7 +5,7 @@
**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:** 151 passing (6 files, 5/5 modules covered)
**Tests:** 161 passing (6 files, 5/5 modules covered)
**Last Updated:** 2026-04-11
## Usage
@@ -66,7 +66,7 @@ cli/
├── cli_json/ # Auto-created JSON output (three-file pattern)
├── dropbox/ # Inbound file drop
├── logs/ # Branch-level logs
├── tests/ # 151 tests across 6 files
├── tests/ # 161 tests across 6 files
│ ├── test_bootstrap.py # bootstrap.py handler tests
│ ├── test_json_handler.py # json_handler tests
│ ├── test_display.py # display module tests
+24 -1
View File
@@ -412,9 +412,23 @@ def _gitignore() -> str:
def _claude_settings(aipass_home: str | None = None) -> str:
"""Generate .claude/settings.json — minimal hooks for prompt injection.
Installs two UserPromptSubmit hooks:
1. Global prompt — injects .aipass/aipass_global_prompt.md from CWD.
2. Local prompt — walks up from CWD to find .aipass/aipass_local_prompt.md
(branch-level prompt, e.g. inside src/<agent>/).
Args:
aipass_home: Optional AIPass installation root to add as env.AIPASS_HOME.
"""
_local_prompt_cmd = (
"dir=$(pwd); "
"while [ \"$dir\" != \"/\" ]; do "
"if [ -f \"$dir/.aipass/aipass_local_prompt.md\" ]; then "
"cat \"$dir/.aipass/aipass_local_prompt.md\"; break; "
"fi; "
"dir=$(dirname \"$dir\"); "
"done"
)
data: dict = {
"hooks": {
"UserPromptSubmit": [
@@ -426,7 +440,16 @@ def _claude_settings(aipass_home: str | None = None) -> str:
"command": "cat .aipass/aipass_global_prompt.md 2>/dev/null || true",
}
],
}
},
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": _local_prompt_cmd,
}
],
},
]
}
}
+10 -3
View File
@@ -260,6 +260,12 @@ def _handle_init(args: List[str]) -> bool:
success(f"Created {len(result['created_files'])} files")
if result.get("aipass_home"):
console.print()
console.print(f"[bold cyan]AIPASS_HOME:[/bold cyan] [yellow]{result['aipass_home']}[/yellow]")
console.print(f"[dim]For terminal usage, add to your shell profile:[/dim]")
console.print(f" [green]export AIPASS_HOME={result['aipass_home']}[/green]")
json_handler.log_operation("aipass_init", {
"project_name": result["project_name"],
"target": result["target"],
@@ -462,7 +468,7 @@ def _print_init_help():
console.print("[bold cyan]WHAT IT CREATES:[/bold cyan]")
console.print()
files_text = """[bold]Project scaffold (11 items):[/bold]
files_text = """[bold]Project scaffold (12 items):[/bold]
[green]1.[/green] [yellow]{NAME}_REGISTRY.json[/yellow] Project registry with UUID
[green]2.[/green] [yellow].aipass/aipass_global_prompt.md[/yellow] Global prompt (injected every turn)
@@ -472,9 +478,10 @@ def _print_init_help():
[green]6.[/green] [yellow]README.md[/yellow] Getting started guide
[green]7.[/green] [yellow]STATUS.local.md[/yellow] Project status
[green]8.[/green] [yellow].gitignore[/yellow] Standard AIPass ignores
[green]9.[/green] [yellow].claude/settings.json[/yellow] Claude Code hooks
[green]9.[/green] [yellow].claude/settings.json[/yellow] Claude Code hooks + AIPASS_HOME
[green]10.[/green] [yellow]hooks/[/yellow] User hooks directory
[green]11.[/green] [yellow]src/[/yellow] Agent directories live here"""
[green]11.[/green] [yellow]src/[/yellow] Agent directories live here
[green]12.[/green] [yellow].ai_mail.local/inbox.json[/yellow] Empty project mailbox"""
console.print(Panel(files_text, border_style="green", padding=(1, 2), box=box.ROUNDED))
console.print()
+22
View File
@@ -278,6 +278,28 @@ def test_init_project_claude_settings_content(tmp_path):
assert "UserPromptSubmit" in data["hooks"]
def test_init_project_settings_has_two_hooks(tmp_path):
""".claude/settings.json has both global and local prompt hooks."""
target = tmp_path / "proj"
target.mkdir()
init_project(target, project_name="alpha")
settings_path = target / ".claude" / "settings.json"
data = json.loads(settings_path.read_text(encoding="utf-8"))
hooks = data["hooks"]["UserPromptSubmit"]
assert len(hooks) == 2, f"Expected 2 UserPromptSubmit hooks, got {len(hooks)}"
# First hook: global prompt
global_cmd = hooks[0]["hooks"][0]["command"]
assert "aipass_global_prompt.md" in global_cmd
# Second hook: local prompt walk-up
local_cmd = hooks[1]["hooks"][0]["command"]
assert "aipass_local_prompt.md" in local_cmd
assert "dirname" in local_cmd, "Local prompt hook should walk up with dirname"
def test_init_project_global_prompt_content(tmp_path):
"""Global prompt contains project name and AIPass terminology."""
target = tmp_path / "proj"