From 891ff06fe79be907f1985fd70d4dd942e02aecb0 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Tue, 14 Jul 2026 14:53:10 -1000 Subject: [PATCH 1/4] ci(guardrails): stdlib deprecated-pattern scanner with shrink-only allowlist ratchet Scans src/ for the Charter's cheapest-to-check banned patterns: access-context (S7), mesh.data (S7), hedging names maybe_/try_/do_ (S3), and uncommented except/pass swallows (S4). Line-based, no dependencies; comment lines skipped; heuristics and exclusions documented in the module docstring. --include-docs adds a report-only docs/ inventory. The allowlist records the 87 legacy hits at baseline (development@9f5ed9e9) as path:pattern-id lines and may only shrink; the scanner reports stale entries so cleanups delete them. Part of FO-03 (quality campaign 2026-07, Phase 4). Underworld development team with AI support from Claude Code --- scripts/check_deprecated_patterns.py | 253 +++++++++++++++++++++++ scripts/deprecated_pattern_allowlist.txt | 72 +++++++ 2 files changed, 325 insertions(+) create mode 100644 scripts/check_deprecated_patterns.py create mode 100644 scripts/deprecated_pattern_allowlist.txt diff --git a/scripts/check_deprecated_patterns.py b/scripts/check_deprecated_patterns.py new file mode 100644 index 00000000..d577ce7b --- /dev/null +++ b/scripts/check_deprecated_patterns.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +"""Deprecated-pattern scanner for the UW3 style gates (stdlib only). + +Enforces the cheapest-to-check rules of ``docs/developer/UW3_STYLE_CHARTER.md`` +on ``src/``: + +- ``access-context`` (Charter S7): ``with mesh.access(...)`` / ``with + swarm.access(...)`` context-manager data access. New code uses the ``array`` + property directly. +- ``mesh-data`` (Charter S7): ``mesh.data`` as a coordinate read. New code uses + ``mesh.X.coords``. +- ``hedging-name`` (Charter S3): ``def maybe_*`` / ``def try_*`` / ``def do_*`` + (and single-underscore-private forms). Names state what a thing IS or DOES. +- ``except-pass`` (Charter S4): an ``except ...:`` block whose body is ``pass`` + with no comment nearby. Every intentional swallow states its sanctioned + failure mode. + +Detection is line-based and deliberately simple so any contributor can read +this file and predict what it flags: + +- Lines whose first non-space character is ``#`` are skipped entirely + (commented-out legacy code does not trip the gate; Charter S4 says to delete + it, but that is a review matter, not a machine gate). +- ``except-pass`` heuristic: an ``except`` header line followed (skipping + blank lines) by a bare ``pass``, with no ``#`` comment on the line before + the ``except``, the ``except`` line, the ``pass`` line, or the line after + the ``pass``. A comment anywhere in that window counts as a stated failure + mode. +- NOT scanned (documented exclusions): ``self.data`` inside Mesh methods + (cannot be distinguished reliably from legitimate variable ``.data`` by a + line scanner) and ``.data`` / ``.access`` mentions inside strings or + docstrings other than those matching the patterns above. + +Allowlist ratchet +----------------- +Existing legacy hits are recorded in ``scripts/deprecated_pattern_allowlist.txt`` +as ``path:pattern-id`` lines. A hit is allowed if its file+pattern pair is +listed. The allowlist may only SHRINK: fix the code your PR adds; only a +maintainer adds entries. When a file is cleaned up, remove its entries — the +scanner warns about stale (unused) entries so they are not forgotten. + +Usage +----- + python scripts/check_deprecated_patterns.py # gate src/ + python scripts/check_deprecated_patterns.py --include-docs # + docs report + python scripts/check_deprecated_patterns.py --no-allowlist # raw inventory + python scripts/check_deprecated_patterns.py PATH [PATH...] # custom roots + +Exit status: 0 when every hit under the gated roots is allowlisted, +1 otherwise. The docs report never affects the exit status. +""" + +import argparse +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +DEFAULT_ALLOWLIST = Path(__file__).resolve().parent / "deprecated_pattern_allowlist.txt" + +SOURCE_SUFFIXES = {".py", ".pyx"} +DOCS_SUFFIXES = {".py", ".md", ".ipynb"} + +CHARTER = "docs/developer/UW3_STYLE_CHARTER.md" + +# pattern-id -> (compiled regex, Charter section, replacement guidance) +LINE_PATTERNS = { + "access-context": ( + re.compile(r"\bwith\s+[\w.]+\.access\("), + "S7", + "use the variable's .array / .data property directly", + ), + "mesh-data": ( + re.compile(r"\bmesh\.data\b"), + "S7", + "use mesh.X.coords for coordinates", + ), + "hedging-name": ( + re.compile(r"\bdef\s+_?(maybe|try|do)_\w+"), + "S3", + "name the function for what it DOES (no maybe_/try_/do_ prefixes)", + ), +} + +EXCEPT_RE = re.compile(r"^\s*except(\s+[^:]+)?:\s*(#.*)?$") +PASS_RE = re.compile(r"^\s*pass\s*(#.*)?$") + + +def is_comment_line(line): + stripped = line.lstrip() + return stripped.startswith("#") + + +def scan_file(path): + """Return a list of (pattern_id, line_number, line_text) hits in one file.""" + try: + lines = path.read_text(encoding="utf-8", errors="replace").splitlines() + except OSError as exc: + print(f"WARNING: could not read {path}: {exc}", file=sys.stderr) + return [] + + hits = [] + for lineno, line in enumerate(lines, start=1): + if is_comment_line(line): + continue + for pattern_id, (regex, _section, _fix) in LINE_PATTERNS.items(): + if regex.search(line): + hits.append((pattern_id, lineno, line.strip())) + + hits.extend(scan_except_pass(lines)) + return hits + + +def scan_except_pass(lines): + """Find `except ...:` blocks whose body is a bare `pass` with no comment + on the line before the except, the except line, the pass line, or the + line after the pass.""" + hits = [] + for i, line in enumerate(lines): + if not EXCEPT_RE.match(line): + continue + # Find the first non-blank line after the except header. + j = i + 1 + while j < len(lines) and not lines[j].strip(): + j += 1 + if j >= len(lines) or not PASS_RE.match(lines[j]): + continue + window = [lines[i - 1] if i > 0 else "", line, lines[j], + lines[j + 1] if j + 1 < len(lines) else ""] + if any("#" in w for w in window): + continue + hits.append(("except-pass", i + 1, line.strip() + " ... pass")) + return hits + + +def collect_files(roots, suffixes): + files = [] + for root in roots: + if root.is_file(): + if root.suffix in suffixes: + files.append(root) + continue + files.extend(p for p in sorted(root.rglob("*")) if p.suffix in suffixes) + return files + + +def load_allowlist(path): + """Return the set of allowed `path:pattern-id` keys.""" + allowed = set() + if not path.exists(): + return allowed + for raw in path.read_text(encoding="utf-8").splitlines(): + entry = raw.strip() + if not entry or entry.startswith("#"): + continue + allowed.add(entry) + return allowed + + +def relative_key(path): + try: + return path.resolve().relative_to(REPO_ROOT).as_posix() + except ValueError: + return path.as_posix() + + +def report_hits(all_hits, header): + print(header) + for file_key, pattern_id, lineno, text in all_hits: + _regex, section, fix = LINE_PATTERNS.get( + pattern_id, (None, "S4", "state the sanctioned failure mode in a comment") + ) + print(f" {file_key}:{lineno}: [{pattern_id}] {text}") + print(f" Charter {section}: {fix}") + + +def main(argv=None): + parser = argparse.ArgumentParser( + description="Scan for Charter-banned deprecated patterns (see module docstring)." + ) + parser.add_argument( + "paths", nargs="*", type=Path, + help="Roots to gate (default: src/). Files or directories.", + ) + parser.add_argument( + "--allowlist", type=Path, default=DEFAULT_ALLOWLIST, + help="Allowlist file of path:pattern-id lines (shrink-only).", + ) + parser.add_argument( + "--no-allowlist", action="store_true", + help="Ignore the allowlist: print the raw inventory (used to seed it).", + ) + parser.add_argument( + "--include-docs", action="store_true", + help="Also scan docs/ and report hits there (report only, never fails).", + ) + args = parser.parse_args(argv) + + roots = args.paths or [REPO_ROOT / "src"] + files = collect_files(roots, SOURCE_SUFFIXES) + + hits = [] # (file_key, pattern_id, lineno, text) + for path in files: + file_key = relative_key(path) + for pattern_id, lineno, text in scan_file(path): + hits.append((file_key, pattern_id, lineno, text)) + + allowed = set() if args.no_allowlist else load_allowlist(args.allowlist) + blocked = [h for h in hits if f"{h[0]}:{h[1]}" not in allowed] + # Stale-entry accounting only makes sense for the default full-src scan; + # a partial scan would misreport everything unscanned as stale. + if args.paths: + stale = [] + else: + used_keys = {f"{h[0]}:{h[1]}" for h in hits} + stale = sorted(allowed - used_keys) + + if blocked: + report_hits(blocked, "Deprecated patterns found (not in allowlist):") + print() + print(f"FAIL: {len(blocked)} hit(s) across {len({h[0] for h in blocked})} file(s).") + print(f"These patterns are banned by {CHARTER}.") + print("Fix the code rather than extending the allowlist:") + print(f" {args.allowlist.name} records pre-existing legacy hits only") + print(" and may only SHRINK (maintainer-approved entries only).") + else: + n_allowed = len(hits) + print(f"OK: no new deprecated patterns ({n_allowed} known legacy hit(s) allowlisted).") + + if stale: + print() + print("NOTE: stale allowlist entries (no longer any hits) — please remove:") + for entry in stale: + print(f" {entry}") + + if args.include_docs: + docs_files = collect_files([REPO_ROOT / "docs"], DOCS_SUFFIXES) + docs_hits = [] + for path in docs_files: + file_key = relative_key(path) + for pattern_id, lineno, text in scan_file(path): + docs_hits.append((file_key, pattern_id, lineno, text)) + print() + if docs_hits: + report_hits(docs_hits, f"docs/ report (informational only, {len(docs_hits)} hit(s)):") + else: + print("docs/ report: clean.") + + return 1 if blocked else 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/deprecated_pattern_allowlist.txt b/scripts/deprecated_pattern_allowlist.txt new file mode 100644 index 00000000..e93e7e70 --- /dev/null +++ b/scripts/deprecated_pattern_allowlist.txt @@ -0,0 +1,72 @@ +# Deprecated-pattern allowlist for scripts/check_deprecated_patterns.py +# +# POLICY: THIS FILE MAY ONLY SHRINK. +# +# Each line is `path:pattern-id` and records a PRE-EXISTING legacy hit that +# predates the style gate (baseline: development @ 9f5ed9e9, 2026-07). The +# gate fails on any hit NOT listed here. If your PR trips the gate, fix the +# code — do not add lines. Only a maintainer may add an entry, and only for +# code that already existed when the gate was introduced. +# +# When a file is cleaned up, delete its entries (the scanner prints a +# "stale allowlist entries" note to remind you). Pattern definitions and +# rationale: docs/developer/UW3_STYLE_CHARTER.md (S3, S4, S7) and the +# scanner's module docstring. +# +# NOTE: an entry allows every hit of that pattern in that file, so a listed +# file is not protected against NEW hits of the same pattern. Reviewers +# should keep an eye on diffs touching listed files. + +# --- access-context (Charter S7) ------------------------------------------ +# Legacy test written against the old context-manager API (13 sites). +src/underworld3/tests/test_adaptivity_metrics.py:access-context + +# --- mesh-data (Charter S7) ------------------------------------------------ +# The two hits are the deprecated property's own docstring and its +# DeprecationWarning message — the implementation of the shim, not usage. +src/underworld3/discretisation/discretisation_mesh.py:mesh-data + +# --- hedging-name (Charter S3) --------------------------------------------- +# `_maybe_install_snes_update` is the Charter S3 example of the banned form; +# renaming it is library-code churn outside the guardrails PR's scope. +src/underworld3/cython/petsc_generic_snes_solvers.pyx:hedging-name +# Four `_do_move()` local closures in the mesh-mover machinery. +src/underworld3/discretisation/discretisation_mesh.py:hedging-name +src/underworld3/meshing/_ot_adapt.py:hedging-name +src/underworld3/meshing/smoothing/api.py:hedging-name + +# --- except-pass (Charter S4) ---------------------------------------------- +# Uncommented exception swallows present at baseline (67 sites). Each needs +# a one-line comment stating its sanctioned failure mode; do that in the file +# you are already touching and delete its entry here. +src/underworld3/constitutive_models.py:except-pass +src/underworld3/coordinates.py:except-pass +src/underworld3/cython/petsc_generic_snes_solvers.pyx:except-pass +src/underworld3/cython/petsc_maths.pyx:except-pass +src/underworld3/discretisation/discretisation_mesh.py:except-pass +src/underworld3/discretisation/remesh.py:except-pass +src/underworld3/function/_function.pyx:except-pass +src/underworld3/function/expressions.py:except-pass +src/underworld3/function/quantities.py:except-pass +src/underworld3/function/unit_conversion.py:except-pass +src/underworld3/meshing/_ot_adapt.py:except-pass +src/underworld3/meshing/smoothing/anisotropic.py:except-pass +src/underworld3/meshing/smoothing/monge_ampere.py:except-pass +src/underworld3/meshing/surfaces.py:except-pass +src/underworld3/model.py:except-pass +src/underworld3/scaling/_scaling.py:except-pass +src/underworld3/swarm.py:except-pass +src/underworld3/systems/solvers.py:except-pass +src/underworld3/timing.py:except-pass +src/underworld3/units.py:except-pass +src/underworld3/utilities/_api_tools.py:except-pass +src/underworld3/utilities/_interrupt.py:except-pass +src/underworld3/utilities/_jit_cache.py:except-pass +src/underworld3/utilities/_jitextension.py:except-pass +src/underworld3/utilities/_params.py:except-pass +src/underworld3/utilities/diagnostics.py:except-pass +src/underworld3/utilities/mathematical_mixin.py:except-pass +src/underworld3/utilities/memprobe.py:except-pass +src/underworld3/utilities/nondimensional.py:except-pass +src/underworld3/utilities/rotated_bc.py:except-pass +src/underworld3/utilities/unit_aware_coordinates.py:except-pass From b35e3c21ca3722a6765401114900c9437a995b3a Mon Sep 17 00:00:00 2001 From: lmoresi Date: Tue, 14 Jul 2026 14:53:49 -1000 Subject: [PATCH 2/4] ci(guardrails): style-gates workflow and Charter PR checklist template style-gates.yml runs the deprecated-pattern scanner on every PR to development and main (checkout + python only, no PETSc build, <1 min), failing with the offending lines and a pointer to the Charter and the shrink-only allowlist policy. A second step prints the docs/ inventory report-only. No formatting gate: src/underworld3 is not black-clean (81 of 95 files), so a format check would fail every library PR. The PR template distils the Charter into a nine-line checklist. Part of FO-03 (quality campaign 2026-07, Phase 4). Underworld development team with AI support from Claude Code --- .github/pull_request_template.md | 16 ++++++++++ .github/workflows/style-gates.yml | 53 +++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/style-gates.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..5952dfd1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ + + +## Checklist (UW3 Style Charter — docs/developer/UW3_STYLE_CHARTER.md) + +- [ ] I have read the Style Charter and followed it over the surrounding code (S2) +- [ ] No drive-by refactors, renames, or "while I was here" cleanups (S9) +- [ ] Bug fixes ship the regression test, written first, with `level_*` and `tier_*` markers (S8) +- [ ] No hedging names (`maybe_` / `try_` / `do_`) and no commented-out code (S3, S4) +- [ ] Every exception swallow states its sanctioned failure mode in a comment (S4) +- [ ] New data access uses `.array` / `mesh.X.coords` — no `with ....access(...)`, no `mesh.data` (S7) +- [ ] Parallel safety considered — np2/np4 checked where swarm/mesh/solver dispatch is touched (S11) +- [ ] No `pixi.toml` / `pixi.lock` or other dependency changes riding in an unrelated PR +- [ ] AI-assisted work carries the attribution line below + + diff --git a/.github/workflows/style-gates.yml b/.github/workflows/style-gates.yml new file mode 100644 index 00000000..2f577d0d --- /dev/null +++ b/.github/workflows/style-gates.yml @@ -0,0 +1,53 @@ +name: Style Gates + +# Machine-enforced subset of docs/developer/UW3_STYLE_CHARTER.md (S3, S4, S7). +# Fast (<1 min): no PETSc build, no package install — the scanner is stdlib-only. +# +# The gate fails on deprecated patterns NOT recorded in +# scripts/deprecated_pattern_allowlist.txt. That allowlist records pre-existing +# legacy hits and may only SHRINK: fix the code your PR adds; only a maintainer +# adds allowlist entries. + +on: + pull_request: + branches: + - development + - main + types: [opened, synchronize, reopened] + +jobs: + deprecated-patterns: + name: Deprecated-pattern scan (Charter S3/S4/S7) + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Scan src/ for Charter-banned patterns + run: | + echo "Running the deprecated-pattern scanner (stdlib only)..." + if ! python scripts/check_deprecated_patterns.py; then + echo "" + echo "FAILED: your change introduces a pattern banned by the UW3 Style Charter." + echo "" + echo " Charter: docs/developer/UW3_STYLE_CHARTER.md (S3 naming, S4 comments, S7 data access)" + echo " Guide: docs/developer/guides/style-gates.md" + echo " Run local: pixi run -e amr-dev python scripts/check_deprecated_patterns.py" + echo "" + echo "Fix the flagged code. The allowlist" + echo "(scripts/deprecated_pattern_allowlist.txt) records pre-existing legacy" + echo "hits only and may only SHRINK — only a maintainer adds entries." + exit 1 + fi + + - name: Report deprecated patterns in docs/ (informational) + run: | + # Never fails: docs cleanup is tracked separately. This keeps the + # inventory visible in the job log without blocking PRs. + python scripts/check_deprecated_patterns.py --include-docs || true From 583386f25a14fec4e547eaca3bb3dd89980c3fda Mon Sep 17 00:00:00 2001 From: lmoresi Date: Tue, 14 Jul 2026 14:53:49 -1000 Subject: [PATCH 3/4] docs(guardrails): style-gates guide; Charter + release-checklist pointers Adds guides/style-gates.md (what the gates check, shrink-only allowlist policy, local usage, what to do when a PR trips the gate, why there is no formatting gate) and links it from the developer guides toctree. Charter S10 gains a four-line machine-enforcement pointer; the release process notes that style gates must be green on the release branch. Part of FO-03 (quality campaign 2026-07, Phase 4). Underworld development team with AI support from Claude Code --- docs/developer/UW3_STYLE_CHARTER.md | 5 ++ docs/developer/guides/release-process.md | 5 ++ docs/developer/guides/style-gates.md | 70 ++++++++++++++++++++++++ docs/developer/index.md | 1 + 4 files changed, 81 insertions(+) create mode 100644 docs/developer/guides/style-gates.md diff --git a/docs/developer/UW3_STYLE_CHARTER.md b/docs/developer/UW3_STYLE_CHARTER.md index f607d520..cc470ee8 100644 --- a/docs/developer/UW3_STYLE_CHARTER.md +++ b/docs/developer/UW3_STYLE_CHARTER.md @@ -137,6 +137,11 @@ This Charter wins on any conflict. One governing document per topic: `UW3_Style_and_Patterns_Guide.md` remains as the detailed reference, but where it and this Charter disagree (notably docstring format and coordinate access), the Charter wins. +**Machine enforcement**: the cheapest-to-check rules here (§3 hedging names, §4 silent +swallows, §7 deprecated data access) are enforced in CI by +`scripts/check_deprecated_patterns.py` via `.github/workflows/style-gates.yml`; its +allowlist records pre-existing legacy hits and may only shrink. See `guides/style-gates.md`. + ## 11. Things to Remember Underworld is a parallel code — no feature is complete if it only works in serial. diff --git a/docs/developer/guides/release-process.md b/docs/developer/guides/release-process.md index 2405ccb5..6be44211 100644 --- a/docs/developer/guides/release-process.md +++ b/docs/developer/guides/release-process.md @@ -133,6 +133,11 @@ Step 8 is deliberately left to a human so the review gate on `main` is never bypassed. CI (`.github/workflows/release.yml`) publishes the GitHub Release from the committed `docs/release-notes/vX.Y.0.md`. +The CI style gates must also be green on the release branch — the +`development → main` PR (step 7) runs `.github/workflows/style-gates.yml` +automatically; see [style-gates.md](style-gates.md) for the checks and the +shrink-only allowlist policy. + ### Documentation freshness sweeps (before step 6) Two "pull" documents go stale silently between releases; refresh both as part diff --git a/docs/developer/guides/style-gates.md b/docs/developer/guides/style-gates.md new file mode 100644 index 00000000..f15b4198 --- /dev/null +++ b/docs/developer/guides/style-gates.md @@ -0,0 +1,70 @@ +# Style gates (CI) + +The cheapest-to-check rules of the [UW3 Style Charter](../UW3_STYLE_CHARTER.md) +are machine-enforced on every pull request to `development` and `main` by +`.github/workflows/style-gates.yml`. The job is fast (no PETSc build) and runs +a single stdlib-only scanner: `scripts/check_deprecated_patterns.py`. + +## What the gate checks + +| Pattern id | Charter | Flags | Use instead | +|---|---|---|---| +| `access-context` | §7 | `with mesh.access(...)` / `with swarm.access(...)` | the variable's `.array` property directly | +| `mesh-data` | §7 | `mesh.data` (coordinate read) | `mesh.X.coords` | +| `hedging-name` | §3 | `def maybe_*` / `def try_*` / `def do_*` (and `_`-private forms) | a name stating what the function DOES | +| `except-pass` | §4 | `except ...:` + bare `pass` with no comment nearby | a comment stating the sanctioned failure mode | + +Detection is line-based and deliberately simple; the exact heuristics (and the +documented exclusions, e.g. commented-out lines and `self.data` inside Mesh +methods) are in the scanner's module docstring. A related regression net — +`tests/test_0641_wave_c_api_shims.py` in the `level_1 + tier_a` CI gate — +covers the deprecation *warnings* the shimmed APIs must keep emitting. + +## The allowlist only shrinks + +`scripts/deprecated_pattern_allowlist.txt` records the legacy hits that +existed when the gate was introduced (baseline: `development` @ `9f5ed9e9`, +2026-07), one `path:pattern-id` per line. The gate fails on any hit **not** +listed there. + +```{warning} +The allowlist may only **shrink**. If your PR trips the gate, fix the code — +do not add allowlist lines. Only a maintainer adds entries, and only for code +that predates the gate. When you clean up a listed file, delete its entries; +the scanner reminds you by reporting stale ones. +``` + +## Running locally + +```bash +pixi run -e amr-dev python scripts/check_deprecated_patterns.py # the CI gate +pixi run -e amr-dev python scripts/check_deprecated_patterns.py --include-docs # + docs/ report +pixi run -e amr-dev python scripts/check_deprecated_patterns.py --no-allowlist # full legacy inventory +``` + +The scanner needs nothing beyond the Python standard library, so plain +`python scripts/check_deprecated_patterns.py` works in any environment. + +## When your PR trips the gate + +1. Read the flagged line(s) in the job log — each hit names its pattern id and + the Charter rule it violates. +2. Fix the code (the table above gives the replacement for each pattern). +3. If you believe a hit is a false positive of the line-based heuristic, + say so in the PR and ask a maintainer to rule — do not edit the allowlist + yourself. + +## Formatting (not gated) + +The tree is not currently `black`-clean (81 of 95 files at baseline), so there +is **no** formatting gate — adding one would fail every PR that touches +library code. Do not reformat files wholesale to "fix" this: bulk reformatting +destroys `git blame` and turns reviewable diffs into unreviewable ones. If the +maintainers later adopt a formatter, it will arrive as a single dedicated +reformat commit plus a gate, not piecemeal. + +## See also + +- [UW3 Style Charter](../UW3_STYLE_CHARTER.md) — the normative rules +- [Release process](release-process.md) — style gates must be green on the release branch +- `/check-patterns` (Claude command) — interactive audit of docs, notebooks and tests diff --git a/docs/developer/index.md b/docs/developer/index.md index 9b161f29..7f3965e9 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -127,6 +127,7 @@ guides/HOW-TO-WRITE-UW3-SCRIPTS guides/notebook-style-guide guides/GMSH_INTEGRATION_GUIDE guides/CODE-REVIEW-PROCESS +guides/style-gates guides/SPELLING_CONVENTION guides/version-management guides/branching-strategy From c0f3479955daef97b1be534a888f3702d4800d58 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Tue, 14 Jul 2026 15:02:01 -1000 Subject: [PATCH 4/4] docs(check-patterns): scanner-first workflow; fix stale .qmd style-guide reference The /check-patterns skill now points at scripts/check_deprecated_patterns.py as the single source of truth for the src/ pattern list, and its reference list is aligned with the Charter authority map (the .qmd guide no longer exists). Underworld development team with AI support from Claude Code --- .claude/commands/check-patterns.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.claude/commands/check-patterns.md b/.claude/commands/check-patterns.md index 8599de48..b958c4c0 100644 --- a/.claude/commands/check-patterns.md +++ b/.claude/commands/check-patterns.md @@ -2,15 +2,24 @@ description: Find deprecated access patterns in code and documentation --- -## Reference Document +## Run the CI scanner first -**Read the authoritative patterns guide first:** -`docs/developer/UW3_Style_and_Patterns_Guide.qmd` +The `src/` pattern list is machine-enforced; the scanner is the single source of +truth for what is banned and what is allowlisted legacy: -Key sections: -- Section 4 "Array and Data Management" - array indexing patterns -- Section 5 "Context Managers" - deprecated vs current access patterns -- Section 6 "MPI and Parallel Patterns" - parallel safety +```bash +pixi run -e amr-dev python scripts/check_deprecated_patterns.py +python scripts/check_deprecated_patterns.py --include-docs # report-only docs inventory +``` + +Policy and pattern definitions: `docs/developer/guides/style-gates.md` +(allowlist shrinks only — never add entries to make a PR pass). + +## Reference Documents + +- `docs/developer/UW3_STYLE_CHARTER.md` — normative rules (§3 naming, §4 comments, §7 data access) +- `docs/developer/subsystems/data-access.md` — governing data-access reference +- `docs/developer/UW3_Style_and_Patterns_Guide.md` — broader style reference ---