From 46b7458d9100763056a18e4394f14315506aee7b Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 14:41:03 -0700 Subject: [PATCH 1/8] feat(scripts): add --check to generate_schema.py Exits 1 without writing when schema.json is out of date, so the check doesn't depend on the working tree being clean. --- scripts/generate_schema.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/generate_schema.py b/scripts/generate_schema.py index 8e9b8b0..ae75725 100644 --- a/scripts/generate_schema.py +++ b/scripts/generate_schema.py @@ -7,13 +7,15 @@ python scripts/generate_schema.py The script writes the JSON Schema for :class:`~dcd_mapping.schemas.ScoresetMapping` -to ``schema.json`` at the repository root, overwriting the previous file. +to ``schema.json`` at the repository root, overwriting the previous file. With +``--check`` it writes nothing and exits 1 if ``schema.json`` is out of date. If you change schemas.py, re-run this script and commit the result. """ import json import pathlib +import sys from dcd_mapping.schemas import ScoresetMapping @@ -22,9 +24,17 @@ def main() -> None: - """Generate JSON Schema for ScoresetMapping and write to file.""" - schema = ScoresetMapping.model_json_schema() - SCHEMA_PATH.write_text(json.dumps(schema, indent=2, sort_keys=True) + "\n") + """Generate JSON Schema for ScoresetMapping and write it, or check it with --check.""" + schema = ( + json.dumps(ScoresetMapping.model_json_schema(), indent=2, sort_keys=True) + "\n" + ) + if "--check" in sys.argv[1:]: + if SCHEMA_PATH.read_text() != schema: + msg = f"{SCHEMA_PATH} is out of date; run python scripts/generate_schema.py" + print(msg) # noqa: T201 + sys.exit(1) + return + SCHEMA_PATH.write_text(schema) print(f"Written: {SCHEMA_PATH}") # noqa: T201 From d7dbe730ebbabfd3dd5c7e788c7856123cd92970 Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 14:41:29 -0700 Subject: [PATCH 2/8] build: add bin/check and bin/t, and run them in CI bin/check runs ruff check, the format check and the schema.json check in about 3 s and reports every failure in one pass. bin/t wraps pytest with CI's MAVEDB_BASE_URL. Both use .venv when present and python3 otherwise, so CI runs the same commands. --- .github/workflows/checks.yaml | 11 +++-------- bin/check | 13 +++++++++++++ bin/t | 9 +++++++++ 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100755 bin/check create mode 100755 bin/t diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index bacb99e..23d7600 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -27,7 +27,7 @@ jobs: # only run tests known to work in CI run: | mkdir -p coverage - python3 -m pytest tests/test_mavedb_data.py --cov-report=lcov:coverage/lcov.info + bin/t tests/test_mavedb_data.py --cov-report=lcov:coverage/lcov.info - name: Upload coverage to Coveralls uses: coverallsapp/github-action@v2 @@ -46,10 +46,5 @@ jobs: - name: Install dependencies run: python3 -m pip install ".[dev]" - - name: Check style - run: python3 -m ruff check . && ruff format --check . - - - name: Verify schema.json is up to date - run: | - python scripts/generate_schema.py - git diff --exit-code schema.json + - name: Lint, format and schema.json checks + run: bin/check diff --git a/bin/check b/bin/check new file mode 100755 index 0000000..1f07999 --- /dev/null +++ b/bin/check @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Fast static checks: lint, format and the schema.json contract. Tests run through bin/t. +# Runs every check before exiting so one invocation reports all failures. +# Uses .venv when present (local development), otherwise the python3 on PATH (CI). +set -uo pipefail +cd "$(dirname "$0")/.." +PY="${PYTHON:-$([ -x .venv/bin/python ] && echo .venv/bin/python || echo python3)}" + +status=0 +"$PY" -m ruff check . || status=1 +"$PY" -m ruff format --check . || status=1 +"$PY" scripts/generate_schema.py --check || status=1 +exit $status diff --git a/bin/t b/bin/t new file mode 100755 index 0000000..0f83da8 --- /dev/null +++ b/bin/t @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# Tests: bin/t [test path or node id] [pytest args]. Only tests/test_mavedb_data.py runs in CI; +# most other modules need BLAT, SeqRepo or UTA. MAVEDB_BASE_URL matches CI; requests are mocked. +set -euo pipefail +cd "$(dirname "$0")/.." +PY="${PYTHON:-$([ -x .venv/bin/python ] && echo .venv/bin/python || echo python3)}" + +export MAVEDB_BASE_URL="${MAVEDB_BASE_URL:-https://api.mavedb.org}" +exec "$PY" -m pytest "$@" From 7b569ee38a1d9b7cbbc364de886fd89a66cbd1a4 Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 14:42:14 -0700 Subject: [PATCH 3/8] docs: add AGENTS.md and point the README at bin/check and bin/t AGENTS.md (CLAUDE.md imports it) covers what the code doesn't show: the fork's branch flow, which tests run where, the schema.json contract, the GA4GH dependency pin, and the SeqRepo refget rule. --- AGENTS.md | 30 ++++++++++++++++++++++++++++++ CLAUDE.md | 1 + README.md | 7 +++++-- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4df7852 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# AGENTS.md — dcd_mapping + +Facts about this repo that you can't work out from the code. Keep this file short. Rules shared with the other MaveDB repos are in the workspace `AGENTS.md` one folder up. Update both in the same change that makes any of it untrue. + +The mapper turns a MaveDB score set's variants into GA4GH VRS objects. MaveDB's worker calls it over HTTP (`POST /api/v1/map/{urn}`, served from `src/api/`); the `dcd-map` CLI does the same job locally. This repo is MaveDB's fork of `ave-dcd/dcd_mapping`: work goes to `VariantEffect/dcd_mapping2`, branched from and merged into `mavedb-dev`, which is released to `mavedb-main`. + +## Setup + +- `python3 -m venv .venv && .venv/bin/pip install -e '.[dev,tests]'`, on Python 3.11. The `bin/` scripts use `.venv` when it exists. +- A real mapping run needs UTA, SeqRepo, the Gene Normalizer database and BLAT. The README's Prerequisites section covers each. + +## Commands + +| Task | Command | +|---|---| +| Lint, format and `schema.json` checks | `bin/check` | +| What CI tests | `bin/t tests/test_mavedb_data.py` | +| Other tests | `bin/t tests/test_variant_projection.py` | +| Regenerate `schema.json` | `.venv/bin/python scripts/generate_schema.py` | + +- Run `bin/check` after every edit, and the tests for what you touched before handing work back. +- Only `tests/test_mavedb_data.py` runs in CI. The align and `vrs_map` tests need BLAT, and others need SeqRepo or UTA, so some fail on a machine without them. Check a failure against `mavedb-dev` before blaming your change, and report it rather than skipping or loosening the test. + +## Rules + +- Open pull requests with `gh pr create -R VariantEffect/dcd_mapping2 --base mavedb-dev`. Without `-R`, `gh` may target the upstream `ave-dcd` repo. +- `schema.json` is the output contract the MaveDB api consumes. A change to `schemas.py` that alters it ships with the regenerated file in the same commit; `bin/check` fails otherwise. +- `ga4gh.vrs` is pinned to 2.0.0a6 and `cool-seq-tool` to 0.4.0.dev3, because upgrading either means moving the whole GA4GH stack (gene-normalizer, seqrepo, cdot) at once. Don't bump any of them as part of other work. +- Never store a RefSeq sequence in SeqRepo from another source, such as cdot's genome-assembled transcript. It changes the refget, and with it every allele digest MaveDB has published. +- Docstrings use the reST `:param:` style the code already has, not Google style. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md index 84c81f0..69adf0b 100644 --- a/README.md +++ b/README.md @@ -165,8 +165,11 @@ Add pre-commit hooks pre-commit install ``` -Run tests with `pytest` +Run the lint, format and `schema.json` checks with `bin/check`, and tests with `bin/t`, which wraps `pytest`: ``` -pytest +bin/check +bin/t tests/test_mavedb_data.py ``` + +Only `tests/test_mavedb_data.py` runs in CI; most other test modules need BLAT, SeqRepo or UTA. Conventions for contributors and coding agents are in [AGENTS.md](AGENTS.md). From f846ebf01498c8d884f4bcbe25d798a8d612e706 Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 15:11:29 -0700 Subject: [PATCH 4/8] build(claude): add shared permission rules Deny --no-verify and reads of ~/.aws and ~/.pgpass. Ask before pushes, AWS and Elastic Beanstalk commands, the prod-API dev server and dependency changes. The same file lives in the workspace and in each repo, because Claude Code reads settings only from the folder a session starts in. These are guardrails, not a security boundary: a rewritten command can get past them. --- .claude/settings.json | 31 +++++++++++++++++++++++++++++++ .gitignore | 5 +++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..24b6905 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,31 @@ +{ + "permissions": { + "deny": [ + "Bash(* --no-verify*)", + "Read(~/.aws/**)", + "Read(~/.pgpass)" + ], + "ask": [ + "Bash(git push*)", + "Bash(git -C * push*)", + "Bash(aws *)", + "Bash(eb *)", + "Bash(*MODE=prodapi*)", + "Bash(poetry add *)", + "Bash(poetry remove *)", + "Bash(poetry update*)", + "Bash(poetry lock*)", + "Bash(poetry -C * add *)", + "Bash(poetry -C * remove *)", + "Bash(poetry -C * update*)", + "Bash(poetry -C * lock*)", + "Bash(npm install*)", + "Bash(npm uninstall *)", + "Bash(npm update*)", + "Bash(npm --prefix * install*)", + "Bash(npm --prefix * uninstall *)", + "Bash(npm --prefix * update*)", + "Bash(*pip install *)" + ] + } +} diff --git a/.gitignore b/.gitignore index 439b17c..1aec9a5 100644 --- a/.gitignore +++ b/.gitignore @@ -173,5 +173,6 @@ urn:*.json tmp:*.json *_mapping_*.json -# Agent settings -.claude/ +# Agent settings: personal files stay local; the shared permission rules are committed +.claude/* +!.claude/settings.json From 7df2ec093705c9513df7bb23a14610a0df22975c Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 15:40:47 -0700 Subject: [PATCH 5/8] build(claude): run AWS as the read-only agent role and block ~/.ssh Sessions get AWS_PROFILE=mavedb-agent-readonly, so aws commands use the read-only role (see deployment/environments/aws/agent-access.md) and fail rather than fall back to full credentials where the profile is missing. Choosing another profile asks first, replacing the blanket prompt on aws. Reading ~/.ssh is now blocked, alongside ~/.aws and ~/.pgpass. --- .claude/settings.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 24b6905..cb0cb47 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,14 +1,19 @@ { + "env": { + "AWS_PROFILE": "mavedb-agent-readonly" + }, "permissions": { "deny": [ "Bash(* --no-verify*)", "Read(~/.aws/**)", - "Read(~/.pgpass)" + "Read(~/.pgpass)", + "Read(~/.ssh/**)" ], "ask": [ "Bash(git push*)", "Bash(git -C * push*)", - "Bash(aws *)", + "Bash(*--profile*)", + "Bash(*AWS_PROFILE=*)", "Bash(eb *)", "Bash(*MODE=prodapi*)", "Bash(poetry add *)", From 39f12f97ed5830edb3ef9d234a68d952f8f1a23b Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 15:59:56 -0700 Subject: [PATCH 6/8] build(claude): ask before edits to instruction and settings files Edits to AGENTS.md, CLAUDE.md and anything under .claude/ now ask first. Those files shape every later session, and settings.json holds the guardrails. The rules cover Edit and Write; a shell redirect into one of these files is not caught. --- .claude/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.claude/settings.json b/.claude/settings.json index cb0cb47..5b1bde9 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -10,6 +10,9 @@ "Read(~/.ssh/**)" ], "ask": [ + "Edit(**/AGENTS.md)", + "Edit(**/CLAUDE.md)", + "Edit(**/.claude/**)", "Bash(git push*)", "Bash(git -C * push*)", "Bash(*--profile*)", From 2df116b70e07fcd8384c138e76982e41db946942 Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 16:18:06 -0700 Subject: [PATCH 7/8] build(claude): ask only before hard-to-undo actions Prompts now cover force pushes, pushes to main, mavedb-main, mavedb-dev or release-*, AWS with an explicit profile, eb, adding a dependency and edits to settings.json itself. Ordinary feature-branch pushes, dependency updates and edits to AGENTS.md, CLAUDE.md and .claude/rules no longer ask; the never rule in the workspace AGENTS.md still covers those edits, and PR review sees the diffs. --- .claude/settings.json | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 5b1bde9..509725c 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -10,29 +10,27 @@ "Read(~/.ssh/**)" ], "ask": [ - "Edit(**/AGENTS.md)", - "Edit(**/CLAUDE.md)", - "Edit(**/.claude/**)", - "Bash(git push*)", - "Bash(git -C * push*)", - "Bash(*--profile*)", + "Edit(**/.claude/settings*.json)", + "Bash(git push *--force*)", + "Bash(git push * -f*)", + "Bash(git push * main*)", + "Bash(git push * mavedb-main*)", + "Bash(git push * mavedb-dev*)", + "Bash(git push * release-*)", + "Bash(git -C * push *--force*)", + "Bash(git -C * push * -f*)", + "Bash(git -C * push * main*)", + "Bash(git -C * push * mavedb-main*)", + "Bash(git -C * push * mavedb-dev*)", + "Bash(git -C * push * release-*)", + "Bash(aws *--profile*)", "Bash(*AWS_PROFILE=*)", "Bash(eb *)", "Bash(*MODE=prodapi*)", "Bash(poetry add *)", - "Bash(poetry remove *)", - "Bash(poetry update*)", - "Bash(poetry lock*)", "Bash(poetry -C * add *)", - "Bash(poetry -C * remove *)", - "Bash(poetry -C * update*)", - "Bash(poetry -C * lock*)", - "Bash(npm install*)", - "Bash(npm uninstall *)", - "Bash(npm update*)", - "Bash(npm --prefix * install*)", - "Bash(npm --prefix * uninstall *)", - "Bash(npm --prefix * update*)", + "Bash(npm install *)", + "Bash(npm --prefix * install *)", "Bash(*pip install *)" ] } From 8702dca4c3fbcdb751340b0058054bfc176e205e Mon Sep 17 00:00:00 2001 From: Benjamin Capodanno Date: Fri, 16 Oct 2026 16:23:52 -0700 Subject: [PATCH 8/8] style(claude): format settings.json with Prettier The ui checks it with Prettier, so every copy uses that layout. --- .claude/settings.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 509725c..3dac982 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -3,12 +3,7 @@ "AWS_PROFILE": "mavedb-agent-readonly" }, "permissions": { - "deny": [ - "Bash(* --no-verify*)", - "Read(~/.aws/**)", - "Read(~/.pgpass)", - "Read(~/.ssh/**)" - ], + "deny": ["Bash(* --no-verify*)", "Read(~/.aws/**)", "Read(~/.pgpass)", "Read(~/.ssh/**)"], "ask": [ "Edit(**/.claude/settings*.json)", "Bash(git push *--force*)",