# Lessons

- [1x] PyPI publishing is handled via GitHub Actions trusted publishing. NEVER use twine to upload directly. Create a GitHub release and PyPI pulls it automatically.
- [2x] Always run tests with `uv run python -m pytest` (NOT bare `python -m pytest`). Dev deps (pytest) are in `[dependency-groups] dev` which uv auto-includes. System/conda Python won't have project deps.
- [1x] When adding shell operators to SHELL_OPERATOR_RE, enumerate ALL operators including redirection (>, >>, <) and newlines (\n). Missing even one creates a bypass where safe-prefix commands can write to arbitrary files.
- [1x] SAFE_PREFIXES entries that also exist in SAFE_EXACT must have a trailing space (e.g. "env " not "env") — otherwise startswith() matches unintended commands like envsubst, lsblk, etc.
- [1x] After changing install/uninstall logic, ALWAYS verify the live hook path in ~/.claude/settings.json points to the right python and script. Stale non-editable installs mean your code changes are invisible.
- [1x] When designing LLM prompts for the gatekeeper, ALWAYS require a "reason" field in both allow AND deny JSON responses. Without it, logs are useless for auditing decisions.
- [1x] Avoid double-logging: raw LLM response goes to log_debug() only, parsed DECISION line goes to log(). Users only see the main log — put all useful info (method, reason, timing) on the DECISION line.
- [1x] jacked is installed as a uv tool with `--editable` — code changes are live immediately. ALWAYS check the uv tool's `.pth` file (under the tool's site-packages dir) before telling the user to reinstall.
