Skip to content

feat(types): pyright zero-diagnostic gate — 568-diagnostic ratchet burned to zero, warnings_strict flips to Met (#197, final) - #240

Merged
cdeust merged 8 commits into
mainfrom
feat/warnings-strict-197-pyright
Jul 28, 2026
Merged

feat(types): pyright zero-diagnostic gate — 568-diagnostic ratchet burned to zero, warnings_strict flips to Met (#197, final)#240
cdeust merged 8 commits into
mainfrom
feat/warnings-strict-197-pyright

Conversation

@cdeust

@cdeust cdeust commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Final PR of the #197 maximal-strictness program, following #230 (S110), #232 (BLE001), #234 (PLR2004/E501) and #238 (PLC0415/S608): the 568-diagnostic pyright ratchet backlog is burned to ZERO at typeCheckingMode: "standard" (pyright 1.1.410), the ratchet is retired — CI now fails on ANY pyright diagnostic — and .bestpractices.json flips warnings_strict to Met, citing configuration rather than promise.

What this PR does

The burn-down was fixes, not annotations-to-match

Re-measured on this branch's base in the CI-equivalent env (.[dev,postgresql,sqlite,codebase,otel] + flashrank, pyright pinned 1.1.410): 418 errors (the committed 568 floors dated from a less-resolved env). Every one was traced to its cause; no rule was disabled, no floor raised, and the only per-site suppression in the tree is the unpublished optional cortex_beam_abstain import whose except ImportError arm is the documented degraded mode.

The classes, each a real contract defect the wrong types had been hiding (the memory rule this program keeps proving: a wrong annotation switches type-checking off for every caller):

  1. Implicit mixin→store contract (~115). The eight PgMemoryStore mixins called self._execute/self._conn/self.batch_pool with no declaration. pg_store_host.PgStoreHost (declaration-only, TYPE_CHECKING-guarded, zero MRO change) makes the contract explicit per DIP §5.1; five dead per-mixin _normalize_memory_row shims removed.
  2. _execute's return-type lie (~60 downstream). Declared -> psycopg.Cursor, actually returned the materialized dict-row surrogate. MaterializedCursor (moved to the host module, rows typed DictRow) makes row["column"] check everywhere — and the honest fetchone() -> DictRow | None surfaced ten unchecked INSERT..RETURNING sites, now MaterializedCursor.one() which raises with the real cause when the database breaks the row-guaranteeing contract.
  3. psycopg-only annotations on cross-backend modules (~30). The 16 shared query modules (pg_store_wiki_*, pg_store_memory_*, near_dup, lesson_promotion) served both psycopg connections and PsycopgCompatConnection while typed for one. db_types.StoreConnection names the real union; both halves now check at every call site.
  4. SQLite store parity (~26) — the largest latent-bug class. Eight methods callers already used unconditionally raised AttributeError on the SQLite backend, swallowed by broad stage boundaries into silent degradation (the FlashRank shape): acquire_interactive/acquire_batch, _execute, search_newer_neighbors, update_forgetting_pressure_accum, get_memories_by_tag, iter_memories_for_decay, find_co_accessed_pairs. All implemented with PG-parity semantics (numpy cosine for the pgvector query, json_each containment for @>, the vec-table client-side join idiom). PG-only capabilities (procedural skills, fallback re-embed worklist) became runtime_checkable protocols with named degraded modes.
  5. Row-binding for psycopg 3.3 (~10). connect() returns Self, so Row binds at the class: psycopg.Connection[DictRow].connect(...) at every connect site (pg_store, hooks, migrate); pools parameterized ConnectionPool[Connection[DictRow]].
  6. LiteralString query discipline (~15). psycopg 3.3 types execute(query) against LiteralString; one cast chokepoint in _execute_on_conn plus per-site casts at the S608-documented allowlist-gated builders, each naming its mechanism.
  7. Report/frontmatter/handler honesty (~120 long tail). Lying return types corrected (encode returns bytes not list[float]; emergence/interference reports carry labels), object-typed frontmatter narrowed at each seam, max(d, key=d.get) rewritten (the None-default overload poisons the key type), hasattr-vs-protocol chosen per seam (MagicMock-pinned observability tests keep hasattr; real-store capability checks use protocols — 3.12+ runtime_checkable resolves members via getattr_static), the synthesized fake-Node hack in ast_extractors replaced by a child-list dispatch helper (§7.2), MemoryStore(...) construction in hook processes routed through get_shared_store (the factory the module itself mandates).

Latent bugs fixed in-PR (each with a regression test)

  • Compat cursor had no executemany → every SQLite upsert_page_sources (wiki page provenance) crashed; added with SQL translation.
  • lastrowid honesty: sqlite3's lastrowid is int | None; the compat layer claimed int and one pre-existing type: ignore masked it. Insert paths now raise ProgrammingError on a broken row-id contract.
  • Installer accepted a cargo-less success: a rust-install result claiming success without a cargo path flowed None into the build argv; refused as missing_toolchain.
  • update_style_ema(None, None) returned None against a dict signature.
  • encode_session died with a bare TypeError deep in omp on a direction-less feature; now refuses loudly naming the feature (positional alignment forbids skipping).
  • get_causal_chain could return reason=None; forgetting sent a None timestamp into the store (now: no timestamp → no newer neighbors, stated).

Gate rewiring, docs, flip

  • typecheck-baseline.json + scripts/check_pyright_ratchet.py deleted; the CI typecheck job is pyright mcp_server/ failing on its own exit code; the env adds [otel] so the exporter imports resolve.
  • pyproject.toml documents every excluded ruff family with a measured count and a stated reason (criterion 1): rejected style-police families (D/ANN/COM/TD/I), rewrite-suggestion families (UP/C4/SIM/PIE/RSE/PERF/RUF), and the untriaged judgement families tracked as the program's continuation in Extend the maximal-strictness lint program: remaining judgement families (B, TRY, RET, ARG, PTH, N, ERA, FBT, DTZ, S-rest) #239.
  • docs/ASSURANCE-CASE.md drops the strictness caveat (criterion 4); CONTRIBUTING.md and the remediation plan close out the ratchet; .bestpractices.json flips warnings_strict to Met quoting the select list, the pyright mode, and the measured zero (criterion 5).
  • tests_py/invariants/test_I2_canonical_writer.py re-pinned in its own commit (same writers, seven line shifts, each verified identical).

Mutation check (scoped, §12)

scripts/mutation_check.sh on the new SQLite parity queries: 0 surviving non-equivalent mutants. The one real survivor (default limit 20→21) is now pinned by test_default_limit_caps_at_twenty; the 21 documented equivalents are SQL keyword/identifier case mutants (SQLite is case-insensitive there) and the deliberately-unused chunk_size signature-parity default.

Evidence

$ pyright mcp_server/            # pinned 1.1.410, typeCheckingMode "standard"
0 errors, 0 warnings, 0 informations
$ ruff check .
All checks passed!
$ ruff format --check .
1051 files already formatted

Full suite (sequential directory chunks on the committed tree; DB_PATH/SQLITE_FALLBACK_PATH/CLAUDE_DIR isolated to temp dirs):

$ pytest tests_py/core -q                          → 3278 passed in 25.20s
$ pytest tests_py/infrastructure tests_py/hooks -q →  907 passed in 26.64s
$ pytest tests_py/handlers -q                      → 1252 passed in 96.31s
$ pytest tests_py --ignore=…                       →  860 passed, 10 subtests passed in 44.46s
Total: 6297 passed, 0 failed — matches pytest --collect-only -q (6297)
$ python scripts/check_doc_claims.py --test-count 6297
doc claims OK
$ pytest tests_py/invariants -q → 52 passed

Monkeypatch seams retargeted per the #238 rule (tests patch the consumer's actual binding): test_agent_briefing/test_ble001_sweep_hooks follow the Connection[DictRow].connect / get_shared_store call paths.

Completion Ledger

§13.1 checklist

Item Status Evidence
A1 happy paths done full suite 6297/6297 quoted above; parity smoke of every new sqlite method in test_sqlite_parity_197.py (17 behavior tests)
A2 edge cases done empty inputs (find_co_accessed_pairs([]), zero query vector), duplicates (pair dedup), ordering (similarity/recency order), limit boundary (21-row default-cap test), substring false-positive (x-tag-suffix), stale exclusion
A3 failure paths done untranslatable SQL → OperationalError asserted; MaterializedCursor.one() empty → raise asserted; lastrowid-None → ProgrammingError asserted; cargo-less install → missing_toolchain asserted; direction-less feature → ValueError naming it; observability seams re-verified (test_pg_recall_silent_failures, test_write_gate, ble001 sweeps green)
A4 boundary validation done no new trust boundary; existing allowlist/refusal mechanisms unchanged (S608 sites keep their named mechanisms; LiteralString casts cite them)
A5 invariants/contracts done new/changed functions carry contract docstrings (one(), executemany, parity methods, PgStoreHost, StoreConnection); I2 invariant re-pinned and green (52 passed)
A6 idempotency N/A no new re-invocable operation; parity methods are reads or single-row UPDATEs mirroring existing PG semantics
B1–B3 concurrency N/A no locking or shared-state change; sqlite acquire_* yield the store's existing single WAL connection (the documented shape of PG's POOL_DISABLED path)
C1 scalability done get_memories_by_tag uses json_each EXISTS instead of a full-table Python filter; search_newer_neighbors documents the established client-side vec-join idiom; iter_memories_for_decay single-chunk rationale stated (local-install corpus, matches PG kill-switch path)
C2 resource lifecycle done no new pools/handles; mutation/venv artifacts cleaned; pip cache purge after installs
C3 hot paths N/A no hot-path change; hook store construction swaps to the cached factory (same construction, documented mandate)
D1 injection done zero new string-built SQL without a named mechanism; ruff S608 gate green; LiteralString casts only at sites already carrying the mechanism noqa
D2 untrusted data done frontmatter/object-typed reads narrowed with isinstance before use (wiki_migrate/schema_loader/sync/identity)
D3 secrets/privilege N/A none touched
E1 API/schema done additive only (new store methods, new one()); no signature of an existing public tool changed
E2 downstream consumers done named per method in the parity commit (anchor, get_rules, backfill, codebase_analyze, consolidation stages, ingest_findings, remember_helpers); suite exercises them
E3 persisted data N/A no format change; no migration
E4 cross-platform done msvcrt/fcntl split now guarded by sys.platform literal (checker resolves per-platform; behavior identical — IS_WINDOWS is defined as exactly that comparison)
F1 failure signals done previously-silent AttributeError paths now either work (parity) or emit named signals (recall_skills backend log, embedding_upgrade reason field, _execute OperationalError to the documented mechanism boundaries) — each asserted
F2 degraded modes explicit done "store has no fallback worklist", "backend has no procedural-skills store", requires_postgresql wiki_view guard (now a TypeGuard)
G1 path→test ledger done table below
G2 regression tests done every latent bug above has a test that fails on the pre-fix code (test_sqlite_parity_197.py, style/sparse/installer/forgetting additions)
G3 deterministic tests done in-memory stores, tmp dirs, no sleeps; suite run twice across chunking (initial + committed tree) with identical tallies
G4 negative assertions done stale rows excluded, substring non-match, older neighbor never returned, store-not-queried-without-timestamp (exploding-store assertion)
G5 suite + lint done quoted above
H1 standards done layering respected (core protocols client-owned; db_types leaf module; host contract TYPE_CHECKING-only); no new file >500 lines; §7.2 hacks removed (fake-Node, runtime attr injection)
H2 simplification done dead shims/else-branches/constants deleted (_normalize_memory_row ×5, hasattr dances ×5, _ENTRY_WITH_CONFIDENCE, stale type: ignore)
H3 conventions done per-mixin annotation idiom kept on the sqlite side; #231's exact acquire_* shape reused so the parallel PR merges clean
H4 CHANGELOG/docs done CHANGELOG entry; CONTRIBUTING, ASSURANCE-CASE, remediation plan, .bestpractices.json all updated in this PR
H5 commit hygiene done 7 conventional commits; re-pin and doc/config changes separated from logic
H6 CI green on pushed tree pending this PR's CI run — all gates reproduced locally above
H7 boy-scout (§14) done every defect seen in touched material fixed in-PR (incl. the pre-existing type: ignore[return-value], the bare hasattr dances, the coverage.py narrowing miss); the one out-of-blast-radius deferral is filed: #239 (remaining lint families), plus pre-existing #233/#237 cycles untouched by this diff

Fixed type-error classes (one row each)

Class (count at base) Fix Evidence
reportAttributeAccessIssue _execute on Pg mixins (115) PgStoreHost typed contract pyright 0; suite green; MRO unchanged (__mro__ probe in commit)
TupleRow __getitem__/row-typing (~60) MaterializedCursor[DictRow] + honest _execute return pyright 0; tests_py/infrastructure 660→green pre-commit
reportOptionalSubscript on RETURNING (10) one() raising contract TestMaterializedCursorOne both arms
union conn vs psycopg-only params (30) StoreConnection in 16 modules pyright 0; wiki suites green
SqliteMemoryStore missing members (26) 8 parity methods implemented test_sqlite_parity_197.py 18 tests; scoped mutation 0 non-equivalent survivors
PgMemoryStore missing capability members (2 sites) runtime_checkable protocols, named degraded modes test_semantic_fallback_169, test_procedural_skill_wiring green
psycopg connect Row-binding (10) Connection[DictRow].connect hooks tests retargeted to the real call path, green
QueryNoTemplate/Template str (15) LiteralString discipline (chokepoint + cited casts + Final DDL provenance) pyright 0; S608 gate green
HandlerFn vs Coroutine (11) safe_handler accepts Awaitable registry suites green
UnionType-not-callable MemoryStore(...) (3) hooks use get_shared_store ble001 sweep test retargeted, green
networkx/frontmatter/report object typing (~40) honest annotations + isinstance seams pyright 0; core suites green
max(d, key=d.get) overloads (3) lambda keys core suites green
remaining long tail (~90) per-site root-cause fixes (listed in commit bodies) pyright 0; full suite 6297/6297

Acceptance criteria of #197

  1. select explicit and broad; every excluded family carries a measured, stated reason (pyproject block; continuation tracked in Extend the maximal-strictness lint program: remaining judgement families (B, TRY, RET, ARG, PTH, N, ERA, FBT, DTZ, S-rest) #239)
  2. ruff check . clean; CI fails on any finding
  3. ✅ pyright standard (above basic) at zero — floors didn't just hold, they ceased to exist
  4. ✅ ASSURANCE-CASE strictness caveat replaced with the factual posture
  5. warnings_strict → Met, citing the configuration and the measured zero

🤖 Generated with Claude Code

Closes #197.

cdeust and others added 7 commits July 28, 2026 14:14
… return type

PgStoreHost (declaration-only, TYPE_CHECKING-guarded) makes the implicit
mixin->store contract explicit per DIP §5.1: every self._execute /
self._conn / self.batch_pool call in the eight persistence mixins now
checks against the real signature. _MaterializedCursor moves to
pg_store_host.MaterializedCursor and _execute's declared return type
stops lying (it never returned a psycopg.Cursor): rows are DictRow, so
row["column"] access type-checks throughout the mixins.

The honest fetchone() -> DictRow | None surfaced ten unchecked
INSERT..RETURNING sites (previously typed as never-None); they now use
MaterializedCursor.one(), which raises with the real cause when the
database breaks the row-guaranteeing contract instead of failing later
on a bare None.

Also removes the five dead per-mixin _normalize_memory_row shims
(PgMemoryStore's own definition always wins the MRO; the shims were
unreachable) per §9.

Pyright (standard, 1.1.410): 568-baseline measured 418 on main in the
CI-equivalent env; this commit brings it to 229.

Refs #197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…backend conn contract, compat executemany parity, lastrowid honesty

- StoreConnection (db_types.py): the 16 shared query modules
  (pg_store_wiki_*, pg_store_memory_*, near_dup, lesson_promotion) were
  annotated psycopg-only while also serving PsycopgCompatConnection on
  SQLite; the union names the real contract, so both halves type-check
  at every call site.
- Latent bug: _CompatExecutingCursor had no executemany, so
  upsert_page_sources (wiki page provenance) raised AttributeError on
  the SQLite backend — same silent-degradation class as #206. Added
  with SQL translation.
- Latent bug: pipeline_installer accepted a rust_install result whose
  action claimed success but carried no cargo path, flowing None into
  the build argv; now refused as missing_toolchain.
- lastrowid honesty: sqlite3's lastrowid is int|None; the compat layer
  claimed int. Insert paths now raise ProgrammingError on the broken
  contract instead of masking it (one pre-existing type: ignore removed).
- MCPClient declares _extra_allowed_commands (was runtime attr injection
  + getattr fallback — §7.2); ap_bridge/mcp_client_pool assignments now
  type-check.
- psycopg LiteralString query typing: batch/staging sink SQL pinned as
  LiteralString; allowlist-gated dynamic SQL (dedup/concepts/drafts)
  re-asserts literalness via cast at the S608-documented sites;
  get_all_ddl re-asserts it once after comment-stripping.
- wiki_stats raises on an impossible empty aggregate instead of
  returning None as dict; _entry_row narrows its tuple union via match
  (len()-vs-constant cannot narrow); dead hasattr-fallback branches
  removed where the compat cursor already guarantees dict rows;
  msvcrt/fcntl split now guarded by sys.platform so the checker
  resolves it per-platform.

pyright (standard, 1.1.410): 229 -> 125; mcp_server/infrastructure/ = 0.

Refs #197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onest report types, capability contracts

Store parity (the largest latent-bug class this family surfaced — every
site below raised AttributeError on the SQLite backend, swallowed by
broad stage boundaries into silent degradation, the FlashRank shape):

- SqliteMemoryStore gains acquire_interactive/acquire_batch (yielding
  the store's single WAL connection — the exact shape PgMemoryStore's
  POOL_DISABLED path yields), _execute (translating passthrough),
  search_newer_neighbors (numpy cosine parity of the pgvector query),
  update_forgetting_pressure_accum, get_memories_by_tag (json_each
  containment), iter_memories_for_decay (single-chunk parity of the PG
  kill-switch path), find_co_accessed_pairs (same memory_entities
  self-join). Callers: anchor, get_rules, backfill, codebase_analyze,
  consolidation (sleep/forgetting/homeostatic/memify/cascade),
  ingest_findings, remember_helpers.
- PG-only capabilities became explicit runtime-checkable protocols with
  named degraded modes: procedural skills (recall_skills), fallback
  re-embed worklist (embedding_upgrade). write_gate/pg_recall keep
  hasattr guards (their observability tests pin MagicMock seams, which
  3.12+ protocol isinstance — getattr_static — correctly rejects).

Honest annotations replacing checked-off lies (each switched type
checking back on for every caller):

- EmbeddingEngine.encode returns bytes, not list[float] (compression);
  fractal scoring takes bytes end-to-end (recall_hierarchical);
  compute_centroid accepts the None-bearing lists it already handles;
  emergence/interference reports carry labels, not float-only dicts;
  update_style_ema pins its dict contract ((None, None) returned None).
- MemoryStore construction in hook processes goes through
  get_shared_store (the factory the module itself mandates); the
  TYPE_CHECKING union alias is no longer called.
- psycopg connect sites parameterize the class
  (Connection[DictRow].connect) so dict rows type through; hook tests
  retargeted to the consumer's actual call path (#238 seam rule).
- safe_handler accepts Awaitable-returning callables (what HandlerFn
  is); max(d, key=d.get) rewritten with lambdas (the None-default
  overload poisons the key type); frontmatter object-reads narrowed at
  each seam (wiki_migrate/schema_loader/sync/identity/draft_compiler);
  the synthesized fake-Node hack in ast_extractors replaced by a
  child-list dispatch helper (§7.2).

Latent bugs fixed beyond the store parity:

- sparse_dictionary.encode_session: a direction-less feature now fails
  loudly with its index/label instead of a bare TypeError inside omp
  (positional alignment forbids skipping).
- get_causal_chain: an absent start entity produced reason=None;
  now always a named reason.
- forgetting: a row without created_at is stated to have no newer
  neighbors (was an implicit SQL NULL-comparison no-op).

pyright (standard, 1.1.410): 125 -> 0 on mcp_server/.

Refs #197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x shifts

Same writers, no new ones: the pg_store MaterializedCursor move and the
sqlite_store parity-method insertions shifted seven pinned line numbers
(pg_store 720->694/762->736/860->834, sqlite_store 537->543/567->573/
631->637, pipeline_impact_bump 177->182). Verified each target is the
identical pre-existing writer.

Refs #197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test_sqlite_parity_197: the full SQLite parity surface (acquire_*,
  _execute translation + untranslatable-SQL degraded mode,
  get_memories_by_tag containment/recency/stale/substring,
  iter_memories_for_decay chunk parity, find_co_accessed_pairs
  ordering/dedup, search_newer_neighbors newer-only/self-exclusion/
  similarity order, update_forgetting_pressure_accum persistence),
  compat executemany translation, the lastrowid-None ProgrammingError,
  and MaterializedCursor.one (row + guaranteed-row violation).
- update_style_ema(None, None) returns {} (was None against a dict
  signature).
- encode_session refuses a direction-less Feature loudly, naming it
  (was a bare TypeError inside omp).
- pipeline installer: a rust_install result claiming success without a
  cargo path is refused as missing_toolchain (was None in the argv).
- forgetting: a memory without created_at yields no newer neighbors and
  never queries the store with a None bound.

Refs #197

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uild; flip warnings_strict to Met (#197)

The 568-diagnostic backlog is zero, so the ratchet's only purpose
(tolerating a backlog without letting it grow) is gone:

- typecheck-baseline.json and scripts/check_pyright_ratchet.py deleted;
  the CI typecheck job runs `pyright mcp_server/` and fails on ANY
  diagnostic via pyright's own exit code. The type-check env adds the
  [otel] extra so the exporter imports resolve (their absence was 4
  spurious reportMissingImports).
- .bestpractices.json: warnings_strict -> Met, citing configuration,
  not promise: the explicit ruff select list
  (E4/E7/E9/F/S110/BLE001/PLR2004/E501/PLC0415/S608), pyright standard
  mode, and the measured zero-diagnostic count (2026-07-28).
  coding_standards_enforced updated off the retired ratchet.
- docs/ASSURANCE-CASE.md: the warnings-strictness caveat is replaced by
  the factual posture (zero at standard; strict remains an
  annotation-coverage project, 10,231 errors measured).
- CONTRIBUTING.md, docs/provenance/pyright-remediation-plan.md:
  ratchet references closed out; the plan is marked historical.
- One narrowing fix (context_assembly/coverage.py): bind the candidate
  embedding before branching — variable-index subscripts do not narrow
  across statements, and the un-narrowed Optional reached np.dot.
- Mutation run on the new sqlite parity queries (scoped,
  scripts/mutation_check.sh): 0 surviving non-equivalent mutants —
  the one real survivor (default limit 20->21) is now pinned by
  test_default_limit_caps_at_twenty; the 21 documented equivalents are
  SQL keyword/identifier case mutants (SQLite is case-insensitive
  there) and the deliberately-unused chunk_size signature-parity
  default.
- Advertised test count synced 6275 -> 6297 everywhere the doc-claim
  gate checks; CHANGELOG entry added.

Closes the acceptance criteria of #197.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… criterion 1)

Three stated exclusion classes in [tool.ruff.lint], each with measured
counts (mcp_server/, 2026-07-28): rejected style-police families
(D/ANN/COM/TD/I — the checking they promise is owned by review, pyright,
the formatter, issue governance, and PLC0415 respectively),
rewrite-suggestion families (UP/C4/SIM/PIE/RSE/PERF/RUF — transformations
of working code outside this program's defect classes), and the untriaged
judgement families (B/TRY/RET/ARG/PTH/N/ERA/FBT/DTZ/S-rest) now tracked
as the program's continuation in #239 — a family stays out deliberately
or lands with every finding triaged, never by accident.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ability

TestSearchNewerNeighbors exercises the vector path, which the PG-matrix
CI jobs (and any Python built without extension support) cannot load —
they got the documented empty degraded result and failed. Reuses the
_needs_vec skip idiom from test_semantic_fallback_169.py; the dedicated
SQLite job (extension present) still executes the class for real.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cdeust
cdeust merged commit de5045a into main Jul 28, 2026
14 checks passed
@cdeust
cdeust deleted the feat/warnings-strict-197-pyright branch July 28, 2026 14:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Maximally strict warnings: broaden the ruff rule set and raise pyright above basic

1 participant