Skip to content

fix(deps): pin tree-sitter-language-pack to the range where get_parser().parse(bytes) works (#253) - #261

Merged
cdeust merged 2 commits into
mainfrom
fix-pyright-tree-sitter-tree-253
Jul 29, 2026
Merged

fix(deps): pin tree-sitter-language-pack to the range where get_parser().parse(bytes) works (#253)#261
cdeust merged 2 commits into
mainfrom
fix-pyright-tree-sitter-tree-253

Conversation

@cdeust

@cdeust cdeust commented Jul 29, 2026

Copy link
Copy Markdown
Owner

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 install
resolved pyproject.toml's tree-sitter-language-pack>=0.24.0,<1.14. The two
landed 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:

version SupportedLanguage get_parser parse(b"...") pyright
≤1.6.2 179-name Literal, no csharp SupportedLanguage param ok 1 error
1.6.3 macOS wheel ships no package
1.7.x never published (PyPI goes 1.6.3 → 1.8.0)
1.8.0 symbol absent entirely str param 1 error
1.9.0–1.12.2 306-name Literal builtins.Parser TypeError: 'bytes' object is not an instance of 'str' 1 error
≥1.12.5 306-name Literal tree_sitter.Parser ok 0 errors

_get_extractor_and_tree calls get_parser(language).parse(content) with
content: bytes and catches only ImportError, so on any 1.9.0–1.12.2
resolution codebase_analyze / seed_project die with an uncaught TypeError
on the first file. The pin's own comment described that window ("1.7.0+ …
AttributeError on instances of builtins.Parser") while the pin it annotated
said <1.14 and admitted it.

What changed

  1. pyproject.toml — floor >=0.24.0>=1.12.5, with the measured
    per-release table above as a # source: comment. (>=0.24.0 never matched
    a 0.x release anyway: the pack goes 0.13.0 → 1.0.0.) uv lock re-run with
    the CI-pinned uv 0.11.3; resolution unchanged at 1.13.5, so no requirements
    file moved.
  2. mcp_server/core/ast_parser.py_EXTRACTORS is keyed by the pack's
    SupportedLanguage literal (so the checker validates every grammar name
    against the pack the environment resolved), AST_SUPPORTED is derived
    from that table instead of restated beside it, and _is_ast_language is a
    TypeGuard that narrows before get_parser. Deriving the set deleted an
    unreachable arm: AST_SUPPORTED == set(_EXTRACTORS) held exactly, so
    if not extractor: return None could never fire (§9), and
    _EXTRACTORS[language] is now total after the guard.
  3. CONTRIBUTING.md / CLAUDE.md — both documented installs resolve from
    uv.lock (uv sync), not from the ranges, plus a new
    Reproducing the pyright gate locally section with the exact environment CI
    builds.
  4. .github/workflows/ci.yml — the Type Check job prints the three package
    versions its verdict depends on, one grep per package so a missing one
    fails the step instead of degrading to Unknown inside pyright.

Completion Ledger (§13.2)

Paths introduced by the diff

Path in git diff origin/main...HEAD Evidence
_is_ast_language returns True (name in AST_SUPPORTED) test_is_ast_language_accepts_every_declared_name
_is_ast_language returns False test_is_ast_language_rejects_names_outside_the_set ("", cobol, Python, text)
_get_extractor_and_tree early return on unsupported language test_unsupported_language_falls_back_before_touching_the_pack
_get_extractor_and_tree except ImportError → None test_missing_pack_falls_back_instead_of_raising (sys.modules[...] = None)
_get_extractor_and_tree success → (_EXTRACTORS[language], tree) test_supported_language_with_an_extractor_parses
_EXTRACTORS[language] cannot KeyError test_supported_set_is_exactly_the_extractor_table (equality, not inclusion)
AST_SUPPORTED names are real for the installed pack test_every_supported_name_is_typed_by_the_installed_pack (get_args) + test_every_supported_name_parses_bytes
is_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_absent
build_extra_extractors return-type change test_every_supported_name_parses_bytes exercises all 7 of its keys; pyright validates them against the literal
CONTRIBUTING's uv sync extras == ci-typecheck.txt set test_extras_match_the_ci_typecheck_constraint_set
… groups == typecheck-tool.txt set test_groups_match_the_typecheck_tool_constraint_set
--no-default-groups on both sides test_default_groups_are_excluded_like_the_export
… runs pyright from that venv test_pyright_is_run_from_that_environment
… names the failure mode in both docs test_range_resolution_is_documented_as_forbidden
ci.yml installs the hash-pinned files test_job_installs_the_hash_pinned_files
ci.yml installs the project --no-deps test_job_installs_the_project_without_resolving_its_ranges
ci.yml never range-installs an extra test_job_does_not_pip_install_an_extra_range
ci.yml records the resolved type surface test_job_records_the_resolved_type_surface

Negative controls run for the parity gate (each edit reverted after):
removing --extra otel from the documented block → test_extras_match_…
fails; replacing ci.yml's --no-deps -e . with pip install -e ".[dev,…]"
test_job_installs_the_project_without_resolving_its_ranges and
test_job_does_not_pip_install_an_extra_range fail.

§13.1 checklist

  • A1/A2 happy + edge paths: table above. Edge cases enumerated for the
    narrowing input — empty string, unknown name, wrong case, non-language text.
  • A3 every failure path asserted by its observable effect: both None
    returns and the ImportError degraded mode have tests; there is no signal
    emission on these paths (the fallback to the regex parser is the contract).
  • A4 input validated at the boundary: detect_language output is narrowed
    before it reaches the pack; nothing else crosses.
  • A5/A6 N/A: pure functions, no partial state, no retry semantics.
  • B1–B3 N/A: no concurrency touched.
  • C1 the guard is O(1) (frozenset membership) and replaces an O(1) dict
    .get; no new loops. C2 no resources. C3 no hot path measured
    because none moved.
  • D1–D3 N/A: no queries, no secrets, no new untrusted input (the language
    name was already derived from a path).
  • E1 API change is the dependency floor, documented in CHANGELOG; the
    public AST_SUPPORTED keeps its value and membership semantics.
    E2 consumers: tests_py/core/test_ast_extractors_multilang.py (imports
    AST_SUPPORTED) and parse_file_ast — both pass. E3 N/A: no persisted
    format. E4 the pin covers macOS/Linux/Windows wheels for cp310-abi3.
  • F1/F2 N/A: no new failure mode emits a signal; the existing degraded
    mode (regex fallback) is unchanged and asserted.
  • G1 ledger above. G2 regression control: the contract test fails on
    a version the old pin admitted
    — under tree-sitter-language-pack 1.12.2,
    test_every_supported_name_parses_bytes and
    test_supported_language_with_an_extractor_parses fail with the TypeError.
    G3 deterministic: no temp paths, no sleeps, monkeypatch scoped.
    G4 negative assertions present (_is_ast_language False arm,
    test_job_does_not_pip_install_an_extra_range). G5 below.
  • H1 SOLID/layering/sizes/DI/local-reasoning hold: ast_parser.py is 267
    lines, 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)

$ .venv/bin/python -m pyright mcp_server/          # locked 1.13.5
0 errors, 0 warnings, 0 informations

$ .venv/bin/python -m pyright -p <cfg with 1.13.6 on extraPaths> mcp_server/
0 errors, 0 warnings, 0 informations               # newest the pin admits

$ .venv/bin/python -m pyright -p <cfg with 1.12.5 on extraPaths> mcp_server/
0 errors, 0 warnings, 0 informations               # the new floor

Paired control, same tree, versions the new pin excludes:

1.6.2  -> 1 error   ("csharp" is not in that release's SupportedLanguage)
1.9.0  -> 1 error   (parse(source: str) rejects bytes)
1.11.0 -> 1 error
1.12.0 -> 1 error
1.12.1 -> 1 error
1.12.2 -> 1 error

Runtime probe of the same window:

$ PYTHONPATH=<tslp 1.12.2> python -c "from tree_sitter_language_pack import get_parser; get_parser('python').parse(b'x')"
  parser type: builtins.Parser
  parse(bytes) FAILED: TypeError 'bytes' object is not an instance of 'str'
$ PYTHONPATH=<tslp 1.12.5> ...
  parser type: tree_sitter.Parser
  parse(bytes): module

Suite, lint, and the repo's own gates (G5):

$ pytest -q                       # sqlite backend, DATABASE_URL unset
6367 passed, 81 skipped, 3 warnings, 117 subtests passed in 123.48s

$ ruff check .                    -> All checks passed!
$ ruff format --check .           -> 1069 files already formatted
$ python3 scripts/generate_pip_constraints.py --check   -> requirements OK (13 checked)
$ python3 scripts/check_doc_claims.py --test-count 6448 -> doc claims OK
$ python3 scripts/generate_repo_badges.py --check --test-count 6448 -> badges OK (5 checked)
$ actionlint .github/workflows/ci.yml                   -> no findings

Mutation testing (§12), scoped to the changed file:

$ scripts/mutation_check.sh tests_py/core/test_ast_parser_language_contract.py \
                            mcp_server/core/ast_parser.py
171/171  🎉 8  🫥 163
>>> survivors (must be empty, or documented equivalents):
  none — 0 surviving mutants 🎉

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-language
extractors, 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

  • Fixed here: four pre-existing shellcheck findings in the touched
    ci.yml (SC2015 ×3 — A && B || C retry loops rewritten as
    if …; then break; fi; SC2034 — unused loop variable). actionlint .github/workflows/ci.yml now reports nothing; it reported 4 before.
  • Fixed here: the stale pin comment (claimed <1.7 while pinning <1.14)
    and the >=0.24.0 floor that matches no 0.x release of this package.
  • Deferred with an issue number: the suite emits 3
    DeprecationWarnings for the stdlib sqlite datetime adapter at
    mcp_server/infrastructure/sqlite_compat.py:287. Fixing it changes the
    on-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

Their AC Where it is met
#249-1 narrow at the guard, no cast _is_ast_language TypeGuard + frozenset[SupportedLanguage]; no cast, no type: ignore
#249-2 reproducible env from the lock, version quoted in the job log CI already installs requirements/ci-typecheck.txt (exported from uv.lock, --require-hashes, gated by generate_pip_constraints.py --check); the new Record the resolved type surface step prints it
#249-3 0 errors under both the lock and an unconstrained resolution 1.13.5 (locked) and 1.13.6 (newest admitted) runs above
#249-4 job fails loudly if the env differs from uv.lock --require-hashes + the --check Lint step; the version probe fails on a missing package
#251-1 uv sync + pyright reports 0 documented command in CONTRIBUTING, asserted equal to CI's set by the parity test
#251-2 lock bumped / CI from lock / narrowed all three: lock at 1.13.5, CI installs from it, floor narrowed to the measured range
#251-3 note in CONTRIBUTING/CLAUDE.md added — they are not allowed to differ, and the note says so

CI 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:

Type Check  Record the resolved type surface   pyright==1.1.410
Type Check  Record the resolved type surface   tree-sitter==0.26.0
Type Check  Record the resolved type surface   tree-sitter-language-pack==1.13.5
Type Check  Run pyright (zero-diagnostic gate) 0 errors, 0 warnings, 0 informations

That is the same tree-sitter-language-pack a lock-derived developer install
now resolves — which is the whole point of #253.

@cdeust
cdeust force-pushed the fix-pyright-tree-sitter-tree-253 branch from a8b96e8 to 20f962e Compare July 29, 2026 19:01
cdeust and others added 2 commits July 29, 2026 21:36
…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
cdeust force-pushed the fix-pyright-tree-sitter-tree-253 branch from c50980d to b3a023c Compare July 29, 2026 19:38
@cdeust
cdeust merged commit 8ecdb5d into main Jul 29, 2026
19 checks passed
@cdeust
cdeust deleted the fix-pyright-tree-sitter-tree-253 branch July 29, 2026 22:09
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
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
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
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
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
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
cdeust added a commit that referenced this pull request Jul 30, 2026
…eric + close Swift/Rust test gaps (follow-on to #249, closed by #261) (#270)

* fix(types): narrow ast_parser's language str to a Literal, closing the resolver-dependent typecheck gate (#249)

mcp_server/core/ast_parser.py:88 passed a plain `str` to
tree_sitter_language_pack.get_parser(), whose declared parameter type
differs across releases (a `SupportedLanguage` Literal in 1.6.2 vs plain
`str` in 1.13.5) -- so pyright's zero-diagnostic verdict was a property
of which version pip/uv resolved, not of this source. AST_SUPPORTED is
now a Literal union narrowed via a TypeGuard instead of a bare set[str],
verified clean under both the current uv.lock resolution and a
standalone 1.6.2 install (the only residual error there is 1.6.2's own
SupportedLanguage omitting "csharp" -- a pre-existing grammar-availability
gap in that release, not a narrowing defect).

Boy-scout pass on the touched file (mutation-tested, tools/mutation_check.sh
equivalent, scoped to ast_parser.py + ast_extractor_registry.py): removed
the flat `calls` list every per-language extractor computed and
parse_file_ast immediately discarded (superseded by calls_per_function,
never itself consumed), and closed the resulting zero test coverage for
Swift/Rust parsing plus gaps in is_available, _node_text,
_extract_module_doc, content_hash length, and calls_per_function's
populated content. Filed #269 for the 50 mutmut "survived" reports left
after that pass, which are proven false positives (verified directly via
MUTANT_UNDER_TEST against the full test selection): mutmut's coverage
attribution undercounts callers of a dispatch table built once, eagerly,
at module-import time.

Test count 6526 -> 6549 (+23), resynced across README/CONTRIBUTING/
CLAUDE.md/ASSURANCE-CASE.md/.bestpractices.json/badge-tests.svg via
scripts/check_doc_claims.py + scripts/generate_repo_badges.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u

* chore: reconcile rebase onto main (#261) — recompute suite count, fix 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

* chore(ast): delete extract_calls_generic — its own direct unit test was the last caller (#249 review)

Review finding on PR #270: removing the dead `calls` tuple element from
every per-language extractor (previous commit in this branch) stranded
extract_calls_generic (mcp_server/core/ast_extractors.py) — nothing in
production called it anymore, only TestCallExtraction's two tests called
it directly. Dead code per coding-standards.md §9: deleted the function
and its now-pointless direct unit test, corrected ast_extractors.py's
module docstring (dropped the false "Also provides the generic
call-site extractor used by all languages" claim), and updated
ast_extractor_registry.py's _make_extractor docstring to record that the
function itself, not just its result, is now gone.

_MAX_CALL_NAME_LEN and _walk_type remain live — both are still used by
the per-function, caller-qualified call extraction a few lines down
(extract_calls_per_function), which is the value parse_file_ast actually
consumes.

Suite count recomputed from a live `pytest --collect-only -q` on this
tree (6571, down 2 from 6573 for the deleted tests) and resynced across
CLAUDE.md/README.md/CONTRIBUTING.md/.bestpractices.json/
docs/ASSURANCE-CASE.md/assets/badge-tests.svg;
scripts/check_doc_claims.py --test-count 6571 and
scripts/generate_repo_badges.py --check --test-count 6571 both green.

Verification: tests_py/core/test_ast_parser.py +
test_ast_extractors_multilang.py + test_ast_extractors.py +
test_ast_parser_language_contract.py (69 passed), tests_py/core/ (3384
passed), full suite (6571 passed, 117 subtests passed, exact match to
the recomputed collect-only count), ruff check + ruff format --check
clean on all three changed .py files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u

* refactor(ast): split extract_calls_per_function's nested walk under the 40-line/method cap (boy-scout, CLAUDE.md §4)

Seen while touching this file for the extract_calls_generic deletion:
extract_calls_per_function (51 lines including its nested `walk`
closure) already violated this repo's 40-line/method cap on main,
pre-existing and unrelated to either fix in this branch. Fixed in place
since it is squarely in this diff's blast radius (coding-standards.md
§14.1) and the split carries zero external risk — the public
`extract_calls_per_function(root, source) -> dict[str, list[str]]`
signature and behavior are unchanged, only the internals move.

Split into three functions, each under the cap: `extract_calls_per_function`
(17 lines, unchanged behavior/contract) delegates to `_walk_for_calls`
(36 lines, the former nested closure — now a named top-level helper
taking `out` as an explicit accumulator parameter instead of a captured
free variable) which delegates per-function call collection to
`_collect_call_basenames` (10 lines, the former inner per-call-type loop).
Pure extract-method refactor: no branch, condition, or dedup logic
changed.

Not addressed here: this file's own line count (330 after this commit,
still over the repo's 300-line/file cap — it was already 337 before
either commit in this branch touched it). A real fix needs a genuine
module split (this file has 6 importers: ast_parser.py plus the
clike/extra/jvm/scripting extractor siblings and its own test file —
Move-7's >5-importers High-stakes trigger), which means updating import
statements in 5 other files. That is a separate, dedicated
behavior-preserving-refactor PR per coding-standards.md §15.1 ("never
ship the connection half-made... never mix the enabling refactor and
the feature in one PR"), not a rider on a rebase-plus-one-review-finding
PR already carrying real risk from two prior CONFLICTING states. Flagged
in this session's report for the coordinator rather than filed as an
issue (explicit instruction: no new issues this round).

Verification: tests_py/core/test_ast_parser.py +
test_ast_extractors_multilang.py + test_ast_extractors.py +
test_ast_parser_language_contract.py (69 passed, unchanged), tests_py/core/
(3384 passed, unchanged), ruff check + ruff format --check clean, pyright
0 errors/0 warnings on the changed file. Full suite count unchanged at
6571 (no tests added or removed by this commit).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Yu6EnWspTfqHoGkExyS6u

* chore: rebase onto main and resync the advertised suite size to 6777

Rebase bookkeeping only. Conflicts were confined to the six files that hard-code the suite
size, and only hunks differing solely by that number were resolved (main's figure wins); any
hunk with real content divergence aborts the rebase for human/agent resolution instead of
being guessed at. Count is the measured absolute from a real collection. The branch's own
touched tests were re-run after resolution to catch content loss.

* fix(ast): rebase reconciliation — retire an obsolete mutation-guard test, split test_ast_parser.py under the 300-line cap, dedupe a CHANGELOG entry

Rebasing onto main pulled in #269/#286, which added
test_make_extractor_threads_the_real_source_into_calls_extraction to pin
mutmut mutant x__make_extractor__mutmut_10 (a source-argument mutation on
the extract_calls_generic call _make_extractor used to thread through).
This branch's own earlier commit deletes that exact call site and
extract_calls_generic itself (dead code, issue #249 boy-scout pass), so
the mutant the test existed to kill can no longer be generated from the
source at all -- git's line-based merge didn't conflict (the two edits
touch different lines), but the merged tree failed
test_ast_extractors_multilang.py with AttributeError on
ast_extractor_registry.extract_calls_generic. Root cause: a semantic
merge conflict between two independently-correct changes, invisible to
textual diffing. Fixed by deleting the retired test (its own in-file note
records why) rather than adapting it -- the 2-tuple _make_extractor now
composes exactly two calls, and a source-argument mutant on either is
already killed by the 7 per-language tests (test_java et al.), each of
which asserts on _imports/_defs output that is only correct when the REAL
source bytes reach imports_fn/defs_fn. Verified empirically, not just by
inspection: a scoped mutmut run (ast_extractor_registry.py +
ast_parser.py, full 6-file test selection) reports 50 "survived" mutants
recovered by mutation_recheck_survivors.py's full-selection reverification
(the same eager-import-dispatch-table false-positive pattern #269
diagnosed) -- 0 genuine survivors.

Boy-scout (seen while touching this tree): test_ast_parser.py had grown
to 405 lines under this branch's own Go/Swift/Rust wrapper-test additions,
over CLAUDE.md's 300-line/file cap -- a NEW violation this branch
introduces (main's version is 171 lines), not pre-existing debt. Split
(Extract Module, zero behavior change, same test names/assertions) into
test_ast_parser.py (223 lines), test_ast_parser_node_extraction.py (103
lines: the fake-Node _extract_module_doc/_node_text unit tests) and
test_ast_parser_extra_langs.py (122 lines: the Go/Swift/Rust
parse_file_ast integration tests) -- all three under cap.

Also seen: CHANGELOG.md's [Unreleased] section carried the #249
boy-scout entry TWICE (a stale pre-extract_calls_generic-deletion draft
alongside the final one), both stating a suite count already stale
relative to main's independent growth. Deduped to the single correct
entry; reworded its count claim to a relative delta (net +20 tests) per
the moment any other PR merges first -- assets/badge-tests.svg is the
one artifact that still states one).

Suite count re-measured post-fix: 6776 (pytest --collect-only -q on this
tree), matching the full run (6771 passed + 5 skipped). assets/badge-tests.svg
regenerated via scripts/generate_repo_badges.py --test-count 6776;
scripts/check_doc_claims.py --test-count 6776 and
scripts/generate_repo_badges.py --check --test-count 6776 both green.

Verification: tests_py/core/test_ast_parser.py +
test_ast_parser_node_extraction.py + test_ast_parser_extra_langs.py +
test_ast_extractors.py + test_ast_extractors_multilang.py +
test_ast_parser_language_contract.py (69 passed, unchanged assertions),
tests_py/core/ (3384 passed), full suite (6771 passed, 5 skipped, 116
subtests passed), ruff check + ruff format --check clean on all changed
files, pyright 0 errors/0 warnings on all changed files.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant