Pre-existing ruff errors surfaced during Wave 4 QC:
- F401: drop unused `TerminalSession` import (only `TerminalSessionManager` is used)
- F841: drop unused `start = time.monotonic()` local in `_execute_standalone`
`ruff format` then reformatted a few long lines in the same file
(frozenset literal, curl exfiltration regex, pipe operators, session.env
call). No behavior change — formatting only.
Why now: shell.py was already touched by U1 (widen
`bash_command_filter`). Leaving known ruff failures in a file this PR
modifies would make future CI gates noisy.
Reuses ShellTool._is_dangerous as the default bash filter for PLANNING
and VERIFICATION phases, closing the regex ceiling documented in Wave 3.
- Convert ShellTool._is_dangerous and _is_single_command_dangerous to
@staticmethod (backward-compatible; instance calls still work via
Python's descriptor protocol).
- Widen PhasePolicy.bash_command_filter field type to
dict[PhaseState, Callable[[str], bool] | re.Pattern | None].
- is_bash_command_allowed dispatches on callable vs pattern at call time.
Empty commands short-circuit to allowed (Wave 3 contract; ShellTool
emits the clearer empty-command error).
- to_dict serializes callables as <callable> for log readability.
- default_policy() now wires ShellTool._is_dangerous for PLANNING and
VERIFICATION. _DEFAULT_BASH_FILTER kept for backward compat with
configs that pass a re.Pattern.
- Tests: characterization tests pin Wave 3 behavior (rm/mv/cp/echo >
still blocked) plus new edge-case coverage for ceiling closed
(dd of=/dev/sda, :>file, chain operators, pipe segments).
- Shell whitelist: use exact binary match instead of startswith
- Shell audit log: use deque(maxlen=10000) to cap memory
- Terminal history: use deque(maxlen) for O(1) eviction
- Path optimizer: cap _pending_paths at 50 entries per task_type
- Pitfall detector: only add tips to matching steps, not all
- Experience store: handle non-numeric _parse_time_window input
- Extract shared is_safe_url() to utils/security.py (DRY)
- Workflow condition evaluator: handle float() ValueError