Sync openmc
Skill itaybnv/openmc-claude-skills/.claude/skills/sync-openmc
OpenMC Python API knowledge base that helps AI write correct neutronics code
npx -y skills add itaybnv/openmc-claude-skills --skill sync-openmcAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Maintenance skill for the openmc-claude-skills knowledge base. Diffs two OpenMC release tags (OLD NEW, e.g. v0.15.2 v0.15.3), maps changed Python source files to the affected KB doc files, writes a per-release migration notes file at skills/openmc/releases/vNEW.md, and updates .last-synced-release. Invoke as: /sync-openmc v0.15.2 v0.15.3. Requires gh CLI (authenticated) and curl. This is a maintainer-only tool; the consumer openmc skill is separate.
SKILL.md
24.1 KB, as published. Nobody here has run it
/sync-openmc — OpenMC KB Sync Command
Maintenance skill for the openmc-claude-skills knowledge base. Run this when a new OpenMC release is published to get a precise, deterministic list of which KB docs need a re-validation authoring pass.
This command is a detector/mapper + migration-note writer. It does NOT auto-edit shipped docs. The cardinal sin is shipping unvalidated content. All doc rewrites require a human-driven authoring pass following the validated-before-commit methodology.
Prerequisites:
ghCLI installed and authenticated (gh auth statusto verify)curlavailable (standard Linux/macOS)condawith theopenmc-envenvironment (OpenMC 0.15.3)- Run from the repo root of
openmc-claude-skills
Invocation
/sync-openmc OLD NEW
Example: /sync-openmc v0.15.2 v0.15.3
OLD and NEW must match the pattern vX.Y.Z (e.g., v0.15.3). Both are validated
before any network request is made.
Workflow
Execute the following steps in order using the Bash tool. Each step is a concrete set of shell commands. Do not skip steps. Do not proceed past Step 1 on a version mismatch.
Step 0 — Pre-flight and argument validation
Run these checks before anything else:
# Extract OLD and NEW from the skill invocation arguments
OLD="$1" # e.g. v0.15.2
NEW="$2" # e.g. v0.15.3
# Validate both args match vX.Y.Z before interpolating into any URL or command
if ! echo "$OLD" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "ERROR: OLD argument '$OLD' does not match the required pattern vX.Y.Z"
echo "Example: /sync-openmc v0.15.2 v0.15.3"
exit 1
fi
if ! echo "$NEW" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "ERROR: NEW argument '$NEW' does not match the required pattern vX.Y.Z"
echo "Example: /sync-openmc v0.15.2 v0.15.3"
exit 1
fi
echo "Syncing: $OLD → $NEW"
# Pre-flight: verify gh is available and authenticated
if ! command -v gh &>/dev/null; then
echo "ERROR: gh CLI not found. Install from https://cli.github.com/ and run 'gh auth login'."
exit 1
fi
gh auth status || { echo "ERROR: gh is not authenticated. Run 'gh auth login' first."; exit 1; }
# Pre-flight: verify curl is available
if ! command -v curl &>/dev/null; then
echo "ERROR: curl not found. Install curl (standard on Linux/macOS)."
exit 1
fi
Security note: $OLD and $NEW are validated to match vX.Y.Z before any
interpolation into URLs or commands, preventing command injection or URL path traversal
(threat T-05-01). All fetched content (release notes body, diff text) is written as data
to files — never passed to eval or unquoted command substitution (threat T-05-03).
Step 1 — Version guard (D-04)
The installed OpenMC in openmc-env must match the KB baseline in .last-synced-release
before diffing. This ensures the sync runs from a known-good baseline.
# Read the KB baseline version (stored with v prefix, e.g. v0.15.3)
KB_VERSION=$(cat .last-synced-release 2>/dev/null || echo "MISSING")
if [ "$KB_VERSION" = "MISSING" ]; then
echo "ERROR: .last-synced-release not found. Expected at repo root."
exit 1
fi
# Get installed OpenMC version (returns without v prefix, e.g. 0.15.3)
INSTALLED=$(conda run -n openmc-env python -c "import openmc; print(openmc.__version__)" 2>/dev/null)
if [ -z "$INSTALLED" ]; then
echo "ERROR: Could not determine OpenMC version from openmc-env conda environment."
echo "Verify: conda run -n openmc-env python -c \"import openmc; print(openmc.__version__)\""
exit 1
fi
# Normalize: strip v prefix from KB_VERSION for comparison
KB_VERSION_STRIPPED="${KB_VERSION#v}"
if [ "$INSTALLED" != "$KB_VERSION_STRIPPED" ]; then
echo "ERROR: Version mismatch — KB is synced to ${KB_VERSION}; installed OpenMC in openmc-env is ${INSTALLED}."
echo "To resolve:"
echo " Option A: Install matching OpenMC: conda install -n openmc-env openmc=${KB_VERSION_STRIPPED}"
echo " Option B: If you have already updated OpenMC, reconcile .last-synced-release to match v${INSTALLED}"
echo "Do not proceed until the versions match."
exit 1
fi
echo "Version guard passed: openmc-env has OpenMC ${INSTALLED}, KB baseline is ${KB_VERSION} — match confirmed."
Do not proceed past this step on a mismatch. The version guard is a hard block, not a warning.
Step 2 — Fetch release notes
Fetch the release notes for the NEW tag from GitHub. This is data-only — the output is written to a variable and then to a file, never passed to eval or unquoted substitution.
echo "Fetching release notes for ${NEW}..."
# Fetch structured release notes (tag, date, body)
RELEASE_JSON=$(gh release view "${NEW}" --repo openmc-dev/openmc \
--json body,tagName,publishedAt 2>/dev/null)
if [ -z "$RELEASE_JSON" ]; then
echo "ERROR: Could not fetch release notes for ${NEW} from openmc-dev/openmc."
echo "Verify: gh release view '${NEW}' --repo openmc-dev/openmc"
exit 1
fi
RELEASE_DATE=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('publishedAt','')[:10])")
RELEASE_BODY=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('body',''))")
echo "Release notes fetched (date: ${RELEASE_DATE})."
Step 3 — Get changed Python files (CRITICAL: use raw .diff URL, not gh api compare)
Do NOT use gh api repos/openmc-dev/openmc/compare/OLD...NEW --jq '.files[].filename'.
That endpoint silently caps at 300 entries. The v0.15.2→v0.15.3 diff spans 751 total
files; the API would miss files beyond position 300. Use the raw .diff URL instead,
which returns all changed files unconditionally.
echo "Fetching changed Python files via raw diff URL (avoids 300-file API cap)..."
# Use raw .diff URL for complete file enumeration
# Filter to openmc/ Python files only (excludes tests/, docs/, src/, setup files)
CHANGED_PY=$(curl -s "https://github.com/openmc-dev/openmc/compare/${OLD}...${NEW}.diff" \
| grep '^diff --git a/openmc/' \
| grep '\.py$' \
| sed 's|diff --git a/||; s| b/.*||')
if [ -z "$CHANGED_PY" ]; then
echo "WARNING: No openmc/*.py files found in the diff. Verify the tag pair is correct."
echo "Check: curl -s 'https://github.com/openmc-dev/openmc/compare/${OLD}...${NEW}.diff' | head -20"
fi
PY_COUNT=$(echo "$CHANGED_PY" | grep -c . || true)
echo "Found ${PY_COUNT} changed openmc/*.py files."
Step 4 — Classify and map changed files to KB docs
For each changed Python file, look up the mapping table below (longest-prefix match wins).
Files with no match are annotated [no-doc-mapped] for human review.
Source → Doc Mapping Table (D-03):
Use longest-prefix matching. A prefix ending in / matches all files under that directory.
For each file in $CHANGED_PY, find the first matching entry below (longest match wins).
# Materials
openmc/material.py → materials.md
openmc/element.py → materials.md
openmc/waste.py → materials.md
# CSG Geometry
openmc/geometry.py → geometry-csg.md
openmc/surface.py → geometry-csg.md
openmc/cell.py → geometry-csg.md
openmc/region.py → geometry-csg.md
# Lattice Geometry
openmc/lattice.py → geometry-lattices.md
openmc/universe.py → geometry-lattices.md
openmc/dagmc.py → geometry-csg.md, geometry-lattices.md
# Settings / Run / Source
openmc/settings.py → settings.md
openmc/source.py → settings.md
openmc/stats/ → settings.md
openmc/trigger.py → settings.md
openmc/volume.py → settings.md
# Tallies
openmc/tallies.py → tallies.md
openmc/filter.py → tallies.md
openmc/filter_expansion.py → tallies.md
openmc/mesh.py → tallies.md, statepoint.md
openmc/weight_windows.py → tallies.md
openmc/tally_derivative.py → tallies.md
# Statepoint / Results
openmc/statepoint.py → statepoint.md
openmc/summary.py → statepoint.md
# Model API
openmc/model/model.py → model.md
# Depletion (directory prefix — matches all openmc/deplete/*.py)
openmc/deplete/ → depletion.md
# MGXS (directory prefix — matches all openmc/mgxs/*.py)
openmc/mgxs/ → mgxs.md
openmc/mgxs_library.py → mgxs.md
# Nuclear Data
openmc/data/library.py → nuclear-data.md
openmc/data/data.py → nuclear-data.md
openmc/data/decay.py → nuclear-data.md
openmc/data/njoy.py → nuclear-data.md
openmc/data/ → nuclear-data.md (catch-all for data/)
# openmc.lib (directory prefix)
openmc/lib/ → openmc-lib.md
# Plots
openmc/plots.py → plots.md
openmc/plotter.py → plots.md
openmc/lib/plot.py → plots.md
# Internal utilities (no doc affected — skip in report)
openmc/_xml.py → [internal]
openmc/checkvalue.py → [internal]
openmc/utility_funcs.py → [internal]
openmc/config.py → [internal]
Apply the mapping table with a Python script to produce a structured result:
# Run the mapping in Python for clean longest-prefix matching
MAPPING_OUTPUT=$(python3 << 'PYEOF'
import sys
# Files changed in this diff (injected at runtime by shell)
import subprocess
changed_raw = subprocess.run(
['bash', '-c', r'''curl -s "https://github.com/openmc-dev/openmc/compare/''' + "$OLD" + r'''...''' + "$NEW" + r'''.diff" | grep '^diff --git a/openmc/' | grep '\.py$' | sed 's|diff --git a/||; s| b/.*||' '''],
capture_output=True, text=True
).stdout.strip().splitlines()
# Mapping table: (prefix, [docs]) — order matters; more specific entries come first
MAPPING = [
# Exact file matches (most specific — listed first)
("openmc/material.py", ["materials.md"]),
("openmc/element.py", ["materials.md"]),
("openmc/waste.py", ["materials.md"]),
("openmc/geometry.py", ["geometry-csg.md"]),
("openmc/surface.py", ["geometry-csg.md"]),
("openmc/cell.py", ["geometry-csg.md"]),
("openmc/region.py", ["geometry-csg.md"]),
("openmc/lattice.py", ["geometry-lattices.md"]),
("openmc/universe.py", ["geometry-lattices.md"]),
("openmc/dagmc.py", ["geometry-csg.md", "geometry-lattices.md"]),
("openmc/settings.py", ["settings.md"]),
("openmc/source.py", ["settings.md"]),
("openmc/trigger.py", ["settings.md"]),
("openmc/volume.py", ["settings.md"]),
("openmc/tallies.py", ["tallies.md"]),
("openmc/filter.py", ["tallies.md"]),
("openmc/filter_expansion.py", ["tallies.md"]),
("openmc/mesh.py", ["tallies.md", "statepoint.md"]),
("openmc/weight_windows.py", ["tallies.md"]),
("openmc/tally_derivative.py", ["tallies.md"]),
("openmc/statepoint.py", ["statepoint.md"]),
("openmc/summary.py", ["statepoint.md"]),
("openmc/model/model.py", ["model.md"]),
("openmc/mgxs_library.py", ["mgxs.md"]),
("openmc/plots.py", ["plots.md"]),
("openmc/plotter.py", ["plots.md"]),
("openmc/lib/plot.py", ["plots.md"]),
("openmc/data/library.py", ["nuclear-data.md"]),
("openmc/data/data.py", ["nuclear-data.md"]),
("openmc/data/decay.py", ["nuclear-data.md"]),
("openmc/data/njoy.py", ["nuclear-data.md"]),
# Internal utilities — no doc affected
("openmc/_xml.py", ["[internal]"]),
("openmc/checkvalue.py", ["[internal]"]),
("openmc/utility_funcs.py", ["[internal]"]),
("openmc/config.py", ["[internal]"]),
# Directory prefix matches (catch-alls — must come after exact matches for same prefix)
("openmc/stats/", ["settings.md"]),
("openmc/deplete/", ["depletion.md"]),
("openmc/mgxs/", ["mgxs.md"]),
("openmc/data/", ["nuclear-data.md"]),
("openmc/lib/", ["openmc-lib.md"]),
]
def lookup(filepath):
"""Longest-prefix match: try exact match first, then directory prefixes by specificity."""
# Try exact match first
for prefix, docs in MAPPING:
if filepath == prefix:
return docs
# Try prefix matches — collect all matches, return the most specific (longest prefix)
matches = [(prefix, docs) for prefix, docs in MAPPING if filepath.startswith(prefix)]
if matches:
# Longest prefix wins
matches.sort(key=lambda x: len(x[0]), reverse=True)
return matches[0][1]
return ["[no-doc-mapped]"]
# Collect unique affected docs (excluding internal markers)
affected_docs = {} # doc → list of source files
unmapped = []
for f in changed_raw:
docs = lookup(f)
for doc in docs:
if doc == "[internal]":
continue
if doc == "[no-doc-mapped]":
unmapped.append(f)
else:
affected_docs.setdefault(doc, []).append(f)
print("=== AFFECTED DOCS ===")
for doc in sorted(affected_docs):
sources = affected_docs[doc]
print(f"\n{doc}:")
for s in sources:
print(f" - {s}")
if unmapped:
print("\n=== UNMAPPED FILES (review manually) ===")
for f in unmapped:
print(f" [no-doc-mapped] {f}")
PYEOF
)
echo "$MAPPING_OUTPUT"
Step 5 — Write skills/openmc/releases/${NEW}.md
Write the migration notes file. This is the SYNC-02 deliverable. The file is written to
skills/openmc/releases/ (a sibling to docs/, NOT inside docs/), so the router
skill never loads version-history as task context (D-06).
Construct the file content from the release notes and mapping output, then write it to disk. All content is data-only — the release notes body and diff are written as-is, never passed to eval or command substitution.
RELEASES_DIR="skills/openmc/releases"
mkdir -p "$RELEASES_DIR"
RELEASE_FILE="${RELEASES_DIR}/${NEW}.md"
# Extract key changes from release notes body for structured output
# Write the migration notes file
cat > "$RELEASE_FILE" << RELEOF
---
from_tag: ${OLD}
to_tag: ${NEW}
date: ${RELEASE_DATE}
generated_by: /sync-openmc
---
# Migration Notes: ${OLD} → ${NEW}
> Generated by \`/sync-openmc ${OLD} ${NEW}\`. Human review required before updating docs.
> After reviewing, perform a validated authoring pass on each affected doc listed below.
## Breaking Changes
### \`openmc.mgxs.Library.add_to_tallies_file\` → \`add_to_tallies\` (soft break)
- **Source:** \`openmc/mgxs/library.py\`
- **Affected doc:** \`mgxs.md\`
- **Detail:** \`add_to_tallies_file(tallies_file, merge=True)\` renamed to
\`add_to_tallies(tallies, merge=True)\`. The old name still works but emits a
\`FutureWarning\` and delegates to the new method. Treat as a breaking rename.
- **Action:** Update \`mgxs.md\` — change all references to \`add_to_tallies_file\`
to \`add_to_tallies\`, update parameter name from \`tallies_file\` to \`tallies\`.
Validate the updated code example in \`openmc-env\`.
## New Features
### \`openmc.deplete.R2SManager\` — Rigorous 2-step shutdown dose
- **Source:** \`openmc/deplete/r2s.py\` (new file)
- **Affected doc:** \`depletion.md\`
- **Detail:** New class for rigorous 2-step (R2S) shutdown dose calculations.
Combines activation transport with gamma transport for shutdown dose rate estimation.
- **Action:** Add R2SManager section to \`depletion.md\` with constructor signature,
key methods, and a validated example. Validate in \`openmc-env\`.
### \`openmc.Model.keff_search\` — Automated criticality search
- **Source:** \`openmc/model/model.py\`
- **Affected doc:** \`model.md\`
- **Detail:** New method for automated k-effective search (e.g., searching for critical
boron concentration or geometry parameter). Large change (1272 diff lines).
- **Action:** Add \`Model.keff_search\` section to \`model.md\` with signature,
parameters, and validated example. Validate in \`openmc-env\`.
### \`openmc.MeshMaterialFilter\` — New tally filter
- **Source:** \`openmc/filter.py\`
- **Affected doc:** \`tallies.md\`
- **Detail:** New tally filter class combining mesh and material filtering in a single
filter object.
- **Action:** Add \`MeshMaterialFilter\` to \`tallies.md\` filter section. Validate
constructor and tally setup in \`openmc-env\`.
### \`openmc.WeightWindowsList\` — Weight windows collection export
- **Source:** \`openmc/weight_windows.py\`
- **Affected doc:** \`tallies.md\`
- **Detail:** New class for exporting/managing collections of weight windows.
- **Action:** Add \`WeightWindowsList\` to \`tallies.md\` §Weight Windows. Validate
in \`openmc-env\`.
### \`openmc.Material.mean_free_path\` — New method
- **Source:** \`openmc/material.py\`
- **Affected doc:** \`materials.md\`
- **Detail:** New method computing the mean free path in a material for a given energy
and particle type.
- **Action:** Add \`mean_free_path\` to \`materials.md\` material properties section.
Validate in \`openmc-env\`.
### \`openmc.lib.TemporarySession\` — New context manager
- **Source:** \`openmc/lib/__init__.py\`, \`openmc/lib/core.py\`
- **Affected doc:** \`openmc-lib.md\`
- **Detail:** New context manager for scoping the C library initialization lifecycle.
- **Action:** Add \`TemporarySession\` to \`openmc-lib.md\`. Validate in \`openmc-env\`.
### Distributed cell density support
- **Source:** \`openmc/geometry.py\` (and related cell/geometry files)
- **Affected doc:** \`geometry-csg.md\`
- **Detail:** Support for distributed (per-instance) cell densities — different density
values for the same cell in different universe fills.
- **Action:** Review \`geometry-csg.md\` for distributed density documentation gaps.
Validate with a multi-fill geometry example in \`openmc-env\`.
### Higher tally moments (variance-of-variance, normality tests)
- **Source:** \`openmc/tallies.py\`
- **Affected doc:** \`tallies.md\`
- **Detail:** Higher-order tally statistical moments added. Enables variance-of-variance
and normality testing of tally results.
- **Action:** Add higher-moment tally coverage to \`tallies.md\` statistical section.
Validate in \`openmc-env\`.
## Compatibility Notes
### MCPL changed from build-time to runtime optional dependency
- **Source:** Build system changes
- **Affected doc:** \`openmc-lib.md\` (if applicable; otherwise no doc change needed)
- **Detail:** MCPL (Monte Carlo Particle Lists) is now a runtime optional dependency
rather than a build-time dependency. Pure Python users are unaffected. Affects only
builds from source that previously enabled MCPL at compile time.
- **Action:** Note in \`openmc-lib.md\` if MCPL usage is documented there; otherwise
no doc change required.
## Additional Modified Files
The following files also changed but may not require doc updates — review the mapping
and decide per-file:
- \`openmc/settings.py\`: \`free_gas_threshold\` and \`source_rejection_fraction\` user settings
added → \`settings.md\`
- \`openmc/plots.py\`: 160 changes (geometry plot enhancements) → \`plots.md\`
- \`openmc/plotter.py\`: Cross-section plotting fixes → \`plots.md\`
- \`openmc/statepoint.py\`: \`get_tally\` filter type option → \`statepoint.md\`
- \`openmc/mesh.py\`: Mesh load from \`weight_windows.h5\`; auto-dimension → \`statepoint.md\`, \`tallies.md\`
- \`openmc/stats/\`: \`PolarAzimuthal\` reference direction; \`MeshSource\` spatial constraints → \`settings.md\`
- \`openmc/source.py\`: MeshSource enhancements → \`settings.md\`
- \`openmc/lib/filter.py\`, \`openmc/lib/tally.py\`: lib-layer filter/tally changes → \`openmc-lib.md\`
## Re-Validation Checklist
Docs that need a validated authoring pass after this release:
- [ ] \`mgxs.md\` — \`add_to_tallies\` rename (breaking; FutureWarning on old name)
- [ ] \`depletion.md\` — \`R2SManager\` new class; depletion enhancements
- [ ] \`model.md\` — \`Model.keff_search\`; \`Model.plot()\` enhancements
- [ ] \`tallies.md\` — \`MeshMaterialFilter\`; \`WeightWindowsList\`; higher tally moments; mesh changes
- [ ] \`materials.md\` — \`Material.mean_free_path\`; waste disposal methods (\`openmc/waste.py\`)
- [ ] \`openmc-lib.md\` — \`TemporarySession\`; lib filter/tally changes; MCPL note
- [ ] \`geometry-csg.md\` — Distributed cell density support
- [ ] \`statepoint.md\` — \`get_tally\` filter type option; mesh changes
- [ ] \`settings.md\` — \`free_gas_threshold\`; \`source_rejection_fraction\`; MeshSource/stats enhancements
- [ ] \`plots.md\` — 160 geometry plot changes; cross-section plotter fixes
**Process:** For each checked-off doc above, perform a full validated authoring pass:
deep-read the changed source at the \`${NEW}\` tag, update the doc, run the validation
script in \`openmc-env\`, update the manifest date.
## Release Notes (Full)
$(printf '%s' "$RELEASE_BODY")
RELEOF
echo "Written: $RELEASE_FILE"
Step 6 — Update .last-synced-release
Update the KB baseline to the new tag (with the v prefix).
echo "${NEW}" > .last-synced-release
echo ".last-synced-release updated to ${NEW}"
Step 7 — Report to stdout
Print a summary of what was done and what actions are needed.
echo ""
echo "============================================================"
echo " /sync-openmc complete: ${OLD} → ${NEW}"
echo "============================================================"
echo ""
echo "Migration notes written to: skills/openmc/releases/${NEW}.md"
echo ".last-synced-release updated to: ${NEW}"
echo ""
echo "Docs requiring validated authoring passes:"
echo " - mgxs.md (add_to_tallies rename — BREAKING)"
echo " - depletion.md (R2SManager new class)"
echo " - model.md (Model.keff_search new method)"
echo " - tallies.md (MeshMaterialFilter, WeightWindowsList, higher moments)"
echo " - materials.md (mean_free_path, waste disposal methods)"
echo " - openmc-lib.md (TemporarySession, MCPL note)"
echo " - geometry-csg.md (distributed cell density)"
echo " - statepoint.md (get_tally filter type)"
echo " - settings.md (free_gas_threshold, source_rejection_fraction)"
echo " - plots.md (geometry plot enhancements)"
echo ""
echo "Next steps:"
echo " 1. Review skills/openmc/releases/${NEW}.md for accuracy"
echo " 2. Work through the Re-Validation Checklist — one doc per authoring pass"
echo " 3. For each doc: deep-read source at ${NEW} tag → update doc → validate in openmc-env"
echo " 4. Commit each doc update with its validation script update"
echo "============================================================"
Important Notes
-
Detector only: This skill detects and maps changes. It does NOT edit any doc in
skills/openmc/docs/. All doc rewrites require a human-driven, validated authoring pass following the Phase 1–4 methodology. -
300-file cap avoidance: The raw
.diffURL is used for complete file enumeration. Do NOT substitutegh api .../compare/.../files— it silently caps at 300 entries. -
Version prefix handling:
.last-synced-releasestoresv0.15.3(withv);openmc.__version__returns0.15.3(without). The version guard normalizes this (stripsvfrom the file value before comparing) — this is Pitfall 2 from the research notes. -
Re-running: After the first sync from
vOLDtovNEW,.last-synced-releaseis updated tovNEW. The next sync call should usevNEWasOLD.