Read before changing apeGmsh's OpenSees bridge (`src/apeGmsh/opensees/`): adding or changing a typed primitive (material, section, element, time series, pattern, recorder, analysis option), consuming a Ladruno fork feature, touching the emit path (Tcl/py/live/H5), the response catalog, a FEMData stream, or the neutral/opensees H5 schema. A checklist of the traps bridge PRs have hit. For changing apeGmsh only; *using* the bridge in a model is the `apegmsh` skill, out of scope here.
Installs into .claude/skills of the current project.
Are you the author of Apegmsh Bridge Feature?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nmorabowen-apegmsh-bridge-feature)
---
name: apegmsh-bridge-feature
description: >
Read before changing apeGmsh's OpenSees bridge (`src/apeGmsh/opensees/`):
adding or changing a typed primitive (material, section, element,
time series, pattern, recorder, analysis option), consuming a Ladruno
fork feature, touching the emit path (Tcl/py/live/H5), the response
catalog, a FEMData stream, or the neutral/opensees H5 schema. A
checklist of the traps bridge PRs have hit. For changing apeGmsh only;
*using* the bridge in a model is the `apegmsh` skill, out of scope here.
---
# Read this before changing the OpenSees bridge
Each item points at where its lesson lives. Open that file and grep for
the quoted heading. Paths are from the repo root; `architecture/` is
`architecture/`.
## Before code
- [ ] Read `architecture/charter.md`, "Principles". The 14 principles are what new
code is judged against; P2 (primitives never touch `ops`), P7 (one
way to declare each thing) and P12 (static typing first) are the
ones PRs trip on.
- [ ] A new primitive follows `architecture/agent-onboarding.md` "The
standard slice", and adds exactly what `architecture/testing.md`
"What every PR adds"
lists for its kind (the `ALL_*` contract lists included).
- [ ] Check the source, not the docs, for what exists today (AGENTS.md,
"What this repo is"). If you are numbering an ADR, list the decisions
directory on `origin/main` first; the `adr-number` quirk rule holds
this.
## The fork (Ladruno) vs stock OpenSees
- [ ] Fork-only behaviour carries the `ladruno_fork` marker, which the root
`tests/conftest.py` auto-skips off-fork, and a row in
`docs/concepts/backend-capabilities.md` "The fork-only surface".
- [ ] Everything else must still run on **stock** openseespy. The
`live-stock` lane exists because three fork/stock divergences reached
main (#1021); its "Backend must really import" step says why a
skipped `live` test is not a pass.
- [ ] A capability probe that can be inconclusive returns `None`, not
`False`. See the ADR 0110 D5 row in the decisions README.
- [ ] Code that talks to the live domain takes its module from the
resolver: an explicit `ops=` first, else `get_ops()` in
`opensees/emitter/live.py`. Beside a fork build, importing openseespy
by name binds a second module, and a capture once sampled that empty
domain (9ffe6aa2). The `openseespy-import` quirk rule holds this.
- [ ] Consume a fork flag only after the fork PR that adds it has merged
into `ladruno` (`gh pr view <N> -R nmorabowen/OpenSees --json
mergedAt`). A build from its branch passing is not enough. Where the
fork parser ignores unknown options, a flag that no `ladruno` build
carries is silently discarded, so refuse it on the live route and
warn on decks until then. #1184 shipped `-betaC`/`-crackedNu` from the
closed fork #873; the gate is `opensees/_rc_c2_flags.py`.
## Shared literals and streams
- [ ] The response catalog (`opensees/_response_catalog.py`) and the
`expected` sets in `tests/test_results_element_response*.py` are
shared literals. Two green PRs that both edit one can merge red
(#605 + #606 → #608; AGENTS.md "How work lands").
- [ ] A new FEMData stream (a new `ElementComposite`/`NodeComposite`
parameter) is carried by `mesh/_compose.py` **and** the model.h5
round-trip in `mesh/_femdata_h5_io.py`. The `compose-streams` quirk
rule fails the PR otherwise: the compose rebuild dropped streams
twice (#707, #912/#913).
- [ ] Fail loud. A resolver or chain phase that swallows an error and
returns `None`/`False` turns a missing tie or contact into a silent
no-op (45340ac3, "chain-phase sessions fail loud instead of silently
dropping defs"; `tests/test_resolution_contract.py`). The
`resolve-swallow` quirk rule holds this in `_kernel/resolvers/**` and
`mesh/_fem_factory.py`; a handler that really may stay silent carries
a waiver that says why.
## Schema
- [ ] A schema bump follows `architecture/h5-schema.md` ("Versioning": the
floor rule, bump rules, shim ledger), ADR 0023's bump cadence and
ADR 0113's floor. Compatibility is a floor per zone, not a window: the
reader opens every minor from the zone's floor to the current one, and
the floor moves only with a major bump, by ADR. Cite the ADR's Decision
section, not older prose.
- [ ] A bump adds its corpus file. Rebuild the zone's file with
`python scripts/build_schema_corpus.py --zone <neutral|opensees> --base HEAD`
after committing the bump (`--list` shows the plan first), and commit the
new `tests/fixtures/schema_corpus/` files and `MANIFEST.json`.
`tests/opensees/h5/test_schema_corpus.py` fails a bump that skipped it.
The builder anchors non-current eras on `origin/main`, and the suite fails a manifest
SHA that is not an ancestor of it (#1365, #1407), so a squash merge cannot orphan one.
- [ ] A change to what an existing field *means* ships a reader shim keyed on
a named `*_FROM` constant above the floor, a corpus file below it, and a
row in the shim ledger in `h5-schema.md`. A restructure is a major bump.
- [ ] Tests read versions from `tests/fixtures/schema.py`, never a literal.
The `schema-literal` quirk rule holds this: hard-coded versions
turned main red at 2.12.0, 2.13.0 and 2.16.0 (#642, #738).
- [ ] A schema bump must bump the app target in the same PR (`tests/test_app_target_drift.py` fails otherwise).
## Emitted decks
- [ ] Paths in an emitted Tcl deck are brace-quoted. An unquoted
`recorder -file` with backslashes or spaces "exits 0 having written
nothing" (be4aa1e7, #1086).
- [ ] Emit cost is gated against a committed baseline (`emit-cost-gate`,
`tests/benchmarks/test_emit_regression_gate.py`). A 2-3x slowdown
once reported PASS for 124 commits (#876).
- [ ] A contact with a numeric `kn` always emits the full `kn kt mu`
triple: the fork parser aborts on a bare `kn` followed by more flags,
and the short form came back in #744 the day ADR 0073 recorded it
(ADR 0073, "bare numeric `kn`"; `tests/opensees/unit/test_contact_emit.py`).
- [ ] A partitioned or staged emit change is checked against the
sequential run field by field, not by "it ran": a stage-claimed MP
tie and ghost BCs were silently dropped once (49ff0766).
## Gates before the PR
- [ ] `ruff check src/apeGmsh/opensees` (a hard gate) and
`mypy src/apeGmsh/opensees` (baseline **0**). Six red-main episodes
were merges past this gate; never raise the baseline.
- [ ] Warn-as-contract code is run with `-W error::<Category>`
(AGENTS.md "Build and test").
- [ ] `python scripts/check_quirks.py`.
- [ ] A fix that turns an existing test red: classify that test as pinning
wrong behaviour or as a real contract before you edit it, and own the
file in the slice card first (#2217: two fixes had to amend their
cards).
- [ ] The curated `suite` lane locally, since boundary tests live outside
the package you touched. Add a CHANGELOG section per
`internal_docs/changelog_workflow.md`.
- [ ] Judge a local run against a baseline, not a raw count (AGENTS.md "Build
and test"). On the maintainer's machine `import opensees` resolves to
the installed Ladruno fork
(`C:\Program Files\Ladruno\OpenSees\bin\opensees.pyd`). That un-skips
the `ladruno_fork` tests, which then fail against a stale build
("element type LadrunoLST is unknown"); CI never runs them. Use CI's
`-m` selection, exclude `tests/opensees/integration_ladruno`, or diff
against the same command on `origin/main`.