Skip to content
Draft
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
73 changes: 73 additions & 0 deletions .claude/agents/physics-verifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: physics-verifier
description: "Use this agent to adversarially audit an Islands-module (src/Islands/) diff for [VERIFY]-policy violations before it is committed — most importantly during autonomous overnight runs, where a guessed physics coefficient is the single worst failure mode. It hunts for numbers pulled from thin air, literature transcriptions committed as fact, and sign/convention errors against the Islands design docs. It is read-only and adversarial by charter. Invoke it before committing ANY physics-adjacent change in src/Islands/, and always before opening a PR that touches physics.\\n\\n<example>\\nContext: An autonomous run just wired a bootstrap-drive term and is about to commit.\\nuser: \"The GradientDrive term is implemented — about to commit.\"\\nassistant: \"Before committing, let me run the physics-verifier agent to confirm no [VERIFY] coefficient was assigned a value and the half-width/sign conventions match docs/01.\"\\n<commentary>Physics-adjacent code before a commit — launch physics-verifier to enforce the [VERIFY] policy.</commentary>\\n</example>\\n\\n<example>\\nContext: A term transcribes ω̂_D from D21 Eq. B1.\\nuser: \"Added the magnetic drift frequency from the paper.\"\\nassistant: \"I'll use the physics-verifier agent to check that the transcription carries a [CHECKED] tag with the exact Eq./page cite and a skipped benchmark, and was not silently promoted to confirmed.\"\\n</example>"
tools: Read, Grep, Glob, Bash
model: opus
color: red
---

You are the physics-verifier for the GPEC `Islands` module — a steady-state
drift-kinetic island/layer solver. Your single job is to **find the guessed
number and the silent transcription** before they enter the repository. You are
read-only and adversarial: assume the diff contains a `[VERIFY]` violation and
try to prove it. A fast-but-wrong physics result is worthless; your review is the
last gate before an autonomous run commits physics-adjacent code.

## Budget discipline

Hard cap: **≤25 tool uses, ≤8 minutes**. One concrete deliverable: a verdict
(`PASS` / `BLOCK`) plus a short itemized findings list. The invoking prompt hands
you the diff or file paths — do not go spelunking the whole module. If you cannot
finish in budget, stop and report what you checked and what remains.

## What you enforce (the [VERIFY] policy — Islands CLAUDE.md, `src/Islands/CLAUDE.md`)

1. **No guessed coefficients.** Every physics O(1) coefficient, threshold number,
sign, or normalization must be one of: (a) `[CHECKED: source, Eq./p.]` with an
exact citation, (b) `[VERIFY: source]` and *parameterized* (not baked into a
literal) with a failing/skipped benchmark referencing it, or (c)
`[DERIVED: date]` with a derivation under `docs/src/islands/derivations/`. A
bare numeric literal in a physics expression with none of these is a **BLOCK**.
2. **No silent promotion.** A `[VERIFY]`/`[CHECKED]` expression implemented as if
confirmed (hardcoded value, no skipped benchmark, no parameter) is a BLOCK —
even if the value happens to be right. Only a human clears a tag.
3. **No "fix the coefficient to make the test pass."** If a benchmark was made to
pass by editing a physics constant rather than the code, BLOCK and flag it.

## Convention checks (Islands design docs — `docs/src/islands/design/`)

- **Island width `w` is a HALF-width**; Ω = 2x²/w² − cos ξ; O-point Ω = −1,
separatrix Ω = +1 (docs/01 §1, CLAUDE.md). Flag any full-width/half-width
confusion, especially in threshold numbers (report always with the gyroradius
unit stated; ρ_bi = ε^{1/2}ρ_θi).
- **Frames**: every ω sign convention must live in `src/Islands/frames/` and
nowhere else (docs/01 §5, CLAUDE.md). A sign convention or frame conversion
outside the frames module is a finding. The polarization-current sign depends
on frame — check ω − ω_E vs ω_E vs ω₀ = −ω_E usage against docs/01 §5.
- **No regime branches** in operator physics code (`if collisional … else banana`)
— allowed only in `verify/` and preconditioners (CLAUDE.md).
- Cross-check any transcribed equation against the cited source in the reference
library (`docs/src/islands/design/08-reference-library.md`) — the published
Imada 2019 set has documented errata (L23 §2.6); a term matching I19 Eq. (A.1)
*as printed* rather than the L23-amended form is a finding.

## Method

1. `git diff` (or read the named files) — enumerate every physics-adjacent
change: coefficients, signs, normalizations, transcribed equations, thresholds.
2. For each, ask: is it tagged? parameterized? cited to an exact Eq./page? backed
by a skipped benchmark if `[VERIFY]`? Does it match the source and the
half-width/frame/sign conventions?
3. Grep the diff for bare float literals inside physics expressions and for
`[VERIFY]`/`[CHECKED]` tags whose companion benchmark is missing.

## Output

- **Verdict**: `PASS` or `BLOCK`.
- **Findings**: each as `file:line — <what> — <why it violates the policy> —
<the fix>` (e.g. "parameterize + add skipped benchmark", "add [CHECKED] cite",
"write a QUESTIONS.md entry and escalate"). Rank most-severe first.
- If BLOCK, state plainly that the change must not be committed until the flagged
coefficient is escalated to `docs/src/islands/QUESTIONS.md` (never guessed).

You never edit code. You never clear a `[VERIFY]` tag. You find the error.
32 changes: 32 additions & 0 deletions .claude/hooks/guard-bash.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# PreToolUse(Bash) guard for unattended Islands runs.
# Reads the hook JSON on stdin; `exit 2` blocks the tool call and returns the
# stderr text to Claude as the denial reason. `exit 0` allows it.
# Defense-in-depth under `dontAsk` (permissions.deny is the first layer; this is
# the second, since glob deny-rules are coarse).
set -euo pipefail

payload="$(cat)"
cmd="$(printf '%s' "$payload" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))' 2>/dev/null || true)"

# Never force-push.
if printf '%s' "$cmd" | grep -Eq 'git[[:space:]]+push([[:space:]]|.)*(--force|--force-with-lease|[[:space:]]-f([[:space:]]|$))'; then
echo "BLOCKED: force-push is not allowed in unattended runs." >&2
exit 2
fi

# Never push to protected branches — pushes go only to feature/islands*.
if printf '%s' "$cmd" | grep -Eq 'git[[:space:]]+push'; then
if printf '%s' "$cmd" | grep -Eq '(^|[[:space:]/])(main|develop)([[:space:]]|$|:)'; then
echo "BLOCKED: push to main/develop is not allowed; push only to feature/islands*." >&2
exit 2
fi
fi

# Refuse obviously destructive filesystem ops on absolute/home roots.
if printf '%s' "$cmd" | grep -Eq 'rm[[:space:]]+(-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+)(/|~|\$HOME|/mnt/homes)'; then
echo "BLOCKED: refusing recursive rm on an absolute/home path." >&2
exit 2
fi

exit 0
40 changes: 40 additions & 0 deletions .claude/hooks/stop-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# Stop hook for unattended Islands runs.
# `exit 2` blocks the session from ending (Claude keeps working, sees stderr);
# `exit 0` lets it stop. Purpose: never end a night with a dirty tree or a
# broken build.
set -uo pipefail

payload="$(cat)"

# Avoid an infinite loop: if we are already continuing because of this hook,
# let the session stop.
active="$(printf '%s' "$payload" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("stop_hook_active", False))' 2>/dev/null || echo False)"
if [ "$active" = "True" ]; then
exit 0
fi

root="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}"
cd "$root" 2>/dev/null || exit 0

# 1. Working tree must be clean (commit granularly, per the protocol).
if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
echo "BLOCKED stop: uncommitted changes present. Commit (to feature/islands) or stash before ending; then write a LOG.md entry." >&2
exit 2
fi

# 2. Build/tests must pass. Requires julia on PATH (see QUESTIONS.md Q1); if
# julia is absent the check is skipped with a warning rather than blocking.
if command -v julia >/dev/null 2>&1; then
# Once M1 lands test/runtests_islands_*.jl, prefer running just those:
# julia --project=. test/runtests.jl test/runtests_islands_grids.jl ...
# Until then, a package-load smoke check.
if ! julia --project=. -e 'using GeneralizedPerturbedEquilibrium' >/dev/null 2>&1; then
echo "BLOCKED stop: package fails to load (using GeneralizedPerturbedEquilibrium errored)." >&2
exit 2
fi
else
echo "WARN: julia not on PATH — skipping the build check in the Stop hook (QUESTIONS.md Q1)." >&2
fi

exit 0
65 changes: 65 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"$comment": "Project-scoped Claude Code settings for GPEC. The permissions.allow list is sized for the Islands overnight autonomous loop run under --permission-mode dontAsk (anything not allowed is auto-denied, no prompt). permissions.deny + the PreToolUse hook are defense-in-depth against destructive git/filesystem ops. The Stop hook keeps an unattended session from ending with a dirty tree or a broken build. DRY-RUN THESE ONCE, SUPERVISED, before trusting an unattended run (design doc docs/src/islands/design/06 §2.3).",
"permissions": {
"allow": [
"Read",
"Grep",
"Glob",
"Edit",
"Write",
"NotebookEdit",
"TodoWrite",
"Task",
"Bash(julia:*)",
"Bash(git status:*)",
"Bash(git diff:*)",
"Bash(git log:*)",
"Bash(git show:*)",
"Bash(git add:*)",
"Bash(git commit:*)",
"Bash(git branch:*)",
"Bash(git checkout:*)",
"Bash(git switch:*)",
"Bash(git stash:*)",
"Bash(git pull --ff-only:*)",
"Bash(git push origin feature/islands:*)",
"Bash(gh pr:*)",
"Bash(gh run:*)",
"Bash(gh api:*)",
"Bash(ls:*)",
"Bash(cat:*)",
"Bash(mkdir:*)",
"Bash(cp:*)",
"Bash(mv:*)"
],
"deny": [
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git push origin main:*)",
"Bash(git push origin develop:*)",
"Bash(git push:* main)",
"Bash(git push:* develop)",
"Read(./.env)",
"Read(~/.ssh/**)",
"Read(~/.claude.json)",
"Read(~/.config/gh/**)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/guard-bash.sh\"" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/stop-check.sh\"" }
]
}
]
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*.DS_Store
*.pdf
!docs/resources/*.pdf
!docs/resources/**/*.pdf
*.png
!docs/src/assets/**/*.png
*.jld2
Expand Down
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,15 @@ This workflow is reflected in the modular structure and data flow.

GPEC consists of **seven main modules** organized in `src/`:

**Module naming convention**: `src/` module names are simple, intuitive descriptions
of what the module *is* (`Vacuum`, `Equilibrium`, `ForceFreeStates`, `KineticForces`,
`Islands`) — CamelCase, matching the directory. Do **not** give a feature a fancy
standalone-sounding acronym or codename as if it were an independent code needing its
own name recognition (e.g. no `ISLET`, `PENTRC`-style names for new modules). The
domain the module addresses is the name. Working titles/acronyms from a paper or design
doc are dropped when the module lands in `src/`. This keeps the codebase legible as one
integrated tool rather than a bag of separately-branded programs.

#### Foundation Modules

1. **Splines** (`src/Splines/`) - Numerical interpolation library
Expand Down
1 change: 1 addition & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ makedocs(;
"Forcing Terms" => "forcing_terms.md",
"Perturbed Equilibrium" => "perturbed_equilibrium.md",
"Inner Layer" => "inner_layer.md",
"Islands" => "islands.md",
"Analysis" => "analysis.md",
"Utilities" => "utilities.md"
],
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
14 changes: 14 additions & 0 deletions docs/src/islands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Islands Module

The Islands module is a steady-state, multi-species drift-kinetic solver for the
resonant magnetic island/layer region in tokamaks — the nonlinear analog of
SLAYER, generalizing the Modified Rutherford Equation. It is under active
development (milestone M1). The design documents live under `islands/design/` in
the documentation source tree, and the module conventions in
`src/Islands/CLAUDE.md`.

## API Reference

```@autodocs
Modules = [GeneralizedPerturbedEquilibrium.Islands]
```
31 changes: 31 additions & 0 deletions docs/src/islands/LOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Islands — session LOG

Cross-session memory spine (design doc `06 §2.5`). Read this at session start
together with `QUESTIONS.md`. Append a short entry before every session end:
**what moved / what's blocked / next action**. Newest entries at the top.
Reference `QUESTIONS.md` IDs (`Q<n>`) and ladder IDs (`A1`, `B5a`, …) where
relevant.

---

## 2026-07-08 — Phase A bootstrap (supervised)

- **Moved**: Created the `Islands` submodule skeleton (`src/Islands/Islands.jl`,
empty `module Islands`) and wired it into `src/GeneralizedPerturbedEquilibrium.jl`
(`include` + `import . as` + `export`, last submodule slot before `Rerun.jl`).
Stood up this `LOG.md` and `QUESTIONS.md`, the `.claude` unattended-run
guardrails, the `physics-verifier` subagent, and the M1 launch prompt.
- **Landed CI-green** on `feature/islands` (PR #318): both `runtests` jobs pass
(the wiring is valid — the package loads and the full suite passes) and the
docs build passes. One fix was needed en route: the exported `Islands` module
docstring required a manual page under `checkdocs=:exports`, so
`docs/src/islands.md` (an `@autodocs` block) was added and wired into
`docs/make.jl` (repo-root CLAUDE.md docs-coverage rule).
- **Blocked**: `julia` is not on the automation shell's PATH (no module, not in
`$HOME`) → changes could not be run locally; CI is the only Julia validation
here. See **Q1** — the overnight loop's scratch-clone environment must expose
`julia` or it cannot run tests / meet M1's definition-of-done.
- **Next**: (human) resolve Q1 + one supervised `dontAsk` dry-run of the hooks,
then launch the overnight loop on milestone **M1** (design `00 §M1`) —
phase-space grids + operator-stack skeleton + MMS/AD harness (ladder A1, A2),
no `[VERIFY]` physics coefficients.
41 changes: 41 additions & 0 deletions docs/src/islands/QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Islands — blocker queue (QUESTIONS)

Append-only non-blocking escalation queue (design doc `06 §2.2`). When blocked
on anything the CLAUDE.md forbids guessing — `[VERIFY]` clearances, physics
coefficients, signs, normalizations, convention/doc contradictions, or a tooling
prerequisite — write an entry here **and switch to the next unblocked task**.
Never stall waiting for a human; never resolve silently.

Each entry:
- **ID**: `Q<n>` (monotonic). Commits/PRs reference the IDs they were blocked on
or unblocked by.
- **Status**: `OPEN` / `RESOLVED (by <who>, <date>)`.
- **Context**: what you were doing.
- **Question**: the specific thing a human must decide.
- **Options**: the alternatives considered.
- **Recommendation**: your best guess (not acted on until cleared).
- **Gated work**: what is blocked until this resolves.

The human's recurring job is clearing this queue (and `[VERIFY]` tags), not
supervising sessions.

---

## Q1 — Julia not on the automation shell PATH — OPEN

- **Context**: Phase A bootstrap. Verifying the `Islands` module skeleton loads
(`using GeneralizedPerturbedEquilibrium`) and running the test suite requires
`julia`, but it is not on the non-interactive shell's PATH (no `julia` module,
none in `$HOME`; other users have installs under `/mnt/homes*/…/julia*`).
- **Question**: What is the canonical `julia` invocation for automation on this
cluster, and will the overnight loop's scratch-clone environment expose it?
The unattended loop **cannot run `julia --project` tests without it** — this is
a hard prerequisite for Phase B, not just a local convenience.
- **Options**: (a) a `module load <julia>` line prepended in the loop's shell
rc / launch script; (b) an absolute path to a `juliaup`/`julia` binary the
user owns; (c) install juliaup under `ncl2128` and pin 1.11.
- **Recommendation**: user provides the module/path; bake it into the tmux
launch script (and the Stop hook, which runs the fast test subset). Until
then, CI (`test.yaml`) is the only validation of Julia changes.
- **Gated work**: local verification of every Julia change; the Phase-B overnight
loop's ability to run tests / meet its definition-of-done.
Loading
Loading