docs: make agents and contributors effective — CLAUDE.md + CONTRIBUTING.md#102
Conversation
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
…ents 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
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
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 <dir>/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
| - `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. |
There was a problem hiding this comment.
This was accurate when written, but #99 merged after this PR was opened and flipped mypy-strict to a required gate (continue-on-error: true is gone — see the updated comment block in lint.yml). As it stands, this bullet tells an agent to ignore the exact job that's blocking their merge.
| - `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. | |
| - Two mypy jobs, **both required**: the deps-less `mypy .` inside `lint`, and | |
| `mypy-strict` (`uv run mypy .` with real deps, required since #99). They can | |
| disagree — with no deps installed, third-party imports fall back to `Any` — | |
| so a green run of one says nothing about the other. `uv run mypy .` after | |
| `uv sync` reproduces the strict job locally; the pre-commit hook reproduces | |
| the deps-less one. |
Worth considering whether to keep even this much detail here: this bullet went stale in the week the PR sat open, because it restates gating rules that live in lint.yml. The durable fact is just "the two mypy runs can disagree; check lint.yml for what currently gates" — the same defer-don't-duplicate principle this PR applies to CONTRIBUTING.md.
There was a problem hiding this comment.
Confirmed and fixed — thanks, this was a real trap.
I checked the claim before writing it, but against the wrong tree: I grepped
continue-on-error on my operate branch, which was cut from a pre-#99 base.
This branch already contained #99 the whole time. Verifying on a stale checkout
is indistinguishable from not verifying at all.
I went with your second suggestion rather than the literal one, because you're
right that the correction would rot the same way. The section now names
lint.yml as the source of truth for what gates, and keeps only what doesn't
depend on current config:
- two mypy runs exist and can disagree — deps-less resolves third-party imports
asAny, deps-installed sees real types — with how to reproduce each locally - a job stops at its first failing step, so green ruff isn't evidence mypy ran
- CI is Python-only, so JS tests need running by hand
- no PR open means no signal
Dropped the specific "which job is required" statement entirely, since that's
exactly the fact that changed under it.
Pushed as 6f7504e.
There was a problem hiding this comment.
Followed your defer-don't-duplicate point through to CONTRIBUTING.md, since this
PR now leans on it being right (CLAUDE.md defers the whole toolchain to it).
It was stale in the same way: the "Type checking" section documented a
[[tool.mypy.overrides]] / ignore_errors list and a policy to manage it, but
that block is gone from pyproject.toml (the migration finished), and the "CI"
section still said one mypy runs and pre-commit run --all-files clears any
failure — false since #99 added the strict gate that pre-commit doesn't
reproduce. Both now defer gating to lint.yml and keep only the durable fact
(two mypy runs, they can disagree, here's how to reproduce each).
Notable while doing it: I cited no issue numbers on purpose. #49 is OPEN but its
body describes 369 mypy errors that have since been cleared — mypy . is clean
today — so the tracker itself is an unreliable source. pyproject.toml and
lint.yml are the only things worth pointing at.
Both docs are now in this PR (655d44e). Re-requested your review.
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
| - **CI is Python-only.** Nothing runs the JS tests, so if you touch | ||
| `gently/ui/web/static/js/`, run `node --test tests/js/` yourself. |
There was a problem hiding this comment.
tests/js/ doesn't exist — not on this branch and not on development. There are no *.test.js files anywhere outside paper/diagrams/node_modules/, and no package.json defines a test script (desktop/package.json only has the tauri scripts). So the warning is right, but the command it recommends fails immediately with "could not find tests/js".
If a JS test suite is landing in another PR, this line should reference it and merge after it. Otherwise:
| - **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 is Python-only, and there is no JS test suite at all.** Nothing will | |
| catch a regression in `gently/ui/web/static/js/` — verify changes there by | |
| running the app and exercising the UI by hand. |
There was a problem hiding this comment.
Right on both counts — the command fails on this branch. Fixed in eab49a4.
One thing you don't have visibility into that changes the wording: #100 (the
operate rebuild) adds tests/js/operate-math.test.mjs — a node --test
suite for the pixel↔stage transform, the F-drive floor gate, and the interlock
predicate. So tests/js/ is genuinely absent on development right now, but
about to exist. That makes "there is no JS test suite at all" true today and
false the moment #100 merges — the same rot we just took out of the mypy bullet.
So I kept your "verify by hand" intent but pinned it to the durable fact instead:
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.
That holds in every merge order — a local suite existing doesn't change that CI
won't run it. Once both PRs are in, #100 is the honest owner of "there's a
tests/js/ suite, run node --test tests/js/", since it's the PR that ships
the suite; I'll add that pointer there rather than assert it here off a branch
where the path doesn't exist.
Same root cause as the mypy one, for the record: I wrote node --test tests/js/
with my head in the operate branch, where the file exists, and it leaked into a
doc built off development, where it doesn't.
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
#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
|
Heads-up: this PR's lint is red through no fault of its own. CI's ruff is Fix is in #103 (pin ruff 0.16.0 across CI + pre-commit + pyproject, and reformat |
Makes the two docs a coding agent (or a new contributor) reads before touching
this repo actually correct, and non-overlapping. Objective: give them what they
can't infer from the code, and nothing they can.
Division of labour
uv sync,uv run pytest,launch_gently.pyflags). Unchanged.CLAUDE.md previously restated most of CONTRIBUTING.md's toolchain section. Cut
to pointers — but that only works if the thing it points at is right, which
forced the CONTRIBUTING.md fixes in this same PR.
CLAUDE.md — what it now adds
development, PR againstdevelopmentingently-project/gently. Both routes: pushing to the org repo with writeaccess, and fork +
upstreamwithout it. Plus the twoghdefaults thatmisfile a PR (repo default is
main, PRs targetdevelopment;ghresolvesthe repo from remotes).
ruff and mypy even though
.pre-commit-config.yamlwires them.exist and can disagree; a job stops at its first failing step; CI runs no JS;
no PR open means no CI signal.
lint.ymlis named as the source of truth forwhat gates, rather than restating rules that change.
D:/Gently3storage default is not absolute onLinux/macOS and silently creates a junk
D:directory (Storage defaultD:/Gently3is non-portable — creates a stray relativeD:dir off-Windows #56). Records theworking
GENTLY_STORAGE_PATH=... --no-api --no-auth --no-browserinvocation.GentlyStore/ContextStoreas superseded-but-still-tested (do not wirenew code to them), and fixes log-tail commands that never worked (
lsprintsa full path; the doc prefixed the directory again).
CONTRIBUTING.md — why it's in this PR
@subindevs' review noted that the defer-don't-duplicate principle applies to
CONTRIBUTING.md too. It did — and worse, this PR now depends on it. It was stale:
[[tool.mypy.overrides]]/ignore_errorslistand a policy to manage it. That block no longer exists (
ignore_errors: 0 inpyproject.toml); the migration it belonged to finished. Cited closed issue Gradually introduce mypy type checking #46.
pre-commit run --all-filesclears any failure.Since ci: make mypy-strict a required gate (closes #63) #99 there are two mypy jobs, and pre-commit reproduces only the deps-less
one — so following it exactly still lets a strict failure through.
Both now state the durable fact (two mypy runs that can disagree; how to
reproduce each) and defer gating specifics to
lint.yml. No issue numbers arecited: #49 is OPEN but describes 369 errors that have since been cleared, so the
tracker isn't a reliable source — only pyproject.toml and lint.yml are.
Docs only; no code changes.