Skip to content
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,37 @@ PyPI version — not the changelog header.

## [2026-07-05]

### Fixed

- **HVTrust badge restored in the root README.** hvtracker corrected the
methodology v4.1 grade-computation bug (issue #109); the badge shows the
right grade again, so the temporary comment-out from earlier today is
reverted.

- **Installer no longer destroys a user's custom Claude Code hooks (DPLAN-0234
Strand C).** setup.sh used to write `settings["hooks"]` wholesale — anyone
with their own hooks in `~/.claude/settings.json` lost them on install or
re-run. Now it merges: every AIPass bridge entry (identified by the
`bridges/claude.py` marker) is refreshed, while user-wired hooks and custom
events are preserved. Verified against fixtures: custom hooks survive, stale
AIPass entries are replaced without duplicates, and the fresh-install output
is shape-identical to before (7 events, 6 UserPromptSubmit + 6 PreCompact
entries). Found while fire-testing a fresh install's hooks in Docker — all
17 wired hook entries pass on a cold Linux clone (real kernel/navmap/branch
prompt bytes, git gate blocks, clean no-ops on empty state).

- **Windows fresh installs get a working hook bridge.** setup.sh wrote the
Claude bridge command with `.venv/bin/python3` on every OS — but Windows
venvs put the interpreter at `.venv/Scripts/python.exe` and have no `bin/`,
so hooks on a fresh Windows install pointed at a nonexistent python and
would never fire. The bridge string is now OS-aware (bash passes
`IS_WINDOWS` into the hook-install step). @hooks assessed the rest of the
chain: `$AIPASS_HOME` expansion works on Windows because Claude Code runs
hooks via Git Bash (which must exist for setup.sh to have run), and the
bridge itself has zero POSIX assumptions — the interpreter path was the
only gap. Verified: both OS modes produce the right bridge string, merge
marker unchanged, custom-hook preservation intact. (assessed by @hooks)

### Added

- **`./aipass` — repo-root cold-clone launcher (DPLAN-0234 Strand B).** The
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ PRs are welcome after an issue has been discussed. Keep changes focused -- one f
- Python 3.10+
- Tests: `pytest` (4,900+ tests across the project)
- Quality: AIPass uses its own standards system ([seedgo](src/aipass/seedgo/README.md)) for automated audits
- Run `./setup.sh` to bootstrap the full environment
- Run `./aipass install --no-init` to bootstrap the full environment (contributors work in the engine repo itself — no first-project scaffold needed)

## Questions?

Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
[![codecov](https://codecov.io/gh/AIOSAI/AIPass/graph/badge.svg)](https://codecov.io/gh/AIOSAI/AIPass)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/AIOSAI/AIPass/badge)](https://scorecard.dev/viewer/?uri=github.com/AIOSAI/AIPass)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/13095/badge)](https://www.bestpractices.dev/projects/13095)
<!-- HVTrust badge temporarily hidden 2026-07-05: hvtracker methodology v4.1 recalibration bug is showing Grade D/~10 (dimensions on the detail page sum to ~78); correction filed as hvtracker issue #109. Restore when resolved. Was:
[![HVTrust](https://hvtracker.net/badge/aipass.svg)](https://hvtracker.net/agents/aipass) -->
[![HVTrust](https://hvtracker.net/badge/aipass.svg)](https://hvtracker.net/agents/aipass)

<p align="center">
<img src="assets/logo.png" alt="AIPass" width="400" />
Expand Down Expand Up @@ -232,7 +231,7 @@ AIPass is built and tested with **Claude Code** on Linux/WSL.
| [Claude Code](https://code.claude.com/docs) | `claude -p "prompt" --permission-mode bypassPermissions` | Fully tested |
| [Codex](https://github.com/openai/codex) | `codex exec "prompt" --dangerously-bypass-approvals-and-sandbox` | Experimental |

setup.sh auto-detects which CLIs are installed and configures hooks for each.
The installer (`./aipass install`, powered by setup.sh) auto-detects which CLIs are installed and configures hooks for each — merging with any hooks you've already wired, never overwriting them.

---

Expand Down
26 changes: 22 additions & 4 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -633,19 +633,24 @@ if [ -f "$SCRIPT_DIR/src/aipass/hooks/apps/handlers/bridges/claude.py" ]; then
echo "Installing Claude Code hooks ..."
mkdir -p "$HOME/.claude"

"$PYTHON" - "$SCRIPT_DIR" "$CLAUDE_SETTINGS" << 'PYEOF'
"$PYTHON" - "$SCRIPT_DIR" "$CLAUDE_SETTINGS" "$IS_WINDOWS" << 'PYEOF'
import json
import os
import sys
from pathlib import Path

repo_root = sys.argv[1]
settings_path = Path(sys.argv[2])
is_windows = len(sys.argv) > 3 and sys.argv[3] == "1"

# Bridge entry point — all hooks route through the engine via this bridge.
# Uses $AIPASS_HOME env var (injected into settings.env below) so the
# settings file stays relocatable.
bridge = "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py"
# settings file stays relocatable. CC on Windows runs hooks via Git Bash
# (which must exist for setup.sh to have run), so $VAR expansion works —
# but the venv interpreter lives at Scripts/python.exe there, not bin/python3
# (@hooks assessment, DPLAN-0234 Strand C).
venv_python = ".venv/Scripts/python.exe" if is_windows else ".venv/bin/python3"
bridge = f"$AIPASS_HOME/{venv_python} $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py"

# Load existing settings or start fresh
if settings_path.exists():
Expand All @@ -657,7 +662,7 @@ else:
# UserPromptSubmit: 5 separate entries (EventType:hook_name) to avoid output merging
# PreToolUse, PostToolUse, SubagentStop, Stop, Notification: single aggregate entries
# PreCompact: 3 hooks x 2 matchers (manual + auto) = 6 entries
settings["hooks"] = {
aipass_hooks = {
"UserPromptSubmit": [
{"hooks": [{"type": "command", "command": f"{bridge} UserPromptSubmit:tier0_kernel"}]},
{"hooks": [{"type": "command", "command": f"{bridge} UserPromptSubmit:navmap"}]},
Expand Down Expand Up @@ -693,6 +698,19 @@ settings["hooks"] = {
],
}

# Merge, don't replace (DPLAN-0234 Strand C): refresh every AIPass bridge entry
# (identified by the bridges/claude.py marker) but preserve any hooks the user
# wired themselves. Re-runs stay idempotent; custom hooks survive reinstall.
existing_hooks = settings.get("hooks", {})
merged_hooks = {}
for event in set(existing_hooks) | set(aipass_hooks):
user_entries = [
entry for entry in existing_hooks.get(event, [])
if "bridges/claude.py" not in json.dumps(entry)
]
merged_hooks[event] = aipass_hooks.get(event, []) + user_entries
settings["hooks"] = merged_hooks

# Inject AIPASS_HOME into env block so dispatched agents find AIPass
env_block = settings.get("env", {})
env_block["AIPASS_HOME"] = repo_root
Expand Down
Loading