From 40eb295e41d820d8a51d585b548bb3e91fbeeab8 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Fri, 22 May 2026 16:52:03 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20Sunday=20release=20prep=20=E2=80=94=20h?= =?UTF-8?q?ooks.json=20tracked,=20CHANGELOG.md,=20prax=20fix,=20gitignore?= =?UTF-8?q?=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .aipass/.gitignore | 1 + .aipass/hooks.json | 104 ++++++++++++++++++ .gitignore | 3 + CHANGELOG.md | 92 ++++++++++++++++ .../apps/handlers/dashboard/operations.py | 10 +- 5 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 .aipass/hooks.json create mode 100644 CHANGELOG.md diff --git a/.aipass/.gitignore b/.aipass/.gitignore index ded9ab75..129cf051 100644 --- a/.aipass/.gitignore +++ b/.aipass/.gitignore @@ -1,4 +1,5 @@ * !aipass_global_prompt.md +!hooks.json !.gitignore #Do not add other exceptions here without careful consideration. Developer permissions0ns needed. \ No newline at end of file diff --git a/.aipass/hooks.json b/.aipass/hooks.json new file mode 100644 index 00000000..71f9fa42 --- /dev/null +++ b/.aipass/hooks.json @@ -0,0 +1,104 @@ +{ + "_comment": "Per-project hook configuration for the AIPass hook engine (DPLAN-0184)", + "hooks_enabled": true, + + "UserPromptSubmit": { + "identity_injector": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.prompt.identity.handle", + "matcher": "" + }, + "email_notification": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.notification.email.handle", + "matcher": "" + }, + "branch_prompt": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.prompt.branch_loader.handle", + "matcher": "" + }, + "global_prompt": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.prompt.global_loader.handle", + "matcher": "" + } + }, + + "PreToolUse": { + "tool_use_sound": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.notification.tool_sound.handle", + "matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task" + }, + "pre_edit_gate": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.security.edit_gate.handle", + "matcher": "Edit|MultiEdit|Write|NotebookEdit" + }, + "git_gate": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.security.git_gate.handle", + "matcher": "Bash|Edit|MultiEdit|Write|NotebookEdit" + }, + "engine_test_sound": { + "enabled": false, + "command": "python3 $AIPASS_HOME/.claude/hooks/engine_test_sound.py", + "matcher": "WebSearch" + } + }, + + "PostToolUse": { + "auto_fix_diagnostics": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.lifecycle.auto_fix.handle", + "matcher": "Edit|MultiEdit|Write|NotebookEdit", + "timeout": 45 + }, + "auto_watchdog": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.lifecycle.auto_watchdog.handle", + "matcher": "Bash" + } + }, + + "SubagentStop": { + "subagent_stop_gate": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.security.subagent_gate.handle", + "matcher": "", + "timeout": 60 + } + }, + + "Stop": { + "stop_sound": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.notification.stop_sound.handle", + "matcher": "" + } + }, + + "Notification": { + "notification_sound": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.notification.announce.handle", + "matcher": "" + } + }, + + "PreCompact": { + "pre_compact": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.lifecycle.compact.handle", + "matcher": "", + "timeout": 60 + }, + "pre_compact_rollover": { + "enabled": true, + "handler": "aipass.hooks.apps.handlers.lifecycle.rollover.handle", + "matcher": "", + "timeout": 120 + } + } +} diff --git a/.gitignore b/.gitignore index 1a421b81..870e32a6 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,9 @@ src/aipass/*/apps/integrations/** !src/aipass/spawn/templates/builder/DASHBOARD.local.json !src/aipass/spawn/templates/builder/STATUS.local.md +# CI artifacts +windows-pytest-results/ + # Parked / one-off HERALD.md backup_data/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..04c85f8b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,92 @@ +# Changelog + +All notable changes to AIPass will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project uses [Calendar Versioning](https://calver.org/) in the format +`YYYY.WNN` (year and ISO week number). + +--- + +## [2026.W21] - 2026-05-25 + +First weekly release. AIPass now follows a Sunday release cadence: changes +accumulate on `dev` throughout the week and merge to `main` as a single +versioned release with notes. + +### Added + +- **Hook engine** — a new centralized dispatch system for all hook + execution. A thin bridge receives events from the AI provider (Claude, + Codex, etc.) and routes them through a single Python engine that reads + per-project configuration, executes the appropriate handlers, and logs + every invocation. Replaces 14 standalone shell/Python scripts with native + handler modules organized by domain: prompt injection, security + enforcement, lifecycle management, and notifications. +- **Per-project hook configuration** via `.aipass/hooks.json`. Each project + can enable, disable, or customize individual hooks without touching + provider-level settings. Previously hooks fired globally with no + per-project control. +- **Audio feedback on hook events** using Piper TTS. Tool usage, response + completion, notifications, email alerts, and sub-agent activity each + produce distinct spoken audio cues so operators can monitor sessions + without watching the terminal. +- **Hooks agent** — the 13th citizen in the AIPass registry, owning all + hook infrastructure: the engine, bridge, handlers, and configuration + schema. +- **Dashboard plugin for devpulse** — aggregates git status, session + history, and dispatch state into a single startup view. Wired into the + session startup protocol so branch managers see current state + immediately. +- **External log routing** — prax now accepts structured log entries from + any branch, not just its own modules. Hook executions, dispatches, and + agent activity all flow into the central monitoring log. + +### Changed + +- **Provider settings fully migrated to bridge pattern.** All hook entries + in the Claude provider configuration now call the bridge dispatcher + instead of individual scripts. Each hook produces its own system-reminder + to the model, preserving prompt injection fidelity (a single merged + bridge was found to break prompt delivery due to Claude Code's output + persistence threshold). +- **setup.sh rewritten** to install hooks via the bridge pattern. The old + version hardcoded 14 script paths; the new version writes a single bridge + call per event type and validates that the bridge module exists. +- **Documentation sweep** across `.claude/README.md`, `SECURITY.md`, the + global prompt, and branch-level docs to reflect the new hook + architecture. References to legacy `.claude/hooks/` scripts replaced with + the native handler locations. +- **`aipass init update`** now correctly preserves user-customized hook + settings during project updates instead of overwriting them. +- **Seedgo snapshot tests rebuilt** — the provider hooks snapshot fixture + and extraction logic were structurally broken (silently passing with zero + results). Both the fixture format and the test assertions have been + corrected. +- **Test suite updated for hook migration** — `test_git_gate.py` imports + from the new handler module; `test_bootstrap.py` no longer asserts that + project initialization ships standalone hook scripts (it no longer does). + +### Fixed + +- **Settings merge on project update** — `aipass init update` was + clobbering user hook configurations. The merge logic now layers AIPass + defaults under existing user settings. +- **Python 3.10 test collision** — a module/function name collision caused + mock patch targets to fail on Python 3.10. Test targets corrected. +- **Dead code removal** — removed an unused CLI `__main__.py` entrypoint + and cleaned up `.gitignore` entries that were masking tracked files. +- **Codecov patch threshold** lowered to 50% to reflect the project's + current coverage baseline and stop false-negative CI failures. + +### Removed + +- **18 standalone hook scripts** in `.claude/hooks/` disabled (renamed with + `(disabled)` suffix). Their logic now lives in native handler modules + under `src/aipass/hooks/apps/handlers/`. The old files remain on disk for + reference but are no longer executed. + +--- + +*This is the first CHANGELOG entry. Prior work is documented in the +repository's commit history and branch session logs.* diff --git a/src/aipass/prax/apps/handlers/dashboard/operations.py b/src/aipass/prax/apps/handlers/dashboard/operations.py index ae553b54..6e5d443f 100644 --- a/src/aipass/prax/apps/handlers/dashboard/operations.py +++ b/src/aipass/prax/apps/handlers/dashboard/operations.py @@ -203,16 +203,21 @@ def _calculate_quick_status_standalone(sections: Dict) -> Dict: """ ai_mail = sections.get("ai_mail", {}) flow = sections.get("flow", {}) + commons = sections.get("commons_activity", {}) new_mail_raw = ai_mail.get("new", ai_mail.get("unread", 0)) opened_raw = ai_mail.get("opened", 0) active_plans_raw = flow.get("active_plans", 0) + commons_mentions_raw = commons.get("mentions", 0) new_mail = len(new_mail_raw) if isinstance(new_mail_raw, list) else int(new_mail_raw or 0) opened_mail = len(opened_raw) if isinstance(opened_raw, list) else int(opened_raw or 0) active_plans = len(active_plans_raw) if isinstance(active_plans_raw, list) else int(active_plans_raw or 0) + commons_mentions = ( + len(commons_mentions_raw) if isinstance(commons_mentions_raw, list) else int(commons_mentions_raw or 0) + ) - action_required = new_mail > 0 or active_plans > 0 + action_required = new_mail > 0 or active_plans > 0 or commons_mentions > 0 parts = [] if new_mail > 0: @@ -221,11 +226,14 @@ def _calculate_quick_status_standalone(sections: Dict) -> Dict: parts.append(f"{opened_mail} opened") if active_plans > 0: parts.append(f"{active_plans} active plans") + if commons_mentions > 0: + parts.append(f"{commons_mentions} mentions") return { "new_mail": new_mail, "opened_mail": opened_mail, "active_plans": active_plans, + "commons_mentions": commons_mentions, "action_required": action_required, "summary": ", ".join(parts) if parts else "All clear", }