From d61067a36726145da7b86bec5189e527597b8ce4 Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Mon, 20 Jul 2026 17:46:44 +0530 Subject: [PATCH 1/7] docs: document the contributor workflow and the CI gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Written for any contributor, not just those with write access to the org repo. Development workflow: branch off `development`, PR against `development` in gently-project/gently. Covers both paths — pushing branches straight to the org repo with write access, and the fork + `upstream` remote route without it. Before every commit: the three commands CI actually gates on (ruff check, ruff format --check, mypy), plus `pre-commit install`. .pre-commit-config.yaml already wires exactly those hooks at matching pins, but the git hook is not installed in a fresh clone, so commits bypass it — which is how an unformatted file reached CI on PR #100. Also records the traps found while fixing that: the lint job stops at the first failing step, so green ruff is not evidence mypy ran; mypy-strict is continue-on-error and must not be read as the required gate; no CI runs on a feature branch until a PR exists; JS has no CI coverage at all; and gh defaults to `main` and to whichever remote it resolves unless --base/--repo are passed. Corrects the gently-perception repo reference to gently-project/gently-perception, which is what CI clones and what pyproject.toml expects beside this repo. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 397f180d..078ea4fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,5 +1,91 @@ # Gently — Microscopy Agent +## Development workflow + +Every change branches off `development` and lands through a pull request against +`development` in **`gently-project/gently`**. Only how you *push* the branch +differs, depending on whether you have write access to that repo. + +**With write access** — push branches straight to the org repo: + +```bash +git clone git@github.com:gently-project/gently.git && cd gently +git checkout development && git pull +git checkout -b feature/ +# ... work, committing as you go ... +git push -u origin feature/ +gh pr create --repo gently-project/gently --base development +``` + +**Without write access** — fork `gently-project/gently` on GitHub, clone the +fork, and keep the org repo as a second remote so you can stay current with it: + +```bash +git clone git@github.com:/gently.git && cd gently +git remote add upstream git@github.com:gently-project/gently.git +git fetch upstream +git checkout -b feature/ upstream/development +# ... work, committing as you go ... +git push -u origin feature/ # pushes to YOUR fork +gh pr create --repo gently-project/gently --base development +``` + +Either way the PR targets `development` in the org repo. Run `git remote -v` to +see which name points where before pushing: in a direct clone `origin` *is* the +org repo, in a fork-based clone `origin` is your fork and `upstream` is the org +repo. Rebase on `upstream/development` (or `origin/development`) before opening +the PR so the diff is only your work. + +Two defaults will send a PR to the wrong place if you let them: + +- The repo's **default branch is `main`, but PRs target `development`.** + `gh pr create` without `--base development` proposes `main`. +- With no `gh` default repo configured, `gh` can resolve to a different remote + than you expect. Pass `--repo gently-project/gently` explicitly, or run + `gh repo set-default` once. + +## Before every commit and push + +CI (`.github/workflows/lint.yml`) gates PRs on these. Run them locally first — +a failure blocks the PR and costs a push/wait/fix round trip: + +```bash +ruff check . # lint +ruff format --check . # formatting (drop --check to apply the fix) +mypy . # deps-less, mypy==2.1.0 — the REQUIRED gate +``` + +(Prefix with `.venv/bin/python -m ` if the venv is not active.) + +`.pre-commit-config.yaml` already wires exactly these three hooks, but **the git +hook is not installed in a fresh clone** — that is how an unformatted file +reaches CI. Install it once per clone: + +```bash +pre-commit install # or .venv/bin/pre-commit install if not on PATH +``` + +The hooks mirror the required gate exactly: same pinned ruff/mypy versions, and +mypy runs with `pass_filenames: false, args: ["."]` so it checks the whole tree +like CI rather than just staged files. First run builds isolated envs and takes +a few minutes; after that it is seconds. Note the `ruff` hook runs with `--fix` +and `ruff-format` rewrites files — when they change something the commit aborts +by design, so `git add` the fixes and commit again. + +Things that bite: + +- The `lint` job runs its steps **in order and stops at the first failure**, so a + ruff error hides whether mypy would have passed. A green run after fixing ruff + is not the same as having checked mypy. Run all three locally. +- `mypy-strict` (`uv run mypy .`, real deps) is a **separate non-blocking job** + (`continue-on-error: true`). Only the deps-less `mypy .` inside `lint` can fail + a PR. Don't read a green `mypy-strict` as proof the required gate passed. +- Lint runs on `pull_request` and on pushes to `main`/`development` only. Pushing + a feature branch with no PR open runs **nothing** — the first CI signal arrives + when the PR is opened, on the whole accumulated diff. +- **JS is not covered by CI at all.** If you touch `gently/ui/web/static/js/`, + run `node --test tests/js/` yourself; nothing else will. + ## Storage Architecture (Gently3 — File-Based) All data lives under `D:\Gently3\` (env: `GENTLY_STORAGE_PATH`). **No SQLite databases.** Everything is human-browsable files. @@ -90,7 +176,9 @@ grep -E "ERROR|Traceback" D:/Gently3/logs/gently_*.log ## Perception -Perception is handled by `gently-perception` (separate repo: `pskeshu/gently-perception`), installed as a pip dependency. The timelapse orchestrator uses `Perceiver()` from `gently_perception` — a self-contained system that loads its own examples and accumulates per-embryo context through sequential calls. +Perception is handled by `gently-perception` (separate repo: +`gently-project/gently-perception` — this is what CI clones and what +`pyproject.toml` expects beside this repo), installed as a pip dependency. The timelapse orchestrator uses `Perceiver()` from `gently_perception` — a self-contained system that loads its own examples and accumulates per-embryo context through sequential calls. ## Device Layer From dbc90bd8608cef9b3e648669d979f03abc5f9de7 Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Mon, 20 Jul 2026 17:58:27 +0530 Subject: [PATCH 2/7] docs: restructure CLAUDE.md around working effectively with coding agents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CLAUDE.md is the file an agent reads before touching this repo, so it should carry what an agent cannot infer and defer everything else. The first draft duplicated most of CONTRIBUTING.md's toolchain section, which is how docs drift apart. Now it points at README.md for setup/run and CONTRIBUTING.md for the lint/type toolchain, and keeps only what neither says: - Where work lands: branch off `development`, PR against `development` in gently-project/gently, with and without write access to the org repo. - The pre-commit hook is not installed in a fresh clone, so commits silently bypass ruff and mypy — how an unformatted file reached CI on #100. - Non-obvious CI behaviour: the lint job stops at its first failing step (so green ruff is not evidence mypy ran), mypy-strict is continue-on-error and does not gate, no CI runs until a PR exists, and JS has no CI coverage at all. - Running off-Windows: the `D:/Gently3` default is not an absolute path on Linux/macOS and silently creates a junk `D:` directory (issue #56), so GENTLY_STORAGE_PATH must be set. Records the --no-api/--no-auth/--no-browser combination and the 8080 UI port. Also corrects the gently-perception reference to gently-project/gently-perception, matching what CI clones and what pyproject.toml expects beside this repo. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 114 ++++++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 63 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 078ea4fc..cd6e1545 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,90 +1,78 @@ # Gently — Microscopy Agent -## Development workflow +## Working on this repo -Every change branches off `development` and lands through a pull request against -`development` in **`gently-project/gently`**. Only how you *push* the branch -differs, depending on whether you have write access to that repo. +**README.md** covers environment setup and how to run things (`uv sync`, +`uv run pytest`, `uv run python launch_gently.py` and its flags). +**CONTRIBUTING.md** covers the lint/type toolchain and the incremental-typing +policy. This section is only for what those two don't say — the things that are +easy to get wrong here. -**With write access** — push branches straight to the org repo: +### Where work lands + +Branch off `development`; open the PR against `development` in +**`gently-project/gently`**. Only the push differs by access level: ```bash +# With write access — branches go straight to the org repo git clone git@github.com:gently-project/gently.git && cd gently -git checkout development && git pull -git checkout -b feature/ -# ... work, committing as you go ... +git checkout -b feature/ origin/development git push -u origin feature/ -gh pr create --repo gently-project/gently --base development -``` -**Without write access** — fork `gently-project/gently` on GitHub, clone the -fork, and keep the org repo as a second remote so you can stay current with it: - -```bash +# Without write access — fork on GitHub, then keep the org repo as `upstream` git clone git@github.com:/gently.git && cd gently git remote add upstream git@github.com:gently-project/gently.git git fetch upstream git checkout -b feature/ upstream/development -# ... work, committing as you go ... -git push -u origin feature/ # pushes to YOUR fork +git push -u origin feature/ # your fork + +# Either way gh pr create --repo gently-project/gently --base development ``` -Either way the PR targets `development` in the org repo. Run `git remote -v` to -see which name points where before pushing: in a direct clone `origin` *is* the -org repo, in a fork-based clone `origin` is your fork and `upstream` is the org -repo. Rebase on `upstream/development` (or `origin/development`) before opening -the PR so the diff is only your work. - -Two defaults will send a PR to the wrong place if you let them: - -- The repo's **default branch is `main`, but PRs target `development`.** - `gh pr create` without `--base development` proposes `main`. -- With no `gh` default repo configured, `gh` can resolve to a different remote - than you expect. Pass `--repo gently-project/gently` explicitly, or run - `gh repo set-default` once. +Check `git remote -v` before pushing: in a direct clone `origin` *is* the org +repo; in a fork-based clone `origin` is your fork and `upstream` is the org repo. +Two `gh` defaults will misfile a PR — the repo's default branch is `main` while +PRs target `development`, and with no `gh` default repo set it resolves from +whichever remote it finds. Pass `--base development --repo gently-project/gently`. -## Before every commit and push +### Before committing -CI (`.github/workflows/lint.yml`) gates PRs on these. Run them locally first — -a failure blocks the PR and costs a push/wait/fix round trip: +CONTRIBUTING.md is canonical for the toolchain. The trap it doesn't mention: +**the pre-commit hook is not installed in a fresh clone**, so commits silently +bypass ruff and mypy. That is how an unformatted file reached CI on #100. Run +`pre-commit install` once per clone, then `pre-commit run --all-files` before +opening a PR. -```bash -ruff check . # lint -ruff format --check . # formatting (drop --check to apply the fix) -mypy . # deps-less, mypy==2.1.0 — the REQUIRED gate -``` +Non-obvious CI behaviour, all in `.github/workflows/lint.yml`: -(Prefix with `.venv/bin/python -m ` if the venv is not active.) - -`.pre-commit-config.yaml` already wires exactly these three hooks, but **the git -hook is not installed in a fresh clone** — that is how an unformatted file -reaches CI. Install it once per clone: +- The `lint` job runs its steps **in order and stops at the first failure**, so a + ruff error hides whether mypy would have passed. A green run after fixing ruff + is not evidence that mypy ever ran. +- `mypy-strict` (`uv run mypy .`, real deps) is a **separate non-blocking job** + (`continue-on-error: true`). Only the deps-less `mypy .` inside `lint` gates a + PR — a green `mypy-strict` proves nothing about the required check. +- Lint runs on `pull_request` and on pushes to `main`/`development` only. A + feature branch with no PR open gets **no CI at all**; the first signal arrives + when the PR is opened, against the whole accumulated diff. +- **JavaScript has no CI coverage.** If you touch `gently/ui/web/static/js/`, + run `node --test tests/js/` yourself — nothing else will. + +### Running the app off-Windows + +The storage paths throughout this file (`D:\Gently3\...`) are the Windows +microscope PCs, where `D:` is the dedicated data drive. Off-Windows that default +is **not an absolute path**: it resolves against the cwd and silently creates a +junk directory literally named `D:` with session data inside it +(`gently/settings.py`, tracked as issue #56). Always set an explicit path when +running on Linux or macOS: ```bash -pre-commit install # or .venv/bin/pre-commit install if not on PATH +GENTLY_STORAGE_PATH=/tmp/gently-dev uv run python launch_gently.py --no-api --no-auth --no-browser ``` -The hooks mirror the required gate exactly: same pinned ruff/mypy versions, and -mypy runs with `pass_filenames: false, args: ["."]` so it checks the whole tree -like CI rather than just staged files. First run builds isolated envs and takes -a few minutes; after that it is seconds. Note the `ruff` hook runs with `--fix` -and `ruff-format` rewrites files — when they change something the commit aborts -by design, so `git add` the fixes and commit again. - -Things that bite: - -- The `lint` job runs its steps **in order and stops at the first failure**, so a - ruff error hides whether mypy would have passed. A green run after fixing ruff - is not the same as having checked mypy. Run all three locally. -- `mypy-strict` (`uv run mypy .`, real deps) is a **separate non-blocking job** - (`continue-on-error: true`). Only the deps-less `mypy .` inside `lint` can fail - a PR. Don't read a green `mypy-strict` as proof the required gate passed. -- Lint runs on `pull_request` and on pushes to `main`/`development` only. Pushing - a feature branch with no PR open runs **nothing** — the first CI signal arrives - when the PR is opened, on the whole accumulated diff. -- **JS is not covered by CI at all.** If you touch `gently/ui/web/static/js/`, - run `node --test tests/js/` yourself; nothing else will. +Those three flags are the usual agent/dev combination: no Anthropic key needed, +no login gate, and no browser window. The UI is then on `http://localhost:8080`. ## Storage Architecture (Gently3 — File-Based) From 810ca32728a0e5aaa3dc92d886ba2d44a6f7a159 Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Mon, 20 Jul 2026 18:19:35 +0530 Subject: [PATCH 3/7] docs: drop the incident reference from the pre-commit note The instruction stands on its own. Which PR happened to expose the missing hook is trivia a future reader has no use for. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cd6e1545..8950931c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -40,9 +40,8 @@ whichever remote it finds. Pass `--base development --repo gently-project/gently CONTRIBUTING.md is canonical for the toolchain. The trap it doesn't mention: **the pre-commit hook is not installed in a fresh clone**, so commits silently -bypass ruff and mypy. That is how an unformatted file reached CI on #100. Run -`pre-commit install` once per clone, then `pre-commit run --all-files` before -opening a PR. +bypass ruff and mypy. Run `pre-commit install` once per clone, then +`pre-commit run --all-files` before opening a PR. Non-obvious CI behaviour, all in `.github/workflows/lint.yml`: From fb4cb84ff97646ab0112223856dd2495065611cb Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Mon, 20 Jul 2026 18:28:22 +0530 Subject: [PATCH 4/7] docs: cut noise and a broken command from CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass over the whole file for content that does not help someone working here. Removed: - "Debugging Data Sources" — all eight bullets restated paths already annotated in the directory-layout tree directly above, and re-hardcoded the Windows storage root eight more times. - "Drop-in API replacement" and the "replaces GentlyStore/ContextStore" framing in Key store classes — migration-era wording, and duplicated by the section below it. - The issue reference in the off-Windows note. The behaviour is described on its own terms; the issue now carries the reminder to update this file when fixed. Reframed the legacy-store section around what actually matters: GentlyStore and ContextStore are still in the package and still have passing tests, so they look live, but nothing outside tests/ instantiates them. Stated as "do not wire new code to these" rather than as data-migration history. Fixed the log-tailing commands, which never worked: `ls -t /gently_*.log` prints a full path, and the documented form prefixed the directory again, yielding `D:/Gently3/logs/D:/Gently3/logs/gently_*.log`. They now resolve the storage root through GENTLY_STORAGE_PATH so they also work off-Windows. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 53 ++++++++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8950931c..ef9e9b1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,8 +63,8 @@ The storage paths throughout this file (`D:\Gently3\...`) are the Windows microscope PCs, where `D:` is the dedicated data drive. Off-Windows that default is **not an absolute path**: it resolves against the cwd and silently creates a junk directory literally named `D:` with session data inside it -(`gently/settings.py`, tracked as issue #56). Always set an explicit path when -running on Linux or macOS: +(`gently/settings.py`). Always set an explicit path when running on Linux or +macOS: ```bash GENTLY_STORAGE_PATH=/tmp/gently-dev uv run python launch_gently.py --no-api --no-auth --no-browser @@ -78,9 +78,9 @@ no login gate, and no browser window. The UI is then on `http://localhost:8080`. All data lives under `D:\Gently3\` (env: `GENTLY_STORAGE_PATH`). **No SQLite databases.** Everything is human-browsable files. ### Key store classes -- **`FileStore`** (`gently/core/file_store.py`) — replaces `GentlyStore`. Manages sessions, embryos, volumes, projections, predictions, traces. Drop-in API replacement. -- **`FileContextStore`** (`gently/harness/memory/file_store.py`) — replaces `ContextStore` / `agent_mind.db`. Manages campaigns, plans, learnings, observations, agent state. Drop-in API replacement. -- **Root manifest**: `D:\Gently3\gently.yaml` — documents the structure for humans and agents. +- **`FileStore`** (`gently/core/file_store.py`) — sessions, embryos, volumes, projections, predictions, traces. +- **`FileContextStore`** (`gently/harness/memory/file_store.py`) — campaigns, plans, learnings, observations, agent state. +- **Root manifest**: `gently.yaml` at the storage root — documents the structure for humans and agents. ### Directory layout ``` @@ -136,29 +136,39 @@ D:/Gently3/ incoming/{uuid}.tif # transient device staging ``` -### Legacy stores (D:\Gently2\ — read-only reference) -The old SQLite-based stores are preserved but no longer written to: -- `gently.db` (GentlyStore) — replaced by FileStore -- `context/agent_mind.db` (ContextStore) — replaced by FileContextStore -- `D:\gently\dataset.db` — legacy benchmarking DB +### Superseded stores — do not wire new code to these + +The SQLite-era classes are **still in the package and still have passing tests**, +so they look live. They have no production callers; only `tests/` instantiate +them. Use the file stores above instead. + +- `GentlyStore` (`gently/core/store.py`) → use `FileStore` +- `ContextStore` (`gently/harness/memory/store.py`, `agent_mind.db`) → use `FileContextStore` +- `gently/dataset/` still defaults to `D:/gently/dataset.db` (legacy benchmarking DB) + +Their data lives under the old `D:\Gently2\` root, read-only reference only. ## Logging -Both the agent and device layer write logs to `D:\Gently3\logs\`: +Both the agent and device layer write logs to `/logs/`: - **Agent**: `gently_YYYYMMDD_HHMMSS.log` — INFO+ to file, console level configurable via `-v` flag - **Device layer**: `device_layer_YYYYMMDD_HHMMSS.log` — INFO level -To check logs during a session: +To check logs during a session (the expansion keeps these working off-Windows, +where the `D:/Gently3` default does not apply — see above): + ```bash -# Latest agent log -tail -f D:/Gently3/logs/$(ls -t D:/Gently3/logs/gently_*.log | head -1) +LOGS="${GENTLY_STORAGE_PATH:-D:/Gently3}/logs" + +# Latest agent log (ls prints the full path — do not prefix $LOGS again) +tail -f "$(ls -t "$LOGS"/gently_*.log | head -1)" # Latest device layer log -tail -f D:/Gently3/logs/$(ls -t D:/Gently3/logs/device_layer_*.log | head -1) +tail -f "$(ls -t "$LOGS"/device_layer_*.log | head -1)" # Filter for errors -grep -E "ERROR|Traceback" D:/Gently3/logs/gently_*.log +grep -E "ERROR|Traceback" "$LOGS"/gently_*.log ``` ## Perception @@ -215,14 +225,3 @@ the Desktop shortcut points at; rebuild with `npm run build` to refresh it. ### Deferred Bundling the Python env (torch/anthropic/perception) for a redistributable installer — today's build launches the repo's `.venv`, so it's single-machine. - -## Debugging Data Sources - -- **Agent logs**: `D:\Gently3\logs\gently_*.log` -- **Device layer logs**: `D:\Gently3\logs\device_layer_*.log` -- **Perception traces**: `D:\Gently3\sessions\{session}\embryos\{embryo}\traces\` — per-timepoint JSON -- **Predictions**: `D:\Gently3\sessions\{session}\embryos\{embryo}\predictions.jsonl` -- **Volume staging**: `D:\Gently3\incoming\` -- **Agent memory**: `D:\Gently3\agent\` — campaigns, learnings, observations (all YAML) -- **Session state**: `D:\Gently3\sessions\{session}\session.yaml` -- **Timelapse state**: `D:\Gently3\sessions\{session}\timelapse.yaml` From 6f7504e0d1ad98e2162361afe62e7375292e949e Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Mon, 20 Jul 2026 19:48:56 +0530 Subject: [PATCH 5/7] docs: stop restating CI gating rules that live in lint.yml Review catch from @subindevs: the mypy-strict bullet said the job was non-blocking, but #99 made it a required gate and removed continue-on-error. The bullet went stale in the week this PR sat open, and told an agent to ignore the exact job that would block their merge. Rather than correct the fact and leave the same trap, apply this PR's own defer-don't-duplicate principle to CI as well. lint.yml is now named as the source of truth for what gates, and the bullets keep only what does not depend on current config: that two mypy runs exist and can disagree (deps-less resolves third-party imports as Any, deps-installed sees real types), that a job stops at its first failing step, that CI is Python-only, and that a branch with no PR gets no signal. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index ef9e9b1b..5624d365 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,19 +43,22 @@ CONTRIBUTING.md is canonical for the toolchain. The trap it doesn't mention: bypass ruff and mypy. Run `pre-commit install` once per clone, then `pre-commit run --all-files` before opening a PR. -Non-obvious CI behaviour, all in `.github/workflows/lint.yml`: - -- The `lint` job runs its steps **in order and stops at the first failure**, so a - ruff error hides whether mypy would have passed. A green run after fixing ruff - is not evidence that mypy ever ran. -- `mypy-strict` (`uv run mypy .`, real deps) is a **separate non-blocking job** - (`continue-on-error: true`). Only the deps-less `mypy .` inside `lint` gates a - PR — a green `mypy-strict` proves nothing about the required check. -- Lint runs on `pull_request` and on pushes to `main`/`development` only. A - feature branch with no PR open gets **no CI at all**; the first signal arrives - when the PR is opened, against the whole accumulated diff. -- **JavaScript has no CI coverage.** If you touch `gently/ui/web/static/js/`, - run `node --test tests/js/` yourself — nothing else will. +`.github/workflows/lint.yml` is the source of truth for which checks gate a PR, +and that changes — read it rather than trusting a list here. What stays true: + +- **There are two mypy runs and they can disagree.** One runs `mypy .` with no + project deps, so third-party imports fall back to `Any`; the other runs + `uv run mypy .` with the real packages and resolves their actual types. A + green run of one says nothing about the other. `uv sync && uv run mypy .` + reproduces the deps-installed run locally; the pre-commit hook reproduces the + deps-less one. +- **A job stops at its first failing step.** A ruff failure means the mypy step + in that job never ran, so a green re-run after fixing ruff is not evidence + that mypy passed. +- **CI is Python-only.** Nothing runs the JS tests, so if you touch + `gently/ui/web/static/js/`, run `node --test tests/js/` yourself. +- CI runs on pull requests and on pushes to `main`/`development`, so a feature + branch with no PR open gets no signal at all. ### Running the app off-Windows From eab49a4dd548cebba092d9dd513acda18d1f2936 Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Fri, 24 Jul 2026 09:48:42 +0530 Subject: [PATCH 6/7] docs: drop the broken tests/js command from the JS bullet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review catch from @subindevs: `node --test tests/js/` fails on this branch — `tests/js/` exists only on the operate branch (#100), not on development. The old wording also implied JS tests already exist on development, which they don't. Reworded to the durable fact — CI runs no JavaScript, so verify UI changes by running the app — which holds whether or not an in-tree JS suite exists, so it does not go stale when #100 lands one. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5624d365..5140439d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,8 +55,8 @@ and that changes — read it rather than trusting a list here. What stays true: - **A job stops at its first failing step.** A ruff failure means the mypy step in that job never ran, so a green re-run after fixing ruff is not evidence that mypy passed. -- **CI is Python-only.** Nothing runs the JS tests, so if you touch - `gently/ui/web/static/js/`, run `node --test tests/js/` yourself. +- **CI runs no JavaScript.** A change under `gently/ui/web/static/js/` is not + covered by CI, so verify it by running the app and exercising the UI by hand. - CI runs on pull requests and on pushes to `main`/`development`, so a feature branch with no PR open gets no signal at all. From 655d44e0d35960202a82d1d7b92aba256a563950 Mon Sep 17 00:00:00 2001 From: P S Kesavan Date: Fri, 24 Jul 2026 09:54:07 +0530 Subject: [PATCH 7/7] docs: fix the stale toolchain content in CONTRIBUTING.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #102 deletes CLAUDE.md's duplicated toolchain text and defers to CONTRIBUTING.md as canonical — which only holds if CONTRIBUTING.md is right. It wasn't; the incremental-typing migration finished and the doc never followed. CONTRIBUTING.md: - "Type checking" described a `[[tool.mypy.overrides]]` / `ignore_errors = true` ignore-list and a policy for managing it. That block no longer exists in pyproject.toml (ignore_errors: 0 occurrences), so the whole mechanism and its policy were describing something gone. Referenced issue #46, which is closed. Replaced with the durable fact: two mypy runs (deps-less `mypy .` with ignore_missing_imports, and deps-installed `uv run mypy .`) that can disagree, and how to reproduce each. New code must pass both. - "CI" said one mypy runs and `pre-commit run --all-files` fixes any failure. Since #99 there are two mypy jobs and pre-commit reproduces only the deps-less one. Now points at lint.yml as the source of truth for gating and names what pre-commit does and does not reproduce. Deliberately cites no issue numbers: #49 is OPEN but its body describes 369 mypy errors that have since been cleared (mypy . passes clean today), so the tracker is not a reliable source — only pyproject.toml and lint.yml are. CLAUDE.md: its one-line pointer described CONTRIBUTING.md as covering "the incremental-typing policy", the section just removed — updated to match. Claude-Session: https://claude.ai/code/session_01XMWRh8uasxiH9jKiDxYtrZ --- CLAUDE.md | 6 +++--- CONTRIBUTING.md | 31 ++++++++++++++----------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5140439d..4f114143 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,9 +4,9 @@ **README.md** covers environment setup and how to run things (`uv sync`, `uv run pytest`, `uv run python launch_gently.py` and its flags). -**CONTRIBUTING.md** covers the lint/type toolchain and the incremental-typing -policy. This section is only for what those two don't say — the things that are -easy to get wrong here. +**CONTRIBUTING.md** covers the lint/type toolchain — ruff, the two mypy runs, +and pre-commit. This section is only for what those two don't say — the things +that are easy to get wrong here. ### Where work lands diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b936732d..1b869a4b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -48,25 +48,22 @@ pre-commit autoupdate ### Type checking -Run mypy the same way pre-commit and CI do: +mypy runs two ways, and they can disagree: -```bash -mypy . -``` - -The codebase is being typed incrementally (see issue #46). Modules with -pre-existing errors are listed in the `[[tool.mypy.overrides]]` block in -`pyproject.toml` with `ignore_errors = true`, so `mypy .` passes today even -though not every module is fully typed yet. - -Policy for working with this list: +- `mypy .` — no project dependencies installed. `[tool.mypy]` sets + `ignore_missing_imports = true`, so third-party imports fall back to `Any`. + This is the form the pre-commit hook runs. +- `uv run mypy .` — after `uv sync`, with the real packages present, so mypy + checks their actual types and can surface mismatches the deps-less run cannot. -- **New modules** must pass `mypy .` cleanly — do not add them to the - overrides list. -- **PRs that substantively touch a module on the overrides list** should fix - that module's type errors and remove it from the list as part of the - change. +A green run of one says nothing about the other, so run `uv run mypy .` before +pushing if you touched code that uses a typed third-party library. New code must +type-clean under both. ### CI -Every pull request runs the lint job (`.github/workflows/lint.yml`), which checks ruff lint and formatting and runs mypy across the entire project. Fix any failures locally with `pre-commit run --all-files` before pushing. +`.github/workflows/lint.yml` is the source of truth for what gates a pull +request, and it changes — read it rather than trusting a summary here. +`pre-commit run --all-files` reproduces the ruff checks and the deps-less +`mypy .` locally, but **not** the deps-installed run; reproduce that with +`uv run mypy .` after `uv sync`.