Merge pull request #505 from AIOSAI/work/system

fix(setup+cli): wire missing hooks (#498) + remove vestigial hooks/ dir (#497)
This commit is contained in:
AIPass
2026-05-03 09:37:05 -07:00
committed by GitHub
4 changed files with 18 additions and 26 deletions
+5
View File
@@ -544,6 +544,8 @@ settings["hooks"] = {
"PreToolUse": [
{"matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task",
"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/tool_use_sound.py"}]},
{"matcher": "Edit|MultiEdit|Write|NotebookEdit",
"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/pre_edit_gate.py"}]},
],
"PostToolUse": [
{"matcher": "Edit|MultiEdit|Write|NotebookEdit",
@@ -555,6 +557,9 @@ settings["hooks"] = {
"Notification": [
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/notification_sound.py"}]},
],
"SubagentStop": [
{"hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/subagent_stop_gate.py"}]},
],
"PreCompact": [
{"matcher": "manual", "hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/pre_compact.py", "timeout": 60}]},
{"matcher": "auto", "hooks": [{"type": "command", "command": f"{hook_python} {hooks_dir}/pre_compact.py", "timeout": 60}]},
+4 -11
View File
@@ -19,9 +19,8 @@ Business logic for `aipass init`. Creates the project scaffold:
7. STATUS.local.md — project status
8. .gitignore — standard AIPass ignores
9. .claude/settings.json — Claude Code hooks configuration
10. hooks/ — directory for user hooks
11. src/ — directory where agents live
12. .ai_mail.local/inbox.json — empty project mailbox
10. src/ — directory where agents live
11. .ai_mail.local/inbox.json — empty project mailbox
Projects are NOT citizens — no .trinity/ directory. Identity lives in the
registry JSON. Init is re-runnable: existing files are skipped, not errors.
@@ -338,13 +337,7 @@ def init_project(target: Path, project_name: str | None = None) -> dict:
shipped = _ship_hooks(aipass_home, target)
created.extend(shipped)
# 10. hooks/ directory
hooks_dir = target / "hooks"
if not hooks_dir.exists():
hooks_dir.mkdir()
created.append(str(hooks_dir))
# 11. src/ directory (where agents live)
# 10. src/ directory (where agents live)
src_dir = target / "src"
if not src_dir.exists():
src_dir.mkdir()
@@ -373,7 +366,7 @@ def update_project(target: Path) -> dict:
Overwrites managed prompt and config files with the latest templates while
leaving all user-owned files (registry, README, STATUS.local.md, .gitignore,
hooks/, src/) untouched.
src/) untouched.
Args:
target: Directory containing the AIPass project to update.
+5 -7
View File
@@ -122,9 +122,8 @@ def print_help():
[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 + AIPASS_HOME
[green]10.[/green] [yellow]hooks/[/yellow] User hooks directory
[green]11.[/green] [yellow]src/[/yellow] Agent directories live here
[green]12.[/green] [yellow].ai_mail.local/inbox.json[/yellow] Empty project mailbox"""
[green]10.[/green] [yellow]src/[/yellow] Agent directories live here
[green]11.[/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()
@@ -354,7 +353,7 @@ def _handle_init_update(args: List[str]) -> bool:
console.print(" CLAUDE.md, AGENTS.md, GEMINI.md")
console.print()
console.print("[bold cyan]What is never touched:[/bold cyan]")
console.print(" *_REGISTRY.json, README.md, STATUS.local.md, .gitignore, hooks/, src/")
console.print(" *_REGISTRY.json, README.md, STATUS.local.md, .gitignore, src/")
console.print()
console.print("[dim]Commands: init update, init update --help[/dim]")
console.print()
@@ -482,9 +481,8 @@ def _print_init_help():
[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 + AIPASS_HOME
[green]10.[/green] [yellow]hooks/[/yellow] User hooks directory
[green]11.[/green] [yellow]src/[/yellow] Agent directories live here
[green]12.[/green] [yellow].ai_mail.local/inbox.json[/yellow] Empty project mailbox"""
[green]10.[/green] [yellow]src/[/yellow] Agent directories live here
[green]11.[/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()
+4 -8
View File
@@ -99,8 +99,7 @@ def test_init_project_creates_all_expected_files(tmp_path):
for f in expected_files:
assert f.exists(), f"Expected file not created: {f}"
# hooks/ and src/ are directories, not files
assert (target / "hooks").is_dir(), "Expected hooks/ directory"
# src/ is a directory, not a file
assert (target / "src").is_dir(), "Expected src/ directory"
# No .trinity/ should be created (projects are not citizens)
@@ -109,8 +108,8 @@ def test_init_project_creates_all_expected_files(tmp_path):
# No local prompt at project level (belongs in agent dirs only)
assert not (target / ".aipass" / "aipass_local_prompt.md").exists()
# 12 files + 2 dirs + 7 shipped hooks (when AIPASS_HOME detected) = 21
assert len(result["created_files"]) == 21
# 11 items + 2 commands + 7 shipped hooks (when AIPASS_HOME detected) = 20
assert len(result["created_files"]) == 20
def test_init_project_return_dict_structure(tmp_path):
@@ -348,7 +347,7 @@ def test_init_project_auto_creates_target_dir(tmp_path):
assert target.is_dir()
assert result["project_name"] == "NESTED"
assert len(result["created_files"]) == 21
assert len(result["created_files"]) == 20
def test_init_project_defaults_name_from_directory(tmp_path):
@@ -395,9 +394,6 @@ def test_init_project_skips_existing_optional_files(tmp_path):
claude_dir.mkdir()
(claude_dir / "settings.json").write_text("{}\n", encoding="utf-8")
hooks_dir = target / "hooks"
hooks_dir.mkdir()
src_dir = target / "src"
src_dir.mkdir()