diff --git a/.claude/hooks/pr_decision_guard.py b/.claude/hooks/pr_decision_guard.py index 788fc303..7875eff1 100755 --- a/.claude/hooks/pr_decision_guard.py +++ b/.claude/hooks/pr_decision_guard.py @@ -1,18 +1,16 @@ #!/usr/bin/env python3 """PreToolUse(Bash) safety net for `gh pr create`. -If the branch changes structural code under / (excluding tests and -the reference-only legacy/ tree) but adds no entry to the decision journal -(), ask the user to confirm before the PR is opened. This is only a -net — the real capture is the `/finish-task` "décision" step; here we just make -the omission visible. +If the branch changes structural code under / (excluding tests) but +adds no entry to the decision journal (), ask the user to confirm +before the PR is opened. This is only a net — the real capture is the +`/finish-task` decision step; here we just make the omission visible. Emits a PreToolUse "ask" decision (human confirms / overrides) rather than a hard deny, so a genuinely decision-free PR isn't deterministically blocked. No-op for any command other than `gh pr create`, or if the project doesn't declare both a `package_dir` and a `decisions` path in .claude/workflow.json. """ - from __future__ import annotations import json @@ -52,10 +50,7 @@ def main() -> None: try: changed = subprocess.run( ["git", "diff", f"{base}...HEAD", "--name-only"], - cwd=ROOT, - capture_output=True, - text=True, - timeout=10, + cwd=ROOT, capture_output=True, text=True, timeout=10, ).stdout.split() except Exception: _allow() @@ -65,7 +60,6 @@ def is_structural(path: str) -> bool: return ( path.startswith(f"{pkg}/") and "/tests/" not in path - and not path.startswith(f"{pkg}/legacy/") and not name.startswith("test_") ) @@ -73,21 +67,17 @@ def is_structural(path: str) -> bool: if touched_code and decisions not in changed: reason = ( f"This branch changes structural code under {pkg}/ but adds no entry " - f"to {decisions}. Capture the *why* (the /finish-task 'décision' step) " + f"to {decisions}. Capture the *why* (the /finish-task decision step) " f"before opening the PR — or confirm this PR genuinely needs no " f"decision entry." ) - print( - json.dumps( - { - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "ask", - "permissionDecisionReason": reason, - } - } - ) - ) + print(json.dumps({ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "ask", + "permissionDecisionReason": reason, + } + })) sys.exit(0) _allow() diff --git a/.claude/hooks/session_start.py b/.claude/hooks/session_start.py index 85c6bbde..749117d7 100755 --- a/.claude/hooks/session_start.py +++ b/.claude/hooks/session_start.py @@ -6,7 +6,6 @@ Claude Code. Reads paths from .claude/workflow.json; degrades gracefully if the descriptor or any target file is missing (prints what it can, never errors out). """ - from __future__ import annotations import json @@ -29,10 +28,7 @@ def _branch() -> str: try: out = subprocess.run( ["git", "branch", "--show-current"], - cwd=ROOT, - capture_output=True, - text=True, - timeout=5, + cwd=ROOT, capture_output=True, text=True, timeout=5, ) return out.stdout.strip() or "(detached)" except Exception: @@ -41,7 +37,8 @@ def _branch() -> str: def main() -> None: cfg = _cfg() - lines = [f"trading_bot workflow — branch: {_branch()}"] + label = cfg.get("package_dir", ROOT.name) + lines = [f"{label} workflow — branch: {_branch()}"] roadmap_rel = cfg.get("roadmap", "doc/dev/07-roadmap.md") roadmap = ROOT / roadmap_rel diff --git a/.claude/settings.json b/.claude/settings.json index b0197317..a0eacaac 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,14 @@ { + "permissions": { + "deny": [ + "Read(./.env)", + "Read(./.env.*)" + ], + "ask": [ + "Bash(pytest -m network:*)", + "Bash(python -m pytest -m network:*)" + ] + }, "hooks": { "SessionStart": [ { diff --git a/.gitignore b/.gitignore index 78416479..a216fe24 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,9 @@ strategies/* !strategies/README.md !strategies/example/ !strategies/another_example/ + +# Claude Code workflow — local-only state (shared workflow.json / settings.json +# and hooks ARE committed; per-machine overrides are not) +.claude/settings.local.json +.claude/scheduled_tasks.lock +.claude/worktrees/ diff --git a/CLAUDE.md b/CLAUDE.md index 132e98b9..5aaeed3c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -61,6 +61,8 @@ mypy trading_bot/ ## Common conventions + + Shared across my repos, mirrored from `~/.claude/CLAUDE.md` (the single source of truth — if they ever disagree, the global file wins). Restated here so the repo stays self-contained: @@ -72,8 +74,11 @@ stays self-contained: `Co-Authored-By` trailers** (personal repo). - **One PR = one concern**, small and disposable — a big plan ships as several small atomic PRs, never one catch-all branch. -- **Model: `opus`, always** — interactive sessions and every spawned subagent; a plan - leaf's `complexity` is effort/ordering only and never downgrades the model. +- **Model: session model for judgement, tiered execution** — sessions, + orchestration and the judgement skills run on the session model (set in + `~/.claude/settings.json`); plan-leaf execution runs at the tier derived from + the leaf's `complexity` (`low→haiku / medium→sonnet / high→session model`), + escalating one tier on failed tests/verification. - **Before every commit** — `python -m pytest` and `ruff check trading_bot/` must pass (run under the `trading_bot_env` pyenv-virtualenv, auto-activated by `.python-version`). @@ -97,9 +102,9 @@ viable. The loop: `/plan` (decompose into a `doc/dev/plans//` tree — single leaf for a trivial task, a global `00-plan.md` + leaves otherwise — and open the **plan PR** onto `develop`) → -`/execute-leaf next` (cut the leaf branch, **spawn an agent at the model -derived from the leaf's `complexity`**, which implements + tests + **verifies on -real data**) → +`/execute-leaf next` (cut the leaf branch, **spawn an agent at the tier +derived from the leaf's `complexity`, escalating a tier on failure**, which +implements + tests + **verifies on real data**) → `/finish-task` (tests, ADR, CHANGELOG, leaf PR, archive the leaf, tick the global checklist) → … per leaf … → last leaf removes the roadmap line → `/release`.