diff --git a/CHANGELOG.md b/CHANGELOG.md index 658d640e..b2b3b76a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ccf041b3..0739712d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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? diff --git a/README.md b/README.md index e29f8694..13a07998 100644 --- a/README.md +++ b/README.md @@ -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](https://hvtracker.net/badge/aipass.svg)](https://hvtracker.net/agents/aipass)

AIPass @@ -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. --- diff --git a/setup.sh b/setup.sh index d58a5267..a4a90d93 100755 --- a/setup.sh +++ b/setup.sh @@ -633,7 +633,7 @@ 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 @@ -641,11 +641,16 @@ 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(): @@ -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"}]}, @@ -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