E2E test harness for Claude Code. Drives the TUI via pexpect PTY, detects state through hooks.
See CLAUDE.md for full development docs.
All tool calls are auto-approved without permission prompts, except AskUserQuestion and ExitPlanMode.
Configured tools are blocked with a custom redirect message. Everything else passes through.
Captures versioned snapshots of plan files as they're created and modified. A PostToolUse hook sets a dirty flag when Write/Edit touches a /plans/*.md file, and a Stop hook snapshots the plan content to ~/.local/state/claude/plan-tracker/{slug}.jsonl (one JSONL file per plan slug). Deduplicates by content hash — only records a new version when the content actually changed.
Per-job lifecycle log keyed by PPID. Fires on every hook event but only records job_start, session_clear, pid_change, mode_change, and stop. State files at ~/.local/state/claude/job-state/{ppid}.jsonl (override via JOB_STATE_DIR). An index.json maps session_id → ppid for resume detection. On resume (new PPID), creates a symlink so all writes go to the original state file. External scripts can determine job status by reading the JSONL.
Injects messages at key moments in the plan-exit lifecycle. Three independent message types, any combination works:
| Message | Config key | When injected | Mechanism |
|---|---|---|---|
| Contextfull | on_exit_plan_contextfull |
Pre-implementation (option 2, no clear) | PostToolUse additionalContext |
| Contextless | on_exit_plan_contextless |
Pre-implementation (option 1, after clear) | SessionStart additionalContext |
| Plan-implemented-stop | on_plan_implemented_stop |
Post-implementation (after Claude stops) | Stop hook blocks with message |
- Planless auto-approve: PermissionRequest hook auto-approves ExitPlanMode when
tool_inputhas noplankey (PreToolUseallowdoesn't bypass the plan dialog — PermissionRequest is required)
Lifecycle (option 2, no clear): PreToolUse writes breadcrumb → PostToolUse injects contextfull as additionalContext, writes plan-impl-stop breadcrumb if configured, deletes breadcrumb → Claude implements → Stop: plan-impl-stop breadcrumb triggers block → re-fire passes through.
Lifecycle (option 1, clear): PreToolUse writes breadcrumb → context clears → SessionStart injects contextless as additionalContext, writes plan-impl-stop breadcrumb if configured, deletes breadcrumb → Claude implements → Stop: plan-impl-stop breadcrumb triggers block.
All behavior tests run twice: once bare (no plugin hooks) and once with the full plugin loaded. This ensures the plugin's hooks don't alter baseline Claude Code behavior.
Verifies Claude boots up and reaches idle prompt.
Verifies /clear creates a new session ID.
Verifies Claude creates a plan and reaches the approval prompt.
Verifies that plan↔act mode transitions emit synthetic mode_change events in session-state JSONL. Triggers a plan, accepts with option 2 (contextfull), and asserts mode_transitions() / mode_at() return correct results.
Tests the hidden messages Claude receives after ExitPlanMode approval:
- Option 1 (clear context): new session gets a synthetic user message with "Implement the following plan:", the plan content, a path to the old transcript, and a TeamCreate suggestion. Old transcript ends with rejection + interruption. Negative: no tool_result, no "approved your plan" language.
- Option 2 (keep context): same session continues with an ExitPlanMode tool_result containing "User has approved your plan", the plan save path, and plan content. Negative: no synthetic user message, no transcript reference, no "Implement" prefix, no
planContentfield.
Negative test that verifies the Bash tool shows (No output) for commands that produce stdout. Runs two sequential Bash commands (echo + fastclaude/ask) and asserts the tool_response contains real output. Currently expected to fail — will pass when the upstream bug is fixed.
Wraps the existing statusline command to track context window usage and fire desktop notifications on threshold crossings (20%, 25%, 30%...). Chains to claudectl show-statusline transparently.
Config: ~/.config/claudectl/context-monitor.yaml (see statusline/context-monitor-config.example.yaml)
Dependencies: notifyctl
Install: ./bin/install (symlinks context-monitor into ~/.local/bin/)
Config lives in claudectl.yaml. Searched at:
- Project root:
./claudectl.yamlor./.claudectl.yaml - XDG fallback:
~/.config/claudectl/config.yaml
First match wins. All keys are optional.
on_exit_plan_contextless: >-
Continue implementing the plan.
Read the plan file and pick up where you left off.
on_exit_plan_contextfull: >-
You just exited plan mode.
Continue implementing the plan.
on_plan_implemented_stop: >-
Run the tests before you stop.
tool_redirects:
WebSearch: |
Use searchctl instead of WebSearch.
Run via Bash: searchctl web-search "query"