The typecheck CI job (.github/workflows/ci.yml:445) is a zero-diagnostic
gate on a pinned pyright (1.1.410, ci.yml:437), but the environment it types
against is not pinned: the job builds .venv with
pip install -e ".[dev,postgresql,sqlite,codebase,otel]" flashrank, which
resolves tree-sitter-language-pack freely inside >=0.24.0,<1.14
(pyproject.toml:77). The repo's own uv.lock pins a different version, and
the two resolutions disagree about whether the tree is clean.
Reproduction (2026-07-29, macOS 15.5, worktree at f606c37)
$ uv sync --all-extras
$ uv run --no-sync python -c "import importlib.metadata as m; print(m.version('tree-sitter-language-pack'))"
1.6.2
$ uv pip install pyright==1.1.410
$ .venv/bin/python -m pyright mcp_server/
mcp_server/core/ast_parser.py
mcp_server/core/ast_parser.py:88:23 - error: Argument of type "str" cannot be
assigned to parameter "name" of type "SupportedLanguage" in function "get_parser"
Type "str" is not assignable to type "SupportedLanguage"
"str" is not assignable to type "Literal['actionscript']"
... (reportArgumentType)
1 error, 0 warnings, 0 informations
CI reports 0 on the same commit (gh run list --branch main --workflow ci.yml:
f606c37 → success), because pip resolves the newest allowed release —
1.13.5 today — whose get_parser signature differs from 1.6.2's.
Root cause
mcp_server/core/ast_parser.py:88 calls get_parser(language) with a plain
str. The value IS narrowed at runtime (if language not in AST_SUPPORTED: return None, line 78), but membership in a set[str] does not narrow str to
the SupportedLanguage Literal union for a type checker. Under the older
stub the call is an error; under the newer one it is not. So the gate's verdict
is a property of the resolver, not of the source.
This is the "unpinned linter is not a gate" failure mode: a green
zero-diagnostic job that would go red (or hide a real diagnostic) on a
dependency bump nobody reviewed.
Acceptance criteria
mcp_server/core/ast_parser.py:88 type-checks clean under both
resolutions — narrow language to the checker's satisfaction at the
AST_SUPPORTED guard (e.g. make AST_SUPPORTED a frozenset of the
Literal members and have _get_extractor_and_tree return early on a
TypeGuard), rather than casting the error away.
- The typecheck job installs a reproducible environment — the same
resolution the repo commits (uv sync --locked) — so the gate measures the
source, not the resolver. Quote the installed
tree-sitter-language-pack version in the job log.
python -m pyright mcp_server/ reports 0 errors under both the uv.lock
resolution and a fresh unconstrained pip resolution; both runs quoted in the
PR.
- A regression guard: the typecheck job fails loudly if the resolved
environment differs from uv.lock (that is what uv sync --locked gives).
Found during #233 (synaptic-plasticity import-cycle fix); the diagnostic is in
an unrelated file that #233 does not touch, so it is deferred here per the
boy-scout rule rather than folded into that PR.
The
typecheckCI job (.github/workflows/ci.yml:445) is a zero-diagnosticgate on a pinned pyright (
1.1.410, ci.yml:437), but the environment it typesagainst is not pinned: the job builds
.venvwithpip install -e ".[dev,postgresql,sqlite,codebase,otel]" flashrank, whichresolves
tree-sitter-language-packfreely inside>=0.24.0,<1.14(
pyproject.toml:77). The repo's ownuv.lockpins a different version, andthe two resolutions disagree about whether the tree is clean.
Reproduction (2026-07-29, macOS 15.5, worktree at
f606c37)CI reports 0 on the same commit (
gh run list --branch main --workflow ci.yml:f606c37→success), because pip resolves the newest allowed release —1.13.5today — whoseget_parsersignature differs from1.6.2's.Root cause
mcp_server/core/ast_parser.py:88callsget_parser(language)with a plainstr. The value IS narrowed at runtime (if language not in AST_SUPPORTED: return None, line 78), but membership in aset[str]does not narrowstrtothe
SupportedLanguageLiteralunion for a type checker. Under the olderstub the call is an error; under the newer one it is not. So the gate's verdict
is a property of the resolver, not of the source.
This is the "unpinned linter is not a gate" failure mode: a green
zero-diagnostic job that would go red (or hide a real diagnostic) on a
dependency bump nobody reviewed.
Acceptance criteria
mcp_server/core/ast_parser.py:88type-checks clean under bothresolutions — narrow
languageto the checker's satisfaction at theAST_SUPPORTEDguard (e.g. makeAST_SUPPORTEDafrozensetof theLiteralmembers and have_get_extractor_and_treereturn early on aTypeGuard), rather than casting the error away.resolution the repo commits (
uv sync --locked) — so the gate measures thesource, not the resolver. Quote the installed
tree-sitter-language-packversion in the job log.python -m pyright mcp_server/reports0 errorsunder both theuv.lockresolution and a fresh unconstrained pip resolution; both runs quoted in the
PR.
environment differs from
uv.lock(that is whatuv sync --lockedgives).Found during #233 (synaptic-plasticity import-cycle fix); the diagnostic is in
an unrelated file that #233 does not touch, so it is deferred here per the
boy-scout rule rather than folded into that PR.