Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 13 additions & 23 deletions .claude/hooks/pr_decision_guard.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
#!/usr/bin/env python3
"""PreToolUse(Bash) safety net for `gh pr create`.

If the branch changes structural code under <package_dir>/ (excluding tests and
the reference-only legacy/ tree) but adds no entry to the decision journal
(<decisions>), 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 <package_dir>/ (excluding tests) but
adds no entry to the decision journal (<decisions>), 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
Expand Down Expand Up @@ -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()
Expand All @@ -65,29 +60,24 @@ 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_")
)

touched_code = any(is_structural(p) for p in changed)
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()
Expand Down
9 changes: 3 additions & 6 deletions .claude/hooks/session_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down
10 changes: 10 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
{
"permissions": {
"deny": [
"Read(./.env)",
"Read(./.env.*)"
],
"ask": [
"Bash(pytest -m network:*)",
"Bash(python -m pytest -m network:*)"
]
},
"hooks": {
"SessionStart": [
{
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
15 changes: 10 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ mypy trading_bot/

## Common conventions

<!-- mirror of ~/.claude/CLAUDE.md — synced 2026-07-04 -->

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:
Expand All @@ -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`).

Expand All @@ -97,9 +102,9 @@ viable. The loop:
`/plan` (decompose into a `doc/dev/plans/<epic>/` tree — single leaf for a trivial
task, a global `00-plan.md` + leaves otherwise — and open the **plan PR** onto
`develop`) →
`/execute-leaf <epic> 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 <epic> 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`.

Expand Down
Loading