# Sync Openmc

> 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: `itaybnv/sync-openmc` (Agent Skill)
- Install (CLI): `npx skillmds@latest add itaybnv/sync-openmc`
- Raw SKILL.md: https://api.skillmd.com/api/skills/itaybnv/sync-openmc/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Research & Search
- Author: itaybnv (https://skillmd.com/u/itaybnv)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/itaybnv/sync-openmc

---


# /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:**
- `gh` CLI installed and authenticated (`gh auth status` to verify)
- `curl` available (standard Linux/macOS)
- `conda` with the `openmc-env` environment (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:

```bash
# 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.

```bash
# 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.

```bash
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.

```bash
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:

```bash
# 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.

```bash
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).

```bash
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.

```bash
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 `.diff` URL is used for complete file enumeration.
  Do NOT substitute `gh api .../compare/.../files` — it silently caps at 300 entries.

- **Version prefix handling:** `.last-synced-release` stores `v0.15.3` (with `v`);
  `openmc.__version__` returns `0.15.3` (without). The version guard normalizes this
  (strips `v` from the file value before comparing) — this is Pitfall 2 from the
  research notes.

- **Re-running:** After the first sync from `vOLD` to `vNEW`, `.last-synced-release`
  is updated to `vNEW`. The next sync call should use `vNEW` as `OLD`.

