ai_mail moved deliver_email_to_branch from modules.email to
handlers.email.delivery. The modules-level re-export is now in
email_send.py. Updated 3 source files + 4 test mock paths.
367 tests passing, seedgo 100%.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix check_and_rollover → run_rollover import in startup.py (type error)
- Remove dead _is_duplicate_error() from log_watcher.py + 3 tests (unused_function)
- Add logger.warning on silent catch in watchers/log_watcher.py (silent_catch)
- Update README date to 2026-04-22 (readme)
- All 24 seedgo categories at 100%, 367 tests passing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>