fix(deps): pin tree-sitter-language-pack to the range where get_parser().parse(bytes) works (#253) - #261
Merged
Merged
Conversation
cdeust
force-pushed
the
fix-pyright-tree-sitter-tree-253
branch
from
July 29, 2026 19:01
a8b96e8 to
20f962e
Compare
…r().parse(bytes) works (#253) The pyright gate's verdict depended on which installer built the environment: CI installs the hash-pinned export of uv.lock, the documented developer install resolved pyproject.toml's >=0.24.0,<1.14 range, and the two landed seven minor versions apart on tree-sitter-language-pack — one error in one environment, zero in the other, on the same commit. Probing every published wheel found a live defect the old floor admitted: 1.9.0-1.12.2 return a builtins.Parser whose parse(source: str) rejects bytes, so get_parser("python").parse(b"...") raises TypeError uncaught and codebase_analyze crashes. The floor moves to 1.12.5, the first release where the whole call chain is valid and type-checkable. - pyproject: floor 1.12.5, with the measured per-release type surface - ast_parser: _EXTRACTORS keyed by the pack's SupportedLanguage literal, AST_SUPPORTED derived from it (deleting an unreachable fallback arm), _is_ast_language TypeGuard narrows before get_parser - CONTRIBUTING/CLAUDE: both documented installs resolve from uv.lock - ci.yml: the Type Check job prints the versions its verdict depends on - tests: language contract (parses with every declared grammar) and typecheck-env parity (docs vs pip_constraint_sets vs ci.yml) Boy-scout (§14): four pre-existing shellcheck findings in the touched ci.yml (SC2015 x3, SC2034) fixed; the suite's 3 sqlite datetime DeprecationWarnings are outside this change's blast radius and filed as Closes #253 Closes #249 Closes #251 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
Rebase bookkeeping after #259 merged; no behaviour change. The earlier rebase resolved the six suite-size files to main's side wholesale, which deleted 49 lines of this branch's own documentation — including the '### Reproducing the pyright gate locally' section that tests_py/scripts/test_typecheck_env_parity.py asserts, failing 5 of its 9 cases. This time the branch's doc additions are re-applied as a PATCH against the new main (git apply --3way) so both sides' prose survives; the only conflicts were the hard-coded count, resolved to the current figure. Count is the measured absolute (6548), not a delta from main's advertised number: CI proved on #259 that this machine's collection matches CI's canonical exactly, so the delta arithmetic I used before was the wrong instrument. Verified: test_typecheck_env_parity.py 9 passed; check_doc_claims.py OK; generate_repo_badges.py --check OK; .bestpractices.json parses and carries no conflict markers (the class that shipped once before, now gated by check_no_conflict_markers).
cdeust
force-pushed
the
fix-pyright-tree-sitter-tree-253
branch
from
July 29, 2026 19:38
c50980d to
b3a023c
Compare
cdeust
added a commit
that referenced
this pull request
Jul 29, 2026
origin/main advanced to 6548 tests (PR #261, tree-sitter-language-pack pin + its own test additions) between this branch's creation and this merge, superseding my prior 6526->6527 resync. Merged origin/main, resolved the doc-count conflicts by taking main's committed values, then re-derived the true paired count on the FULLY MERGED tree via `pytest --collect-only -q` (6549 = 6548 + this branch's one new test), not by arithmetic on stale numbers. Resynced the same 6 files + regenerated assets/badge-tests.svg. Both check_doc_claims.py --test-count 6549 and generate_repo_badges.py --check --test-count 6549 verified green locally, and the full suite (6549 tests) passes on the merged tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
cdeust
added a commit
that referenced
this pull request
Jul 29, 2026
#250) (#272) * fix(shared): kill 9 real json_native mutants, document 5 as equivalent (#250) Scoped mutmut run on the committed [tool.mutmut] scope (mcp_server/shared/ json_native.py) reproduced exactly the reported 14 survivors: 9 were a real test gap on the tolist()-failure debug log (format string + args never asserted), 5 are typing.cast/codec-name mutations that are provably equivalent at runtime (verified via inspect.getsource(typing.cast) and codecs.lookup identity, not just asserted). - tests_py/shared/test_json_native.py: new TestTolistFailureLogging test forces the tolist() exception path and asserts the exact log record (format string + both args via caplog), killing mutants 34-42. - mcp_server/shared/json_native.py: documented the 5 remaining survivors as equivalent at their use sites (§12.1), and split the 60-line to_json_native (over this repo's 40-line/coding-standards' 50-line cap) into _decode_bytes/_coerce_number/_tolist_fallback + a thin dispatcher — each helper under 20 lines, existing 14 tests passing byte-for-byte unchanged as the behavior-preservation proof (boy-scout: the function was already over cap before this change). Re-run after the split: 51 mutants, 0 surviving non-equivalent (same 5 equivalents renumbered under the new helper names). Fixes #250 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u * docs: resync advertised test count 6526 -> 6527 (#250) The new TestTolistFailureLogging test in the prior commit adds exactly one collected test (paired count verified locally and against CI's own `pytest --collect-only` output: both report 6527). This repo hard-codes the suite size in 6 files plus a committed badge SVG, checked by a blocking CI gate (`scripts/check_doc_claims.py` / `generate_repo_badges.py --check`) — CI's Test (Python 3.12) leg correctly failed on the stale 6526 claim. Resynced: README.md (x2), CONTRIBUTING.md (x2), CLAUDE.md, docs/ASSURANCE-CASE.md, .bestpractices.json (x4), assets/badge-tests.svg (regenerated via scripts/generate_repo_badges.py --test-count 6527). Both gates verified green locally with the same --test-count CI uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u * docs: resync test count 6548 -> 6549 after merging main (#250) origin/main advanced to 6548 tests (PR #261, tree-sitter-language-pack pin + its own test additions) between this branch's creation and this merge, superseding my prior 6526->6527 resync. Merged origin/main, resolved the doc-count conflicts by taking main's committed values, then re-derived the true paired count on the FULLY MERGED tree via `pytest --collect-only -q` (6549 = 6548 + this branch's one new test), not by arithmetic on stale numbers. Resynced the same 6 files + regenerated assets/badge-tests.svg. Both check_doc_claims.py --test-count 6549 and generate_repo_badges.py --check --test-count 6549 verified green locally, and the full suite (6549 tests) passes on the merged tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
cdeust
added a commit
that referenced
this pull request
Jul 29, 2026
…the lockfile (#251) Adds a `uv lock --check` step to the lint job, before the existing hash-pinned-requirements check, so a future pyproject.toml constraint change that isn't accompanied by a lock refresh fails at commit time instead of resolving silently differently per install path (the drift that shipped as #251). Rebased onto main (7f3ebf9), which had since landed #253/#261 -- main's tree-sitter-language-pack floor bump (>=1.12.5) already supersedes this branch's own stale-comment correction on the same line, so the redundant/superseded block is dropped rather than kept alongside main's (duplicate package-version lines is what broke `uv lock --check`: pyproject.toml carried two conflicting specifiers for the same package after an earlier bad merge). Reworded the new step's own comment: it quoted the literal historical `pip install -e ".[...]"` command as prose, which collides with tests_py/scripts/test_typecheck_env_parity.py:: test_job_does_not_pip_install_an_extra_range -- a whole-file assertNotIn added on main after this branch was cut, forbidding that exact substring anywhere in ci.yml (including comments, since a contributor could copy-paste it as real usage). Reworded to describe the same historical fact without the literal substring; the assertion itself is untouched per coding-standards.md §6 (never weaken a gate to make it pass). Verified on this tree: - `uv lock --check` -> `Resolved 195 packages in 3ms`, exit 0 - `pytest tests_py/scripts/test_typecheck_env_parity.py -q` -> 9 passed - `pytest tests_py/core/test_ast_parser.py -q` -> 19 passed - `python scripts/check_doc_claims.py` -> OK - `python scripts/generate_repo_badges.py --check` -> OK - `python scripts/generate_pip_constraints.py --check` -> OK - `actionlint .github/workflows/ci.yml` -> no findings - `grep 'pip install -e ".\[' .github/workflows/ci.yml` -> no match (exit 1) - YAML/TOML round-trip parse clean Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
cdeust
added a commit
that referenced
this pull request
Jul 29, 2026
… CHANGELOG claim Rebasing onto main pulled in #261/#253/#251, which independently closed #249 (the resolver-dependent typecheck defect this branch was also fixing) via the same TypeGuard-narrowing mechanism, already keyed off the language pack's own SupportedLanguage type and with AST_SUPPORTED derived from _EXTRACTORS rather than restated. This branch's own duplicate _SupportedLanguage/_is_ast_supported/AST_SUPPORTED-via-get_args mechanism is dropped in favour of main's (ast_parser.py, ast_extractor_registry.py); this branch's actual surviving contribution — removing the dead `calls` tuple element from every extractor and adding the missing Swift/Rust/ is_available/_node_text/_extract_module_doc/content_hash/calls_per_function test coverage — is preserved and now sits on top of main's mechanism cleanly (auto-merged with no conflicts in ast_extractor_registry.py). CHANGELOG's #249 entry rewritten: it no longer claims to close #249 (already closed by #261) and instead describes only the boy-scout dead-code-removal and test-coverage work this branch still adds. Suite count recomputed from a live `pytest --collect-only -q` on this machine (6572, up from main's 6549 by the 23 tests this branch adds) per the standing rule that the count is a measured absolute, not a delta from an advertised number: CLAUDE.md, README.md, CONTRIBUTING.md, .bestpractices.json, docs/ASSURANCE-CASE.md, assets/badge-tests.svg. Verified: scripts/check_doc_claims.py --test-count 6572 and scripts/generate_repo_badges.py --check --test-count 6572 both green; tests_py/scripts/test_typecheck_env_parity.py, test_check_doc_claims.py, test_generate_repo_badges.py all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
7 tasks
cdeust
added a commit
that referenced
this pull request
Jul 30, 2026
…the lockfile (#251) (#268) Adds a `uv lock --check` step to the lint job, before the existing hash-pinned-requirements check, so a future pyproject.toml constraint change that isn't accompanied by a lock refresh fails at commit time instead of resolving silently differently per install path (the drift that shipped as #251). Rebased onto main (7f3ebf9), which had since landed #253/#261 -- main's tree-sitter-language-pack floor bump (>=1.12.5) already supersedes this branch's own stale-comment correction on the same line, so the redundant/superseded block is dropped rather than kept alongside main's (duplicate package-version lines is what broke `uv lock --check`: pyproject.toml carried two conflicting specifiers for the same package after an earlier bad merge). Reworded the new step's own comment: it quoted the literal historical `pip install -e ".[...]"` command as prose, which collides with tests_py/scripts/test_typecheck_env_parity.py:: test_job_does_not_pip_install_an_extra_range -- a whole-file assertNotIn added on main after this branch was cut, forbidding that exact substring anywhere in ci.yml (including comments, since a contributor could copy-paste it as real usage). Reworded to describe the same historical fact without the literal substring; the assertion itself is untouched per coding-standards.md §6 (never weaken a gate to make it pass). Verified on this tree: - `uv lock --check` -> `Resolved 195 packages in 3ms`, exit 0 - `pytest tests_py/scripts/test_typecheck_env_parity.py -q` -> 9 passed - `pytest tests_py/core/test_ast_parser.py -q` -> 19 passed - `python scripts/check_doc_claims.py` -> OK - `python scripts/generate_repo_badges.py --check` -> OK - `python scripts/generate_pip_constraints.py --check` -> OK - `actionlint .github/workflows/ci.yml` -> no findings - `grep 'pip install -e ".\[' .github/workflows/ci.yml` -> no match (exit 1) - YAML/TOML round-trip parse clean Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
cdeust
added a commit
that referenced
this pull request
Jul 30, 2026
… CHANGELOG claim Rebasing onto main pulled in #261/#253/#251, which independently closed via the same TypeGuard-narrowing mechanism, already keyed off the language pack's own SupportedLanguage type and with AST_SUPPORTED derived from _EXTRACTORS rather than restated. This branch's own duplicate _SupportedLanguage/_is_ast_supported/AST_SUPPORTED-via-get_args mechanism is dropped in favour of main's (ast_parser.py, ast_extractor_registry.py); this branch's actual surviving contribution — removing the dead `calls` tuple element from every extractor and adding the missing Swift/Rust/ is_available/_node_text/_extract_module_doc/content_hash/calls_per_function test coverage — is preserved and now sits on top of main's mechanism cleanly (auto-merged with no conflicts in ast_extractor_registry.py). CHANGELOG's #249 entry rewritten: it no longer claims to close #249 (already closed by #261) and instead describes only the boy-scout dead-code-removal and test-coverage work this branch still adds. Suite count recomputed from a live `pytest --collect-only -q` on this machine (6572, up from main's 6549 by the 23 tests this branch adds) per the standing rule that the count is a measured absolute, not a delta from an advertised number: CLAUDE.md, README.md, CONTRIBUTING.md, .bestpractices.json, docs/ASSURANCE-CASE.md, assets/badge-tests.svg. Verified: scripts/check_doc_claims.py --test-count 6572 and scripts/generate_repo_badges.py --check --test-count 6572 both green; tests_py/scripts/test_typecheck_env_parity.py, test_check_doc_claims.py, test_generate_repo_badges.py all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #253. Also closes #249 and #251 — three filings of the same defect, and
this change meets all three acceptance-criteria lists (reconciliation below).
The defect, and what was under it
The pyright gate is zero-diagnostic, but its input was not pinned. CI
installs the hash-pinned export of
uv.lock; the documented developer installresolved
pyproject.toml'stree-sitter-language-pack>=0.24.0,<1.14. The twolanded seven minor versions apart, so the same commit reported 1 error to a
contributor and 0 to CI.
Probing every published wheel to find where the type surface actually changes
turned up a live runtime defect the old floor admitted:
SupportedLanguageget_parser→parse(b"...")csharpSupportedLanguageparamstrparambuiltins.ParserTypeError: 'bytes' object is not an instance of 'str'tree_sitter.Parser_get_extractor_and_treecallsget_parser(language).parse(content)withcontent: bytesand catches onlyImportError, so on any 1.9.0–1.12.2resolution
codebase_analyze/seed_projectdie with an uncaughtTypeErroron the first file. The pin's own comment described that window ("1.7.0+ …
AttributeErroron instances ofbuiltins.Parser") while the pin it annotatedsaid
<1.14and admitted it.What changed
pyproject.toml— floor>=0.24.0→>=1.12.5, with the measuredper-release table above as a
# source:comment. (>=0.24.0never matcheda 0.x release anyway: the pack goes 0.13.0 → 1.0.0.)
uv lockre-run withthe CI-pinned uv 0.11.3; resolution unchanged at 1.13.5, so no requirements
file moved.
mcp_server/core/ast_parser.py—_EXTRACTORSis keyed by the pack'sSupportedLanguageliteral (so the checker validates every grammar nameagainst the pack the environment resolved),
AST_SUPPORTEDis derivedfrom that table instead of restated beside it, and
_is_ast_languageis aTypeGuardthat narrows beforeget_parser. Deriving the set deleted anunreachable arm:
AST_SUPPORTED == set(_EXTRACTORS)held exactly, soif not extractor: return Nonecould never fire (§9), and_EXTRACTORS[language]is now total after the guard.CONTRIBUTING.md/CLAUDE.md— both documented installs resolve fromuv.lock(uv sync), not from the ranges, plus a newReproducing the pyright gate locally section with the exact environment CI
builds.
.github/workflows/ci.yml— the Type Check job prints the three packageversions its verdict depends on, one
grepper package so a missing onefails the step instead of degrading to
Unknowninside pyright.Completion Ledger (§13.2)
Paths introduced by the diff
git diff origin/main...HEAD_is_ast_languagereturns True (name inAST_SUPPORTED)test_is_ast_language_accepts_every_declared_name_is_ast_languagereturns Falsetest_is_ast_language_rejects_names_outside_the_set("",cobol,Python,text)_get_extractor_and_treeearly return on unsupported languagetest_unsupported_language_falls_back_before_touching_the_pack_get_extractor_and_treeexcept ImportError → Nonetest_missing_pack_falls_back_instead_of_raising(sys.modules[...] = None)_get_extractor_and_treesuccess →(_EXTRACTORS[language], tree)test_supported_language_with_an_extractor_parses_EXTRACTORS[language]cannotKeyErrortest_supported_set_is_exactly_the_extractor_table(equality, not inclusion)AST_SUPPORTEDnames are real for the installed packtest_every_supported_name_is_typed_by_the_installed_pack(get_args) +test_every_supported_name_parses_bytesis_available()True / False arms (touched file, previously unasserted)test_is_available_is_true_when_the_pack_imports/test_is_available_is_false_when_the_pack_is_absentbuild_extra_extractorsreturn-type changetest_every_supported_name_parses_bytesexercises all 7 of its keys; pyright validates them against the literaluv syncextras ==ci-typecheck.txtsettest_extras_match_the_ci_typecheck_constraint_settypecheck-tool.txtsettest_groups_match_the_typecheck_tool_constraint_set--no-default-groupson both sidestest_default_groups_are_excluded_like_the_exporttest_pyright_is_run_from_that_environmenttest_range_resolution_is_documented_as_forbiddentest_job_installs_the_hash_pinned_files--no-depstest_job_installs_the_project_without_resolving_its_rangestest_job_does_not_pip_install_an_extra_rangetest_job_records_the_resolved_type_surfaceNegative controls run for the parity gate (each edit reverted after):
removing
--extra otelfrom the documented block →test_extras_match_…fails; replacing ci.yml's
--no-deps -e .withpip install -e ".[dev,…]"→test_job_installs_the_project_without_resolving_its_rangesandtest_job_does_not_pip_install_an_extra_rangefail.§13.1 checklist
narrowing input — empty string, unknown name, wrong case, non-language text.
Nonereturns and the
ImportErrordegraded mode have tests; there is no signalemission on these paths (the fallback to the regex parser is the contract).
detect_languageoutput is narrowedbefore it reaches the pack; nothing else crosses.
.get; no new loops. C2 no resources. C3 no hot path measuredbecause none moved.
name was already derived from a path).
public
AST_SUPPORTEDkeeps its value and membership semantics.E2 consumers:
tests_py/core/test_ast_extractors_multilang.py(importsAST_SUPPORTED) andparse_file_ast— both pass. E3 N/A: no persistedformat. E4 the pin covers macOS/Linux/Windows wheels for cp310-abi3.
mode (regex fallback) is unchanged and asserted.
a version the old pin admitted — under
tree-sitter-language-pack 1.12.2,test_every_supported_name_parses_bytesandtest_supported_language_with_an_extractor_parsesfail with theTypeError.G3 deterministic: no temp paths, no sleeps,
monkeypatchscoped.G4 negative assertions present (
_is_ast_languageFalse arm,test_job_does_not_pip_install_an_extra_range). G5 below.ast_parser.pyis 267lines, every function under 50, core still imports only core+shared, the new
pack import is
TYPE_CHECKING-only. H2 the unreachable arm is gone.H4 CHANGELOG entry added. H5 one logic commit.
H7 boy-scout: below.
Evidence (commands + output)
Paired control, same tree, versions the new pin excludes:
Runtime probe of the same window:
Suite, lint, and the repo's own gates (G5):
Mutation testing (§12), scoped to the changed file:
All 8 mutants of the changed functions (
is_available,_is_ast_language,_get_extractor_and_tree) were killed; the 163 the run reports as "no tests"belong to
parse_file_ast(57),_extract_module_doc(37) and the per-languageextractors, which this scoped selection does not import — untouched by this
diff and covered by other test files (§12.1 scopes the blocking tier to the
changed lines).
Boy-scout (§14) — H7
ci.yml(SC2015×3 —A && B || Cretry loops rewritten asif …; then break; fi;SC2034— unused loop variable).actionlint .github/workflows/ci.ymlnow reports nothing; it reported 4 before.<1.7while pinning<1.14)and the
>=0.24.0floor that matches no 0.x release of this package.DeprecationWarnings for the stdlib sqlitedatetimeadapter atmcp_server/infrastructure/sqlite_compat.py:287. Fixing it changes theon-disk datetime spelling (§13.1 E3) and needs its own migration decision;
filed as sqlite_compat.py relies on the deprecated stdlib datetime adapter (3 DeprecationWarnings in the suite) #260, outside this change's blast radius (no import path from
core/ast_parser.py).Reconciliation with #249 and #251
_is_ast_languageTypeGuard+frozenset[SupportedLanguage]; nocast, notype: ignorerequirements/ci-typecheck.txt(exported fromuv.lock,--require-hashes, gated bygenerate_pip_constraints.py --check); the new Record the resolved type surface step prints ituv.lock--require-hashes+ the--checkLint step; the version probe fails on a missing packageuv sync+ pyright reports 0CI on the pushed tree (H6)
Run 30477779566 —
all 18 required checks pass (Lint, Type Check, Build Package, Test 3.10/3.11/
3.12/3.13, Test SQLite, Test Windows-SQLite, 3× CodeQL Analyze, 2× Docker
Build, Docker Smoke, 2× Fuzz PR batch). The Type Check job's new step and its
verdict:
That is the same
tree-sitter-language-packa lock-derived developer installnow resolves — which is the whole point of #253.