From solid-gemc-claude
Orchestrate the full solid_gemc (SoLID experiment) simulation flow from a single natural-language user request. Load whenever the user asks to "simulate", "run", "do a SoLID / solid_gemc / PVDIS / SIDIS / J/psi / He-3 / heavy-gas Cherenkov" study — including one-shot setups like "PVDIS A_PV asymmetry on LD2 at 11 GeV" or "SIDIS heavy-gas Cherenkov yield on He-3". Forces a gated `bin/solid-gemc-run init` (one-time workspace bootstrap) first when the workspace isn't initialized — before any plan — then captures the physics spec across seven fields (including project name), asks targeted clarifying questions when something required is missing, presents a brief plan for approval, and drives the run end-to-end — seed a project subdir from the per-project template if it doesn't exist, then the simulation (the skill writes the GCard edits, runs `solid_gemc` + `evio2root` inside the container, records provenance), then `analyze` for host-side uproot plots. Works on Claude Code and Codex CLI.
How this skill is triggered — by the user, by Claude, or both
Slash command
/solid-gemc-claude:solid-gemcThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill the moment the user asks for a SoLID-flavored
Use this skill the moment the user asks for a SoLID-flavored
simulation. It is the front door for everything this plugin does —
there are no slash commands. The whole workflow runs through one
wrapper, bin/solid-gemc-run: init (workspace bootstrap), the
simulation loop in between, and analyze (host-side uproot plots).
This skill calls those subcommands directly, the same way on Claude
Code and Codex CLI — the wrapper is the single seam. This keeps the
plugin a thin layer over upstream solid_gemc (which already ships
canonical GCards, hgc_study/run.sh, and hgc_moved/ for detector
authoring).
Everything below drives the wrapper through two variables. Resolve
them once at the start of execution, then use $SGC_RUN / $SGC_ROOT
everywhere — never hardcode a platform's plugin-path env var:
# $SGC_RUN — the wrapper, always invoked by ABSOLUTE path; PATH is never
# required. Resolve in order: Claude's CLAUDE_PLUGIN_ROOT → the known Codex
# install location ($CODEX_HOME/plugins/cache/<mkt>/<plugin>/<ver>/bin, newest
# version) → PATH as a last convenience → error.
SGC_RUN="${CLAUDE_PLUGIN_ROOT:+${CLAUDE_PLUGIN_ROOT}/bin/solid-gemc-run}"
if [ ! -x "$SGC_RUN" ]; then
SGC_RUN="$(ls -d "${CODEX_HOME:-$HOME/.codex}"/plugins/cache/solid-gemc-claude/solid-gemc-claude/*/bin/solid-gemc-run 2>/dev/null | sort -V | tail -1)"
[ -x "$SGC_RUN" ] || SGC_RUN="$(command -v solid-gemc-run)" \
|| { echo "[skill] solid-gemc-run not found — invoke it by absolute path, or symlink it onto PATH"; exit 1; }
fi
# $SGC_ROOT — the plugin root (for templates/ and reference/).
SGC_ROOT="$("$SGC_RUN" paths | awk '/^root:/{print $2}')"
The wrapper resolves its own cache and venv (off Claude they sit at
${PLUGIN_ROOT}/cache and ${PLUGIN_ROOT}/venv, next to the wrapper),
so this skill no longer prefixes SOLID_GEMC_CLAUDE_CACHE=… on every
call.
The plugin uses a two-tier workspace:
init was run in) holds
workspace-common files: CLAUDE.md, .gitignore, log.md,
result.md, report.html, plus the solid_gemc/ build tree.CLAUDE.md, log.md,
result.md, report.html. GCards, run outputs, analysis scripts,
and (if applicable) custom-detector Perl generators all live
directly in the project subdir — no enforced subdir split, mirroring
the flat style of upstream solid_gemc/analysis/hgc_study/.Per-run outputs land at <project>/runs/<id>/.
init (Step 0 — gated once, before planning)
→ ensure <project>/ subdir exists (seed from templates/workspace/ if not)
→ pick a GCard from solid_gemc/script/ (canonical) or solid_gemc/analysis/*/
→ copy to <project>/<preset>.gcard + apply batch overrides
→ bin/solid-gemc-run exec "solid_gemc <gcard>" (run gemc; emits out.evio)
→ bin/solid-gemc-run exec "evio2root -INPUTF=out.evio -R=flux" (post-convert; -R=flux publishes the raw integrated bank — see step 3d)
→ write <project>/runs/<id>/{gcard.gcard, out.evio, out.root, log.txt, config.json}
→ solid-gemc-run analyze <project>/runs/<id> (host-side uproot)
init is one-shot per workspace and idempotent. Step 0 force-
checks the workspace state at the very front — before spec-capture
and before any plan — and runs init (after a one-time confirm) if
anything is missing. init is a prerequisite for everything else
this skill does, so it is resolved first; Step 3a only re-asserts the
post-conditions defensively.
There is no bring-your-own-binary alternative for solid_gemc. If the user needs something the canonical configs don't cover, the divergence point is the GCard — they pick a closer canonical and edit it, or hand-author one. The binary, the geometry tree, and the physics list all come from upstream solid_gemc.
Two hard rules that bound this skill's execution. Both apply every time the skill activates, no exceptions outside the narrow carve-outs at the bottom of each section.
This skill never creates files or runs commands without an
explicit AskUserQuestion "Approve and run" choice picked in the
current turn — full stop. Not when the user says "go". Not when
the user says "yes". Not when the user says "no clarifying
questions". Not when the user says "just run it" or "skip the
approval". Not when the initial message looks fully specified.
The one command that runs before this gate is init, and it has
its own dedicated approval in Step 0 (a separate "Run init now"
confirm). Approving init there does not authorize the simulation
run — every file write and every solid_gemc invocation still waits
for the Step 2 "Approve and run" choice. Two independent gates, never
collapsed into one.
Your interpretation of "PVDIS LD2 11 GeV 10000 events default
analysis" is one of many. You might pick the wrong canonical
GCard (solid_PVDIS_LD2_moved_full vs solid_PVDIS_LD2_simple),
default to a wrong analysis shape, seed a project subdir with an
unintended name, or pick wrong defaults for materials / mirror
tilt / focal length / sensor QE. Showing your interpretation first
surfaces those misreads cheaply, before any artifact is written.
These are two different things, governed by two different rules. The user can waive one but not the other.
| Question type | What it covers | Waivable by user? |
|---|---|---|
| Clarifying (step 1) | Ambiguous spec fields — which GCard variant, what beam energy, which project name | Yes — if the user says "fill in sensible defaults" or "no clarifying questions", skip these and fill defaults |
| Approval gate (step 2) | Final execution authorization for THIS plan in THIS turn | Never. Mandatory regardless of what the user said about questions. |
If the user says "stop asking clarifying questions and just run
my study": fill in defaults instead of asking, present the
resulting plan, then still gate on a final AskUserQuestion
with Approve / Edit / Plan-only options. The user can pick
"Approve and run" in one click — cost to them: a few seconds. Cost
of skipping: a wrong run, mis-named project, or hours of
re-doing work because a default was wrong.
AskUserQuestion tool call. On Codex (or any harness
without AskUserQuestion) it is an explicit, numbered
Approve / Edit / Plan-only question the user must answer before
you proceed — the requirement (an explicit per-turn pick before
any write) is identical; only the mechanism differs. The user
must pick an option.AskUserQuestion, a plain-language "Approve and run" / "1" /
"looks good, go" answering the presented options counts — the
structured-choice rule is about what the plan offered, not about
forcing the user through an extra hoop they already cleared. (This
changes nothing above: a "go" from before the plan, or carried
from an earlier turn or a different plan, still does not count.)AskUserQuestion with three options → wait for the explicit
option pick → then and only then proceed to step 3.If your inner monologue contains any of these, stop and fire the gate before proceeding:
"The user said no clarifying questions, so I'll just run it." (Wrong: clarifying questions ≠ approval gate.)
"The plan is on screen as text, so the user can read it before I act." (Wrong: text ≠ tool call. Without
AskUserQuestion, the user has no structured way to redirect.)
"The user already said 'go' / 'yes' in their first message." (Wrong: pre-emptive authorization before seeing the plan does not count.)
"This is the third turn in a row of the same plan, the user obviously approves." (Wrong: every plan, every turn, gets its own gate.)
"It's faster to just do it and ask forgiveness." (Wrong: every wrong run is more expensive than the 3 seconds the gate costs.)
Only these two cases skip the gate, and they're carve-outs for no new artifacts being created:
(a) Pure re-run of an existing
<project>/<preset>.gcard that the user explicitly
named — the user has already seen and approved this GCard's
content in a prior gated orchestration. The re-run produces a
new runs/<id>/ but uses the already-approved GCard.
(b) analyze against an existing runs/<id>/ — no new
simulation artifacts; just plots from an existing ROOT file.
Neither "the user said no questions" nor "the user said go already" nor "the user is annoyed" nor "the plan is obvious" qualifies as an exception. If you're tempted to add a new exception, you're wrong — fire the gate.
<project>/log.mdEvery orchestration leaves a verbatim trail in
<project>/log.md. The trail is what makes future Claude
sessions (or a future human reader) able to tell what was asked
versus what was inferred — paraphrasing erases that distinction.
A complete log entry has four sections, prepended as a single
dated block at the top of <project>/log.md:
## YYYY-MM-DD HH:MM UTC — <one-line headline>
### User input (verbatim)
> <user message 1, quoted verbatim — block-quote with `>` prefix>
>
> <user message 2 if the spec was revised mid-orchestration>
>
> <…every user message in this orchestration cycle, in order>
### Final plan (the approved version)
- Project name: <…>
- Physics goal: <…>
- SoLID config: <…>
- Beam: <…>
- GCard: <preset + parameter overrides>
- Output: <…>
- Analysis: <…>
Steps (the seven-field plan you presented in step 2):
1. <…>
2. <…>
…
Defaults applied (only if any):
- <…>
### Decision
<"Approved and run" | "Edited spec to …, then approved" | "Plan only — did not run">
### Outcome
- Run id: <id> (or "n/a — plan only" or "stopped at step <N>: <reason>")
- Status: <succeeded | failed at step <N> with <reason>>
- Notes: <one or two lines: what worked, what surprised, what's next>
The four sections are mandatory. Specifically:
> block-quote). Do not summarize. If the
user revised the spec, capture all revisions in order, not
just the final ask.AskUserQuestion was picked, and any spec edits the user
made along the way.<project>/runs/<id>/log.txt.When the log entry is written: at the very end of the
orchestration, after analyze completes (or after the
unrecoverable failure that stopped the flow). If a crash leaves
the run dir incomplete, still write the log entry with
Outcome: stopped at step <N>. Never skip the log entry — a
silent run is the worst possible outcome.
Also append a one-line entry to the workspace-level log.md
(the lightweight cross-project index at the workspace root):
YYYY-MM-DD — <project>: <one-line milestone> plus a link to
<project>/log.md for detail.
This rule has no exceptions. Even the carve-out cases above
(pure re-runs, analyze alone) log a short entry — at minimum
the user's request + the run id + the outcome.
Trigger on any of:
run.sh verbatim, recommend
bin/solid-gemc-run shell and skip the skill flow.Do not load this skill when:
<project>/runs/<id>/out.root
and only wants plots — run bin/solid-gemc-run analyze <run> directly.log.txt + config.json and reason from there.<project>/<preset>.gcard and just wants a re-run —
execute the run loop in step 3 below directly without re-running
the gap-check.Co-existence with geant4_claude/skills/geant4. SoLID-specific
vocabulary (PVDIS, SIDIS, solid_gemc, J/psi, He-3, the
SoLID subsystem names) should keep description-match arbitration
clean. If a user request mentions "Geant4" or "GDML" or "main.cc"
without any SoLID context, prefer the geant4 skill. If a request
mentions both ("simulate PVDIS using Geant4"), this skill wins —
SoLID is more specific.
init is a hard precondition for the entire skill — it runs before
spec-capture, before clarifying questions, and before any plan is
drafted. A plan written against a workspace with no cloned
solid_gemc/ is speculative (the ~20 canonical GCard names can't be
verified), and every real run needs the built binary regardless. So
the skill resolves init first, on every activation.
Resolve the runtime ($SGC_RUN, see "Locating the runtime"), then
force-check the workspace state at cwd:
init_needed=0
for f in CLAUDE.md .gitignore log.md result.md report.html; do
[[ -f "$f" ]] || { init_needed=1; break; }
done
[[ -d solid_gemc/.git ]] || init_needed=1
[[ -x solid_gemc/source/2.9/solid_gemc ]] || init_needed=1
Already initialized (init_needed=0): say nothing, proceed
straight to Step 1.
Not initialized (init_needed=1): fire a dedicated init gate
before doing anything else — an AskUserQuestion (or, on a
harness without it, a numbered question the user must answer) that
states plainly:
This workspace isn't initialized.
initwill pull the ~1.7 GB JLabCE.sif, clonesolid_gemc, and run two scons builds — several minutes, one-time. Run it now?
- Run init now — bootstrap, then continue.
- Cancel — stop here; nothing downloaded, no files written.
On Run init now: "$SGC_RUN" init, wait for it to finish, then
re-check the post-conditions above. If init returns non-zero or any
post-condition still fails, stop — report the last 20 lines of
init's output; do not proceed. On Cancel: stop entirely — do
not capture the spec or draft a plan.
This gate is the approval for init only; it is independent of the
Step 2 run-approval gate (see non-negotiable 1). The forced-init-first
rule has no waiver — it holds even when the user only wants a plan
("just sketch it"), because the plan is worth more drafted against the
real cloned solid_gemc/. The sole carve-outs are the two in
non-negotiable 1's "Narrow exceptions" (a pure re-run of an
already-approved GCard, and analyze against an existing run) — both
already assume an initialized workspace, so Step 0's check passes
trivially.
A working solid_gemc simulation needs all seven fields below. Read the user's message and, for each, decide whether they specified it, whether a default is safe, or whether you must ask.
| Field | What it is | Example |
|---|---|---|
| Project name | The <name>/ subdir under the workspace root where this study lives | pvdis_ld2_aPV, sidis_he3_hgc, jpsi_lh2_acceptance, cherenkov_radius_scan |
| Physics goal | What's being measured / counted | "PVDIS A_PV asymmetry vs Q²", "HGC photoelectron yield vs Cherenkov radius", "J/psi recoil acceptance on LH2" |
| SoLID config | Magnet + spectrometer configuration that fixes the geometry | "PVDIS, LD2 target, full magnet config", "SIDIS He-3, heavy-gas Cherenkov in", "J/psi LH2 simple" |
| Beam | Particle, energy, event count | "11 GeV e-, 10000 events" |
| GCard | Preset from solid_gemc/script/ or solid_gemc/analysis/*/, with any parameter overrides | solid_PVDIS_LD2_moved_full.gcard (canonical), N=10000, OUTPUT=evio,out.evio |
| Output | gemc 2.9 writes EVIO natively (the build has no ROOT writer); the skill post-converts to ROOT via evio2root so uproot analysis works the same. | <project>/runs/<id>/out.evio (raw) + out.root (converted) |
| Analysis | Plots / numbers to produce | "auto-histogram all numeric branches", "asymmetry binned in Q²", "PE yield vs radius via custom script in <project>/" |
solid_gemc/script/. PVDIS vs SIDIS vs J/psi are different
physics; LD2 vs LH2 vs ³He vs NH3 are different targets. Never
pick one without explicit user choice — list available presets
and ask.For each missing required field, ask one focused question via
AskUserQuestion for multi-option fields, or plain prose for
one-line answers. Tell the user which field is missing and why
a default is not safe. Don't chain a bunch of guesses together.
The project name becomes a directory under the workspace root and
is interpolated into cp/mkdir paths, the run dir, and the
unquoted config.json heredoc in step 3e. An unconstrained
name (/, .., whitespace, quotes, $(), backticks, a leading
-, or empty) can write outside the workspace, corrupt the
provenance JSON, or inject into later shell. So the resolved
project name must match exactly:
^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}$
One anchored rule closes every vector: no / (no traversal),
first char alnum/_ (so no leading - flag injection, no hidden
dir, and ./.. cannot match), no whitespace or shell
metacharacters, length 1–64.
_, strip a leading non-[A-Za-z0-9_],
trim to 64. If slugification empties it, use solid_run.Step 3b re-checks this with a hard fail-closed guard before the first filesystem write — that guard, not this prose, is the enforceable line of defense.
pvdis_ld2_aPV. Confirm with the
user once in the plan. If a same-named <project>/ already
exists in the workspace, default to using it (the skill
adds runs to its runs/); ask only if the user
appeared to want a fresh project.solid_PVDIS_LD2_moved_full.evio,out.evio. gemc 2.9 in JLabCE 2.5
supports only evio and txt natively (no ROOT writer); the
skill post-converts EVIO → ROOT via evio2root so the analyze
step reads out.root the same way as any TTree-based file. If the
user explicitly asks for txt output, warn that analyze can't
auto-plot it.USE_GUI=1, the skill flips it.hgc_study flowIf the user asks specifically about "heavy-gas Cherenkov study",
"HGC mirror radius study", "compare He-3 vs NH3 HGC", or anything
that points at solid_gemc/analysis/hgc_study/: that's an upstream
ready-made workflow with its own load.sh / run.sh /
analysis.C. Two ways in:
hgc_study_<variant>/), copy one of the hgc GCards into it,
run through this skill. The analyze step gives
host-side uproot plots from the EVIO → ROOT conversion.bin/solid-gemc-run shell drops the user
into a tcsh prompt with the env set; they
cd solid_gemc/analysis/hgc_study and run ./run.sh. Their
analysis is upstream's .C scripts (run via
bin/solid-gemc-run root <.C>).Mention both in the plan when the request maps onto hgc_study; let the user pick.
Once the spec is complete, show the user a compact plan in this shape (no headings beyond what's here, no preamble, no "great, here's what I'll do"):
Plan: <one-sentence description of the simulation>
Spec
- Project name: <…>
- Physics goal: <…>
- SoLID config: <…>
- Beam: <…>
- GCard: <preset name + parameter overrides>
- Output: <<project>/runs/<id>/out.root | other if user asked>
- Analysis: <auto-plots | custom script in <project>/ | hgc_study upstream>
Steps (workspace already initialized in Step 0)
1. seed <project>/ from templates/workspace/. (skip if already there)
2. copy <project>/<preset>.gcard from solid_gemc/script/<preset>.gcard (or .../analysis/.../<preset>.gcard); apply USE_GUI=0 + OUTPUT=evio,out.evio + N=<n>
3. <only if user wants non-default beam/physics:> edit <project>/<preset>.gcard
4. bin/solid-gemc-run exec "solid_gemc <abs gcard> -OUTPUT=evio,<abs run dir>/out.evio" (from the gcard's upstream dir)
then bin/solid-gemc-run exec "evio2root -INPUTF=out.evio -R=flux" (from <project>/runs/<id>/; -R=flux requests the raw bank for the flux detector)
write <project>/runs/<id>/{gcard.gcard, out.evio, out.root, log.txt, config.json}
5. solid-gemc-run analyze <project>/runs/<id>
<one line: auto-plots | custom <project>/<id>.py | container-side ROOT macro>
Defaults applied
- <only list defaults you actually filled in; skip section if none>
Open questions / risks
- <only list real ones — non-standard beam, missing canonical GCard
for the requested config, output format that bypasses the analyze
path, etc. Don't manufacture caveats.>
Then use AskUserQuestion with three options. This gate is
mandatory — present it even when the user's initial message
already said "go" or "yes" (see "Non-negotiable: plan first,
approval gate always" above).
Wait for the user's explicit "Approve and run" choice. Do not start writing files or running commands before that choice appears. A pre-emptive "go" earlier in the conversation does not count — the plan must be on screen in this turn before authorization is meaningful.
Only after the user picks "Approve and run". Each sub-step has a post-condition check; if it fails, stop and report.
init was already ensured in Step 0 (gated and run before the plan), so this is a cheap defensive re-check, not a second init. Re-verify the same post-conditions:
ok=1
for f in CLAUDE.md .gitignore log.md result.md report.html; do
[[ -f "$f" ]] || ok=0
done
[[ -d solid_gemc/.git ]] || ok=0
[[ -x solid_gemc/source/2.9/solid_gemc ]] || ok=0
If ok=0 here, the workspace lost its initialized state between
Step 0 and now (rare — e.g. a long session where files were moved).
Stop and report — do not silently re-run init mid-execution;
surface it so the user can re-trigger Step 0. The .sif cache is
likewise assumed present from Step 0 (bin/solid-gemc-run info
reports a path, not [not pulled]).
PROJECT=<resolved from spec>
# Fail-closed safety guard — the enforceable line of defense for the
# Step 1 "Project name contract". Runs BEFORE the first filesystem
# write because $PROJECT also flows into mkdir/cp paths, RUN_DIR, and
# the unquoted config.json heredoc in step 3e. Never weaken or skip.
if ! printf '%s' "$PROJECT" | grep -Eq '^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}$'; then
echo "[skill] refusing unsafe project name: '${PROJECT}'" >&2
echo "[skill] must match ^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}\$ (no /, .., whitespace, quotes, shell metachars, leading -)" >&2
exit 1
fi
if [[ ! -d "$PROJECT" ]]; then
cp -r "${SGC_ROOT}/templates/workspace/." "$PROJECT/"
fi
If the guard trips, stop — do not write anything, return to step 1, and get a contract-compliant name from the user. A tripped guard means step 1's slugify/reject logic was skipped; that is the bug, not the name.
Post-condition: <project>/ exists with CLAUDE.md, log.md,
result.md, report.html (flat — no enforced subdirs). If it
pre-existed, leave it alone — never clobber a user's project files.
Resolve the preset against solid_gemc/script/ first, then
solid_gemc/analysis/*/. Copy to <project>/<preset>.gcard.
Apply batch overrides only inside the live <gcard>…</gcard>
block (canonicals often carry a <!-- comment out … --> example
block; leave it alone). The robust regex strips any existing
OUTPUT / N / USE_GUI lines (the
name="USE_GUI" value=... double-space variant in some
canonicals is matched by [^>]*), then appends the overrides at
the bottom of the live block:
PRESET=<resolved>
SRC=<solid_gemc/script/solid_${PRESET}.gcard or under solid_gemc/analysis/*/>
DEST="${PROJECT}/$(basename "$SRC")"
SOURCE_DIR=$(dirname "$SRC") # needed at run time; gemc 2.9 resolves <detector name="..."> relative to cwd, not the GCard's location
cp "$SRC" "$DEST"
python3 - "$DEST" "${N_EVENTS:-100}" "out.evio" "0" <<'PY'
import re, sys, pathlib
path, n_events, output_name, gui_keep = sys.argv[1:5]
strip_keys = ['OUTPUT', 'N'] + ([] if gui_keep == '1' else ['USE_GUI'])
text = pathlib.Path(path).read_text()
m = re.search(r'(<gcard>)(.*?)(</gcard>)', text, re.DOTALL)
if not m:
sys.exit(f"[skill] no <gcard>...</gcard> block in {path}")
head, body, tail = m.groups()
body = re.sub(
r'^[ \t]*<option name="(' + '|'.join(strip_keys) + r')"[^>]*/>[ \t]*\n',
'', body, flags=re.MULTILINE)
adds = [
f'<option name="OUTPUT" value="evio,{output_name}"/>',
f'<option name="N" value="{n_events}"/>',
]
if gui_keep != '1':
adds.append('<option name="USE_GUI" value="0"/>')
new_body = body.rstrip() + '\n' + '\n'.join(adds) + '\n'
pathlib.Path(path).write_text(text[:m.start(2)] + new_body + text[m.end(2):])
PY
"$SGC_RUN" validate-gcard "$DEST"
Post-condition: <project>/<preset>.gcard exists; the
live <gcard> block contains the three overrides; validate-gcard
returns 0.
Three important disciplines: (1) cwd-relative geometry lookup —
gemc 2.9 resolves <detector name="…"> from the process cwd, not
from the GCard's path, so we cd "$SOURCE_DIR" (the dir holding
the canonical's geometry siblings) before invoking solid_gemc.
(2) Absolute paths for the GCard and OUTPUT, since cwd has
changed. Pipe-exit-code capture is portable across bash and zsh
via a tempfile (the harness shell may be either).
(3) evio2root -R=flux — without -R=, evio2root publishes only the
digitized bank for each detector, and the flux hitprocess's
digitized output is just hitn + id (a slim ~200 KB ROOT with no
Edep, no positions, no times — useless for analysis). Adding
-R=flux publishes the raw integrated bank for the flux
detector with all 27 fields (Edep, x/y/z, t, pid, …). If a study
uses non-flux hitprocesses that also need raw banks, append them
comma-separated: -R=flux,Hits,….
RUN_ID=$(date -u +%Y%m%d-%H%M%S)-$(head -c 12 /dev/urandom | base32 | tr 'A-Z' 'a-z' | head -c 6)
RUN_DIR="${PROJECT}/runs/${RUN_ID}"
mkdir -p "$RUN_DIR"
cp "$DEST" "$RUN_DIR/gcard.gcard"
WORKSPACE_ABS=$(readlink -f .)
RUN_DIR_ABS="${WORKSPACE_ABS}/${RUN_DIR}"
GCARD_ABS="${WORKSPACE_ABS}/${RUN_DIR}/gcard.gcard"
START_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ); START_EPOCH=$(date +%s)
GEMC_EC_FILE=$(mktemp)
( cd "$SOURCE_DIR" && \
SoLID_GEMC="${WORKSPACE_ABS}/solid_gemc" \
"$SGC_RUN" exec \
"solid_gemc '${GCARD_ABS}' -OUTPUT='evio,${RUN_DIR_ABS}/out.evio'"; \
echo $? > "${GEMC_EC_FILE}" ) 2>&1 | tee "${RUN_DIR_ABS}/log.txt"
GEMC_EXIT=$(cat "${GEMC_EC_FILE}"); rm -f "${GEMC_EC_FILE}"
EVIO2ROOT_EXIT=0
if [[ "$GEMC_EXIT" = "0" && -f "${RUN_DIR_ABS}/out.evio" ]]; then
EVIO_EC_FILE=$(mktemp)
( cd "$RUN_DIR_ABS" && \
SoLID_GEMC="${WORKSPACE_ABS}/solid_gemc" \
"$SGC_RUN" exec \
"evio2root -INPUTF=out.evio -R=flux"; \
echo $? > "${EVIO_EC_FILE}" ) 2>&1 | tee -a "${RUN_DIR_ABS}/log.txt"
EVIO2ROOT_EXIT=$(cat "${EVIO_EC_FILE}"); rm -f "${EVIO_EC_FILE}"
fi
END_ISO=$(date -u +%Y-%m-%dT%H:%M:%SZ); END_EPOCH=$(date +%s)
EXIT_CODE=$([ "$GEMC_EXIT" = "0" ] && echo "$EVIO2ROOT_EXIT" || echo "$GEMC_EXIT")
Post-condition: <project>/runs/<id>/{gcard.gcard, out.evio, out.root, log.txt} exist and are non-empty;
EXIT_CODE is 0.
config.jsonSIF_NAME=$(grep '^SIF_NAME=' "$SGC_RUN" | head -1 | cut -d'"' -f2)
SOLID_GEMC_SHA=$(cd solid_gemc && git rev-parse HEAD 2>/dev/null || echo unknown)
GEMC_VERSION_PINNED=$(grep '^GEMC_VERSION=' "$SGC_RUN" | head -1 | cut -d'"' -f2)
N_EVENTS=$(grep -oE '<option name="N" value="[^"]+"' "$RUN_DIR/gcard.gcard" | head -1 | sed 's/.*value="//; s/"$//')
python3 - "$RUN_DIR/config.json" <<PY
import json, sys, pathlib
d = {
"run_id": "${RUN_ID}",
"project": "${PROJECT}",
"gcard_source": "${DEST}",
"gcard_frozen": "${RUN_DIR}/gcard.gcard",
"source_dir": "${SOURCE_DIR}",
"n_events": "${N_EVENTS}",
"sif_name": "${SIF_NAME}",
"solid_gemc_repo": "https://github.com/JeffersonLab/solid_gemc",
"solid_gemc_sha": "${SOLID_GEMC_SHA}",
"gemc_version": "${GEMC_VERSION_PINNED}",
"start_utc": "${START_ISO}",
"end_utc": "${END_ISO}",
"wall_seconds": ${END_EPOCH} - ${START_EPOCH},
"gemc_exit_code": ${GEMC_EXIT},
"evio2root_exit": ${EVIO2ROOT_EXIT},
"exit_code": ${EXIT_CODE},
"command": "solid_gemc <gcard> -OUTPUT=evio,<run>/out.evio; evio2root -INPUTF=out.evio -R=flux",
"cwd_gemc": "${SOURCE_DIR}",
"cwd_evio2root": "${RUN_DIR}",
}
pathlib.Path(sys.argv[1]).write_text(json.dumps(d, indent=2) + "\n")
PY
<project>/runs/<id>/config.json is the provenance
record. Treat the run dir as immutable from this point on.
Run "$SGC_RUN" analyze <project>/runs/<id> — host-side uproot,
auto-plots numeric branches into PNGs alongside out.root. For
asymmetries / fits / multi-run comparisons, write a script under
<project>/.
If a sub-step's post-condition check fails, stop. Report the
failure (last 20 lines of the run's log.txt or the command's
stderr) and ask the user how to proceed. Do not silently retry,
do not paper over the error, do not move to the next step.
Apply Non-negotiable #2 at the top of this skill (log every
user input + final plan to <project>/log.md). The four-section
block (User input verbatim → Final plan → Decision → Outcome)
gets prepended at the top of <project>/log.md at the very end
of step 3, after analyze completes (or after the failure that
stopped the flow). Don't skip it; even a crashed run gets a log
entry with Outcome: stopped at step <N>.
In addition: update <project>/result.md with the key numbers
and plot paths after analysis (only for runs that produced
notable results — skip for trivial smokes), then refresh
<project>/report.html so the rendered summary (overview,
configuration table, headline figures, run-index row) stays in
sync with result.md. Use relative paths into runs/<id>/ for
embedded plots; do not inline images as base64 — the run dir is
gitignored but it lives next to the report. Append a one-line
entry to the workspace-level log.md (the cross-project index)
pointing at <project>/log.md for detail, and add/update the
project's card + any cross-project headline plot in the
workspace-level report.html.
End with a single block:
Done.
- Project: <project>
- Run id: <id>
- Output: <project>/runs/<id>/out.root
- Plots: <project>/runs/<id>/<plot1>.png, … (or "(histogram-only output; no auto-plots)")
- Updated: <project>/log.md, <project>/result.md, <project>/report.html, log.md, report.html
Next: <one concrete suggestion — vary the beam energy, scale up n_events,
try a different canonical config, run a custom <project>/<id>.py>
No "let me know if you have more questions". No emoji. No recap of the plan — the plots and numbers are the recap.
bin/solid-gemc-run — the single seam this skill drives. init
bootstraps the workspace (image + clone + build); analyze does
uproot inspect + auto-plot; exec / validate-gcard / shell /
root cover everything that needs the JLabCE 2.5 container. There
are no slash commands — this wrapper is the only command surface.templates/CLAUDE.md — workspace-wide rules loaded into
Claude's context for every session in the workspace.templates/workspace/CLAUDE.md — per-project rules; copied
into each project subdir at seed time.https://gemc.jlab.org — GCard option reference (BEAM_P,
PHYSICS, OUTPUT, N, …) for any user request that needs
non-canonical overrides. Plugin-local mirror of the SoLID-side
"how to use gemc" guide lives in
reference/gemc_simulation_general_note.md.reference/gemc.md and reference/solid_gemc.md — source-
level digests of the gemc 2.9 framework and the SoLID hit
processors. Consult when a user hits an unexpected behavior in
geometry loading, hit-processor output, build flags, or option
overrides.solid_gemc/analysis/hgc_study/ — the ready-made HGC
study workflow (canonical worked example for config + run +
analyze). GCards + run.sh + ROOT analysis scripts.
Recommended as a first-run example.solid_gemc/geometry/hgc_moved/ — the canonical
worked example for custom detector authoring (factory text
files via <detector name="..." factory="TEXT" ...>). Has a
readme.md plus the full Perl-generator → text-file pipeline.
If a user needs to write a new detector, point them here — the
plugin has no detector-authoring surface; follow upstream.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub zhaozhiwen/solid_gemc_claude --plugin solid-gemc-claude