From 1032d2bddd56138507a04dc1f796174e38233819 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 20:22:40 +0000 Subject: [PATCH 1/2] Add agentic-coding scaffolding (AGENTS.md, CLAUDE.md, session hook) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the repo first-class for AI coding agents (Claude Code, opencode, Qwen Code, Codex) so they share one project brief and a reproducible build/test/lint loop: - AGENTS.md: single source of truth — build/test/lint commands, repo map, API model, enforced conventions, generated-data and thread-safety gotchas, definition of done, and how to point opencode at a self-hosted Qwen (OpenAI-compatible) endpoint. - CLAUDE.md: imports AGENTS.md so Claude-specific tooling reads the same brief. - .claude/hooks/session-start.sh: SessionStart hook for Claude Code on the web — configures + builds the debug preset, installs the editable Python binding with dev deps, and exports LIBDEDX_SO so ctest/clang-tidy/pytest work in-session. Idempotent; remote-only. - .claude/settings.json: registers the hook and pre-allows the safe, repeated dev commands (cmake/ctest/ruff/pytest/clang-*/valgrind/git status). https://claude.ai/code/session_01PKNN8m7LaiTseSm1RYkudy --- .claude/hooks/session-start.sh | 43 ++++++++++ .claude/settings.json | 36 +++++++++ AGENTS.md | 139 +++++++++++++++++++++++++++++++++ CLAUDE.md | 6 ++ 4 files changed, 224 insertions(+) create mode 100755 .claude/hooks/session-start.sh create mode 100644 .claude/settings.json create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..926618f --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# SessionStart hook for libdedx. +# +# Prepares a fresh Claude Code on the web container so that builds, tests, +# linters and the Python bindings work immediately in-session: +# - configures + builds the debug preset (produces compile_commands.json for +# clang-tidy and build/src/libdedx.so for the Python ctypes binding) +# - installs the Python package in editable mode with dev dependencies +# - exports LIBDEDX_SO so `pytest` under python/ can find the shared library +# +# Safe to run multiple times. Only runs in the remote (web) environment. +set -euo pipefail + +# Only set up the toolchain in Claude Code on the web; local checkouts are +# assumed to already have a working dev environment. +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "${CLAUDE_PROJECT_DIR:-$(pwd)}" + +echo "[session-start] Configuring debug preset..." +cmake --preset debug + +echo "[session-start] Building debug preset..." +cmake --build --preset debug --parallel + +# Editable install of the ctypes binding plus dev tools (ruff, pytest). +# Ubuntu's system Python is externally managed (PEP 668); fall back to +# --break-system-packages when a normal editable install is refused. +echo "[session-start] Installing Python package (editable, with dev deps)..." +python3 -m pip install -e "python[dev]" \ + || python3 -m pip install --break-system-packages -e "python[dev]" + +# Point the Python test suite at the freshly built shared library so that +# `pytest` works without any manual setup. +LIBDEDX_SO="${CLAUDE_PROJECT_DIR:-$(pwd)}/build/src/libdedx.so" +export LIBDEDX_SO +if [ -n "${CLAUDE_ENV_FILE:-}" ]; then + echo "export LIBDEDX_SO=\"${LIBDEDX_SO}\"" >> "$CLAUDE_ENV_FILE" +fi + +echo "[session-start] Done. Build, tests (ctest), clang-tidy and pytest are ready." diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..f39a2af --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,36 @@ +{ + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + }, + "permissions": { + "allow": [ + "Bash(cmake --preset:*)", + "Bash(cmake --build:*)", + "Bash(cmake -S:*)", + "Bash(ctest:*)", + "Bash(cmake --install:*)", + "Bash(clang-format:*)", + "Bash(clang-tidy:*)", + "Bash(ruff check:*)", + "Bash(ruff format:*)", + "Bash(python -m pytest:*)", + "Bash(python3 -m pytest:*)", + "Bash(pytest:*)", + "Bash(valgrind:*)", + "Bash(python3 tools/dat2c.py:*)", + "Bash(python3 tools/pdf2dat.py:*)", + "Bash(git status:*)", + "Bash(git diff:*)", + "Bash(git log:*)" + ] + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6b5fc8c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,139 @@ +# AGENTS.md + +Project brief for AI coding agents (Claude Code, opencode, Qwen Code, Codex, …). +This is the single source of truth; `CLAUDE.md` imports it. Keep it current when +build commands, conventions or architecture change. + +## What this project is + +`libdedx` is a C11 library for charged-particle **stopping power** (dE/dx) +calculations — the energy loss of a charged particle per unit length of a +material. All tabulated stopping-power data is **embedded into the library**; +no external `.bin` files are needed at runtime. A `ctypes`-based Python binding +lives in `python/`. + +## Build, test, lint — the inner loop + +C library (CMake 3.21+, C11 compiler): + +```bash +cmake --preset debug # configure (also writes compile_commands.json) +cmake --build --preset debug --parallel # build into ./build +ctest --preset debug # run the C test suite +``` + +Other presets: `release` (-> `build-release/`), `coverage` (-> `build-coverage/`). + +Static analysis / formatting (both run in CI with warnings-as-errors): + +```bash +clang-format -i # style is enforced; see .clang-format +clang-tidy -p build # config in .clang-tidy, WarningsAsErrors: '*' +``` + +Memory checks (CI runs valgrind on the bethe test; do the same for memory-touching changes): + +```bash +valgrind --leak-check=full --track-origins=yes --error-exitcode=1 ./build/tests/test_bethe_ext00 +``` + +Python binding (`python/`): + +```bash +pip install -e "python[dev]" # editable install + ruff + pytest +export LIBDEDX_SO="$PWD/build/src/libdedx.so" # point ctypes at the built .so +ruff check python # lint (line-length 120, rules E/F/W) +cd python && python3 -m pytest -q # tests (use `-m pytest`, as CI does) +``` + +In Claude Code on the web, `.claude/hooks/session-start.sh` performs the +configure+build, the editable Python install, and exports `LIBDEDX_SO` +automatically, so the loop above is ready at session start. + +## Repository map + +| Path | What lives here | +|------|-----------------| +| `include/` | **Public API** headers (`dedx.h`, `dedx_error.h`, `dedx_tools.h`, `dedx_wrappers.h`, `dedx_elements.h`). The contract. | +| `src/` | Library implementation. Core engine in `dedx.c`; per-program models (`dedx_bethe.c`, `dedx_mstar.c`, `dedx_mpaul.c`); interpolation in `dedx_spline.c`; lookup/data access in `dedx_data_access.c`. | +| `src/data/embedded/` | **Generated** headers compiled into the library. Do not hand-edit. | +| `data/raw/` | Raw source tables + metadata, the regeneration inputs. | +| `tools/` | `dat2c.py` (raw tables -> embedded C headers) and `pdf2dat.py` (ICRU90 PDFs -> `.dat`). | +| `tests/` | CTest suite, one file per program/area (`test_pstar.c`, `test_bethe_ext00.c`, …). | +| `examples/` | Runnable usage samples; CI executes several of them. | +| `python/libdedx/` | ctypes binding (`_api.py`, typed stubs `__init__.pyi`, `py.typed`). | +| `docs/` | Sphinx + Doxygen sources; published to GitHub Pages. | + +## API model (orient before editing) + +- One-call convenience: `dedx_get_simple_stp(ion, target, energy, &err)`. +- Full path: allocate a `dedx_workspace`, fill a `dedx_config` (`program`, `ion`, + `target`), `dedx_load_config()`, then `dedx_get_stp()`; free both. +- A result is selected by a `(program, ion, target)` triple. Programs include + `DEDX_PSTAR`, `DEDX_ASTAR`, `DEDX_MSTAR`, `DEDX_ICRU49/73/73_OLD`, + `DEDX_BETHE_EXT00`, and `DEDX_ICRU` (auto-selects newest embedded ICRU data). +- Energies are in **MeV/nucl** throughout, including the generated data grids. + +## Conventions (enforced — see CONTRIBUTING.md) + +- **Naming/linkage:** public API `dedx_*`; shared internal helpers + `dedx_internal_*`; file-local helpers `static` with short unprefixed names. + Do **not** introduce new `_dedx_*` identifiers (leading underscore). +- **Declarations at top of block**, before any statements (Linux-kernel style). + Inner-block declarations are fine when genuinely local to that scope. +- Formatting and clang-tidy are CI gates with warnings-as-errors — run them + before claiming a change is done. + +## Gotchas an agent will otherwise get wrong + +- **Embedded data is generated, not authored.** To change tabulated values, + edit `data/raw/` and regenerate, never edit `src/data/embedded/*` directly: + ```bash + python3 tools/pdf2dat.py # refresh ICRU90 *.dat in data/raw/ + python3 tools/dat2c.py all # regenerate src/data/embedded/ headers + ``` +- **Not thread-safe.** There is no synchronization around workspace mutation in + `dedx_load_config()` / dataset loading. Never share a `dedx_workspace` across + threads without external locking. Don't assume re-entrancy. +- **Compound fallback semantics.** For compound targets, some programs + (`DEDX_PSTAR`, `DEDX_ASTAR`, `DEDX_MSTAR`) fall back to Bragg/stoichiometric + weighting when the upstream database lacks that compound. A value returned + under a program label is therefore not always a direct upstream value. Preserve + this behavior and its caveats; don't "fix" it into looking like native coverage. +- **`ASTAR` vs `ICRU_ASTAR`** are distinct datasets (same grid/targets, slightly + different values) — not duplicates. See `data/README.md`. + +## Definition of done + +1. `cmake --build --preset debug` is clean. +2. `ctest --preset debug` passes. +3. `clang-format` clean and `clang-tidy -p build` reports nothing (warnings = errors). +4. For memory-touching changes, valgrind is clean. +5. If Python is affected: `ruff check python` clean and `pytest` passes. + +## Using a self-hosted Qwen (or other local model) via opencode + +This repo is model-agnostic: Claude Code reads `CLAUDE.md` (which imports this +file); opencode and Qwen Code read this `AGENTS.md` directly. To drive the repo +with your own Qwen endpoint on custom hardware, add a custom provider to +opencode (`opencode.json` at the repo root or `~/.config/opencode/`). Point it at +your OpenAI-compatible server (vLLM / SGLang / Ollama / LM Studio) — model ids are +passed through unchanged, so they must match what your server exposes: + +```jsonc +{ + "$schema": "https://opencode.ai/config.json", + "provider": { + "my-qwen": { + "npm": "@ai-sdk/openai-compatible", + "options": { "baseURL": "http://:/v1" }, + "models": { "qwen3-coder": {} } + } + } +} +``` + +Then select `my-qwen/qwen3-coder` via `/models`. (opencode defaults Qwen to +temperature 0.55 when unset.) This file is intentionally **not** committed so the +endpoint stays your local choice; the build/test/lint commands above are all the +context any agent needs regardless of the model behind it. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d688995 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,6 @@ +# CLAUDE.md + +Project guidance for Claude Code lives in the shared, tool-agnostic brief so +that Claude, opencode, Qwen Code and other agents all read the same thing. + +@AGENTS.md From 4812c28b59691d503a06036ebc93ef27a3158bfc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 8 Jun 2026 20:30:58 +0000 Subject: [PATCH 2/2] Address Copilot review: explicit ICRU constants, idempotent env append - AGENTS.md: spell out DEDX_ICRU49 / DEDX_ICRU73 / DEDX_ICRU73_OLD as separate constants instead of the collapsed DEDX_ICRU49/73/73_OLD form, which could be mistaken for a single (non-existent) symbol. - session-start.sh: only append `export LIBDEDX_SO=...` to CLAUDE_ENV_FILE when not already present, so repeated hook runs don't duplicate the line. https://claude.ai/code/session_01PKNN8m7LaiTseSm1RYkudy --- .claude/hooks/session-start.sh | 4 +++- AGENTS.md | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh index 926618f..5a778fe 100755 --- a/.claude/hooks/session-start.sh +++ b/.claude/hooks/session-start.sh @@ -36,7 +36,9 @@ python3 -m pip install -e "python[dev]" \ # `pytest` works without any manual setup. LIBDEDX_SO="${CLAUDE_PROJECT_DIR:-$(pwd)}/build/src/libdedx.so" export LIBDEDX_SO -if [ -n "${CLAUDE_ENV_FILE:-}" ]; then +# Idempotent: only append once, so repeated hook runs don't clutter the env file. +if [ -n "${CLAUDE_ENV_FILE:-}" ] \ + && ! grep -qs '^export LIBDEDX_SO=' "$CLAUDE_ENV_FILE"; then echo "export LIBDEDX_SO=\"${LIBDEDX_SO}\"" >> "$CLAUDE_ENV_FILE" fi diff --git a/AGENTS.md b/AGENTS.md index 6b5fc8c..4dd0b1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,8 +70,9 @@ automatically, so the loop above is ready at session start. - Full path: allocate a `dedx_workspace`, fill a `dedx_config` (`program`, `ion`, `target`), `dedx_load_config()`, then `dedx_get_stp()`; free both. - A result is selected by a `(program, ion, target)` triple. Programs include - `DEDX_PSTAR`, `DEDX_ASTAR`, `DEDX_MSTAR`, `DEDX_ICRU49/73/73_OLD`, - `DEDX_BETHE_EXT00`, and `DEDX_ICRU` (auto-selects newest embedded ICRU data). + `DEDX_PSTAR`, `DEDX_ASTAR`, `DEDX_MSTAR`, `DEDX_ICRU49`, `DEDX_ICRU73`, + `DEDX_ICRU73_OLD`, `DEDX_BETHE_EXT00`, and `DEDX_ICRU` (auto-selects the + newest embedded ICRU data). - Energies are in **MeV/nucl** throughout, including the generated data grids. ## Conventions (enforced — see CONTRIBUTING.md)