Adds physical blockers so agents cannot bypass the correct delivery paths:
1. pre_edit_gate.py v1.3.0 (.claude/hooks/) — two new Track E rules:
- Rule 1: block any write to *.ai_mail.local/inbox.json (use drone @ai_mail email)
- Rule 2: block cross-branch writes unless CWD branch is in TRUSTED_CROSS_WRITERS
2. permissions.py (seedgo/apps/modules/) — single source of truth:
- TRUSTED_CROSS_WRITERS = ("devpulse", "seedgo", "spawn")
- is_trusted_caller(name), identify_caller(cwd)
3. drone auth.py — ALLOWED_CALLERS now imported from permissions.py
(extended from ["devpulse"] to all three trusted cross-writers)
4. ai_mail delivery.py — deliver_to_inbox_file() helper added:
- Single canonical path for direct-path inbox writes, always fires notify-send
- reply.py _deliver_via_reply_path() backdoor routes through this helper
5. inbox_audit.py (seedgo/apps/modules/) — drone @seedgo audit inbox-ids:
- Scans all inbox.json files for non-8-hex message ids
- Alerts with drone @ai_mail email command when violations found
6. test_hooks_track_e.py — 26 tests, all passing (359 total in suite)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- hooks.py v1.1.0: new test and list subcommands
- hooks_ext.py: cmd_hooks_test (pytest per test file, Rich table) +
cmd_hooks_list (reads ~/.claude/settings.json + .claude/settings.json,
shows all wired hooks with version, event, matcher, exists status)
- Version headers added to notification_sound.py, stop_sound.py,
tool_use_sound.py (all 10 hooks now have version headers)
- test_hooks_utility.py: 14 smoke tests for 7 previously uncovered hooks
(branch_prompt_loader, email_notification, identity_injector, pre_compact,
notification_sound, stop_sound, tool_use_sound)
- test_hooks_track_b.py: 7 tests for hooks test + hooks list subcommands
- 443 total tests passing (422 → 443)
Note: drone @git pr scope bug (DPLAN-0140) hit again — raw git/gh used.
Adds an observational probe harness for every Claude Code hook event type.
Used to verify hook wiring, scaffold new hooks, and answer Q12 (subagent
hook propagation / env-var matrix).
## What ships
**7 probe scripts** at `.claude/hooks/probes/`:
- probe_pre_tool_use.py, probe_post_tool_use.py, probe_user_prompt_submit.py
- probe_subagent_stop.py, probe_pre_compact.py, probe_stop.py, probe_notification.py
Each probe: reads stdin JSON, appends one entry to last_ping.jsonl
(APPEND mode, never overwrites), exits 0 always. Fields recorded:
event, tool, cwd, agent_id, timestamp, script_elapsed_ms, cli_version,
env_has_claude_project_dir, env_has_aipass_home.
Pure stdlib, no aipass imports. Silent fail on any exception.
OPT-IN — not auto-wired in settings.json (each probe docstring has
the settings.json snippet to enable it).
**drone @seedgo hooks probe** — new seedgo module (apps/modules/hooks.py):
- No flags: reads last_ping.jsonl, prints Rich [PROBE] table of recent entries
- --subagent: spawns claude -p headless, reads probe log, reports whether
PostToolUse / SubagentStop fired (definitive Q12 data for headless mode)
- --matrix: analyzes last_ping.jsonl by event type, reports env var
propagation patterns, writes Q12_findings_2026-04-20.md
**Tests**: 69 new tests in tests/test_hooks_probe.py covering stdin parse,
output shape, malformed input resilience, and module dispatch.
402 total tests pass.
**README.md** at .claude/hooks/probes/README.md — enable instructions,
example output, flag reference.
last_ping.jsonl added to .gitignore (live log, not source).
Adds an observational probe harness for every Claude Code hook event type.
Used to verify hook wiring, scaffold new hooks, and answer Q12 (subagent
hook propagation / env-var matrix).
## What ships
**7 probe scripts** at `.claude/hooks/probes/`:
- probe_pre_tool_use.py, probe_post_tool_use.py, probe_user_prompt_submit.py
- probe_subagent_stop.py, probe_pre_compact.py, probe_stop.py, probe_notification.py
Each probe: reads stdin JSON, appends one entry to last_ping.jsonl
(APPEND mode, never overwrites), exits 0 always. Fields recorded:
event, tool, cwd, agent_id, timestamp, script_elapsed_ms, cli_version,
env_has_claude_project_dir, env_has_aipass_home.
Pure stdlib, no aipass imports. Silent fail on any exception.
OPT-IN — not auto-wired in settings.json (each probe docstring has
the settings.json snippet to enable it).
**drone @seedgo hooks probe** — new seedgo module (apps/modules/hooks.py):
- No flags: reads last_ping.jsonl, prints Rich [PROBE] table of recent entries
- --subagent: spawns claude -p headless, reads probe log, reports whether
PostToolUse / SubagentStop fired (definitive Q12 data for headless mode)
- --matrix: analyzes last_ping.jsonl by event type, reports env var
propagation patterns, writes Q12_findings_2026-04-20.md
**Tests**: 69 new tests in tests/test_hooks_probe.py covering stdin parse,
output shape, malformed input resilience, and module dispatch.
402 total tests pass.
**README.md** at .claude/hooks/probes/README.md — enable instructions,
example output, flag reference.
last_ping.jsonl added to .gitignore (live log, not source).
ruff_check was branch_level only so drone @seedgo checklist <file> skipped
it entirely. F401 unused imports slipped through to main twice (PRs #349,
#357) because neither checklist nor the pre-edit gate caught them per-file.
Changes:
- ruff_check.py v1.1.0: add check_module() for single-file ruff runs +
_find_ruff_bypass_from_file() to locate .seedgo/ruff_bypass.json from
any file path. AUDIT_SCOPE stays branch_level (audit pipeline unchanged).
- checklist.py: update _is_applicable() — branch_level checkers that also
implement check_module() are now eligible for per-file checklist runs.
ruff_check gains per-file enforcement; dead_code/test_quality/unused_function
remain skipped (genuinely need full branch context).
- auto_fix_diagnostics.py v5.2.0: add run_ruff_lint_structured() — runs
ruff --output-format=json and saves violations as {line, message} dicts
alongside pyright errors in the state file. Pre-edit gate now hard-blocks
on F401/lint just like type errors.
- pre_edit_gate.py v1.2.0: generalize reason message from "type error(s)"
to "error(s)" since state now contains lint violations too.
Verification: drone @seedgo checklist <F401 file> now shows ✗ ruff: 2
violation(s) — F401 L1/L2. 333 seedgo tests pass.
Two tests failed on macOS due to the /var/folders → /private/var/folders symlink:
- test_relative_paths_resolved (line 126: string startswith mismatch)
- test_find_registry_from_child_dir (line 377: Path == comparison)
Root cause: tempfile.mkdtemp() returns the unresolved /var/folders/... form on Mac. Production code (PR #361 / 8a5fbf6) correctly calls Path.resolve() which follows the symlink and canonicalizes to /private/var/folders/.... The test fixture's registry_dir stored the unresolved form, so one side of each comparison had /private/ and the other didn't.
One-line fix: .resolve() the fixture path too so both sides are canonical on every platform.
Platform behavior:
- Linux: no-op (no symlink, path already canonical) — was passing, stays passing
- macOS: follows /var/folders → /private/var/folders — was failing (2/33), now passing (33/33)
- Windows: normalizes short-path to long-path consistently with production code — was passing, stays passing
Verified locally on macOS 12.7.6 Intel: 33/33 in test_registry_handler.py green after fix.
Surfaced during the Mac install feedback session on issue #360. Linux @devpulse green-lit the direct PR.
Follow-up recommendation (NOT this PR, separate cleanup): migrate registry_dir fixture to pytest's built-in tmp_path, which returns a pre-resolved Path. Eliminates the class of fixture bug entirely across the test suite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
_get_json_handler() was reading sys.modules[] directly after _fresh_json_handler
had just popped the module out. importlib.import_module() replaces the lookup so
the module is actually imported fresh each test. Also removes dead reload/pop
code from the fixture that never ran. Fixes 27 failures (issue #360 finding A2).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pyproject.toml: add memory = ["numpy>=2.0", "chromadb>=1.0"] optional-deps group
- setup.sh: install .[dev,memory] so fresh-clone pytest works end-to-end
- test_vector.py: gate with pytest.importorskip("numpy"/"chromadb") — skip cleanly without extras
- memory_watcher.py: _check_vector_deps() probes venv at startup; health report now honest when chromadb absent
- bypass.json: 4 entries covering test_vector.py seedgo false-positives (architecture/docs/encapsulation/meta)
- dispatch/daemon.py: resolve relative branch_path to absolute before use
- dispatch/dispatch_monitor.py: resolve lock_file path so claude cwd is always absolute
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(devpulse): watchdog: breadcrumb on exit + default timeout 1800s to 600s (FPLAN-0189)
Co-Authored-By: @devpulse <devpulse@aipass>
* feat(dispatch): auto-spawn watchdog after every dispatch (FPLAN-0189 Task A)
_orchestrate_dispatch_send now spawns `drone @devpulse watchdog agent <target>`
as a detached background process (start_new_session=True) after a successful
wake. cwd=devpulse_path bypasses _guard_caller's cross-branch rejection.
--no-watchdog flag opts out. 6 new tests cover registry lookup, path
resolution, and error paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: @devpulse <devpulse@aipass>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Fresh-install testing on macOS 12 Intel surfaced five friction points that
blocked or silently broke installation for users without admin rights.
All fixes are Mac-only, gated behind a new IS_MACOS flag. Linux and
Windows paths are untouched. See #353 for full context.
1. Stock macOS 12 ships /usr/bin/python3 at 3.9.6; setup.sh aborts at
the 3.10+ minimum check. Fix: probe versioned python3.10-3.13
binaries before falling back to plain python3.
2. Homebrew auto-install is not a universal fallback. Non-admin Mac
accounts cannot install Homebrew at all because its installer
requires admin/sudo. Fix: add uv (astral.sh/uv) as a no-sudo,
no-admin fallback. uv drops into ~/.local/bin via curl and downloads
a prebuilt standalone Python 3.11 to ~/.local/share/uv/python; the
venv is created from that.
3. macOS defaults to zsh since Catalina (2019). Writing AIPASS_HOME to
~/.bashrc silently fails because zsh does not source it. Fix: on
Mac, pick ~/.zshrc (or ~/.bash_profile for bash) based on SHELL.
4. The final symlink used sudo ln -sf /usr/local/bin/drone, which
prompts for a password on Mac and breaks entirely for non-admin
users. Fix: on Mac, link into ~/.local/bin (user-writable, no sudo)
and ensure it is on PATH via the profile rc.
5. HOOK_PYTHON was set to plain python3 on Unix. On Mac that resolves
to /usr/bin/python3 (3.9.6), which cannot parse hook scripts using
PEP 604 union syntax (X | None). Hooks silently crash on every
prompt. Fix: on Mac, point HOOK_PYTHON at the venv python (3.11)
that setup just built.
Tested end-to-end on a fresh clone, non-admin user, zsh, Intel: all 11
branches bootstrapped, drone CLI works, hooks fire and execute cleanly,
ai_mail delivery verified, sub-agents spawn successfully.
Refs #353
Co-authored-by: Paddy <padddypaddypaddy-boop@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(seedgo): fix(bypass): resolve relative registry paths in get_branch_from_path and _load_bypass_for_file — bypass.json entries silently failed for all checklist invocations because branch paths from registry are relative (src/aipass/seedgo) but were compared directly against absolute resolved file paths