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>
* feat(system): fix(windows-ci): combine pip bootstrap fix + venv activation in Verify step
Combines both fixes needed to get windows-test.yml actually passing:
1. setup.sh: stop swallowing ensurepip errors (quiet + 2>/dev/null + || true was
hiding real failures — pip was silently not installed). Add get-pip.py fallback
and hard pip verification.
2. windows-test.yml: add 'source .venv/Scripts/activate' to Verify step. Each CI
step gets a fresh shell — setup.sh's venv activation doesn't carry over, so
drone wasn't on PATH in the subsequent step.
Together, these should take Windows CI from the 'silent failure every run since
creation' state to actually green. Supersedes PRs #330 and #331 which had the
fixes on separate branches (neither green alone).
Co-Authored-By: @devpulse <devpulse@aipass>
* feat(system): chore(lint): ruff auto-fix sweep — 303 errors across 178 files (F401 unused imports + F541 f-string placeholders + F811 redefined); restored report_error re-export + added logger call in errors.py
Co-Authored-By: @devpulse <devpulse@aipass>
---------
Co-authored-by: @devpulse <devpulse@aipass>
* feat(system): ci: add Windows setup test workflow — runs setup.sh + drone CLI verification on windows-latest GitHub Actions runner. Triggers on changes to setup.sh, handler __init__.py files, cli.py, or pyproject.toml. Closes the 'we never tested on Windows' gap.
Co-Authored-By: @devpulse <devpulse@aipass>
* feat(system): fix(windows): SIGPIPE guard in flow.py (#301) + fcntl platform guards in trigger/config.py and watchdog/registry.py (#302) — lazy import fcntl on Unix only, no-op on Windows. inbox_lock.py already cross-platform (msvcrt). ai_mail.py already guarded (hasattr check).
Co-Authored-By: @devpulse <devpulse@aipass>
* feat(system): fix(windows): handler guard backslash path fix — all 11 __init__.py files (#304). caller_file.replace('\\', '/') normalizes Windows paths before the same-branch check. This is the actual fix for #293 which was incorrectly closed. drone is completely broken on Windows without this.
Co-Authored-By: @devpulse <devpulse@aipass>
---------
Co-authored-by: @devpulse <devpulse@aipass>
* feat(system): fix(windows): PYTHONUTF8=1 in drone cli.py for Rich Unicode on Windows (#296) + STATUS.md reset to stub (#291) + README.md platform honesty (Linux tested, macOS untested, Windows in progress)
Co-Authored-By: @devpulse <devpulse@aipass>
* feat(system): fix: gitignore STATUS.md + STATUS.local.md — interim fix for #291/#298. Auto-generated status files contain developer session data that shouldn't ship in the public repo. Will be un-gitignored when prax sync produces clean public output.
Co-Authored-By: @devpulse <devpulse@aipass>
---------
Co-authored-by: @devpulse <devpulse@aipass>
Email delivery to external branches (e.g. @strategy in Vera-Studio) worked
because delivery.py already had a caller-registry fallback, but wake failed
with "Branch not found" because resolve_branch() only checked AIPASS_REGISTRY.
Extracted the shared registry-walking logic into
registry/read.py::get_caller_project_branches(), then:
- wake.py resolve_branch() now checks AIPASS_REGISTRY first, then falls back
to the caller's project registry via AIPASS_CALLER_CWD (cross-project wake)
- delivery.py _load_caller_project_branches() delegates to the shared function
(eliminates duplicate implementation)
Fixes#283.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add checklist, proof, proof_query, test_map to the Commands list (were
missing from the branch prompt even though the commands work)
- Note that proof/proof_query/test_map are not in --help output yet (TODO)
- Add Hook Ownership section: full inventory of the 8 hooks seedgo owns,
their wiring state (including the unwired subagent_stop_gate), and the
three-location drift. Points at DPLAN-0131 for the consolidation plan.
- Update audit line count from "all branches" to "all 11 agents (33 audit
lines)" to match current reality
Empty __init__.py files were failing both checkers as false positives:
- imports: zero-check result scored 0% and reported "Failed (no details)"
- naming: __init__ fails snake_case regex ^[a-z][a-z0-9_]*$
Python package markers don't require imports by convention and cannot
be renamed (Python-reserved). Short-circuit both checkers to return
PASS for any file named __init__.py.
Reported by @devpulse while adding navigator/__init__.py to compass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>