Skip to content

refactor(languages): step 1 — lazy registry, python/typescript packs - #597

Merged
zaebee merged 7 commits into
mainfrom
feat/language-packs-step1
Oct 14, 2026
Merged

zaebee merged 7 commits into
mainfrom
feat/language-packs-step1

Conversation

@zaebee

@zaebee zaebee commented Oct 14, 2026

Copy link
Copy Markdown
Owner

Step 1 of the language-packs migration (spec docs/specs/2026-10-14-language-packs-design.md, plan docs/plans/2026-10-14-language-packs-step1.md, part of #146). Moves code; changes no graph.

What moved

from to
extractors/base.py core/extractor.py (BaseExtractor, ModuleNamer)
extractors/python_extractor.py, _python_{ast,annotations,classes,functions,imports,types}.py languages/python/extractor.py, _{syntax,annotations,classes,functions,imports,types}.py
extractors/typescript_extractor.py, _typescript_bindings.py languages/typescript/extractor.py, _bindings.py
extractors/registry.py languages/__init__.py — lazy registry (deleted, no shim)

New: languages/python/__init__.py and languages/typescript/__init__.py each expose PACK (LanguagePack: name, extensions, code_fence, is_test, scope_of, extractor). The registry holds one Manifest per pack and imports a pack only when a file of its language is met; extractors(roots) is a Mapping that builds each extractor on first lookup. CLI, MCP server, auto-refresh, guardian, mermaid and the scripts go through the registry — the surface-packs allowlist goes from 3 entries to 0.

Identity report

graph_identity.py diff, main's cgis (f2eea44 = c2f4a63 + an fsspec bump, no src/ change) vs this branch's cgis over the same tree:

  • cgis src/ @ 715cd9a — identical (2019 nodes / 7858 edges)
  • owner-api @ 8de2245 — identical (19115 / 96258)
  • tests/fixtures/graph_identity/snapshot.json untouched; the snapshot test passes under both hash seeds.
  • The final reviewer additionally diffed the repo's ui/ (TS/TSX with package.json and tsconfig): identical.

Acceptance

  • A Python-only cgis ingest (fresh interpreter, repo with a .py and a README) imports tree_sitter_python only; control: adding a .ts file loads tree_sitter_typescript. Each of the three laziness sites (pipeline lookup, workspaces, tsconfig) was mutation-checked against it. On main, importing the CLI loaded both grammars.
  • extraction domain re-pointed to cgis.languages: drift 0.278 → 0.315, unresolved 0.357 → 0.346 (lock 0.40 / 0.37 unchanged).

Rulings (where the code departs from the spec's text)

  • R1 "is test" is two rules: the walk's skip (foo.test.ts) is Manifest.never_ingested + skips(); a pack's is_test is the node flag and delegates to core.paths.is_test_path (the Node validator keeps calling it — core cannot import packs).
  • R2 TS has two grammars: extractor(extension, roots) and code_fence(path).
  • R3 extractor_for(path, roots) is realised as the lazy extractors(roots) mapping; IngestionPipeline keeps its Mapping parameter.
  • R4 the contract lives in cgis.core.extractor — the gate reads any cgis.languages.<x> as a pack.
  • R5 the CLI's Python fallback for extensionless paths lives in the registry's scope_of.
  • R6 _python_ast → _syntax (_ast shadows the stdlib module).
  • R7/load each manifest loads its pack through a function holding a static import, not importlib: the review-fingerprint walk cannot follow a dynamic import (test_no_dynamic_imports_in_the_review_path), and mypy now checks each PACK against the protocol.
  • mermaid slugs by the pack that reads the file, not pack_for_node: directory rows from cgis structure and TS nodes stored before fix(resolver): resolve TypeScript imports through tsconfig paths aliases #513 carry Node.language's "python" default (final review finding, fixed test-first).
  • test_agent_ab_task now checks gold files at the task's pinned sha (the tree the runner checks out), not HEAD; review_path_inventory.txt regenerated (lost exactly the moved files, all back under new paths).

Deferred minors (from the final review)

  • A root package.json (prettier/commitlint) in a Python-only repo still loads the TS grammar — workspaces.note() evaluates the lazy extractor before its root check. Main loaded it eagerly anyway.
  • Registry matches by PurePosixPath.suffix, the pipeline by endswith — differs only for a file named exactly .py (pre-existing).
  • docs/architecture/SELF_PORTRAIT.md still names cgis.extractors.* (point-in-time reading).

Gate: format, lint, mypy strict, 3171 passed, docstring 99%. Independent reviewer (execution tools) ran before this PR.

🤖 Generated with Claude Code

zaebee and others added 6 commits October 14, 2026 09:40
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the contract into core

Pure move (git mv) plus import rewrites; the identity snapshot is unchanged.
BaseExtractor/ModuleNamer -> cgis.core.extractor; _python_ast -> _syntax (an `_ast`
module shadows the stdlib one). Self-graph gates re-pointed: Invariant 2 now has a
guard that fails when it examines no node; extraction domain -> cgis.languages
(drift 0.278 -> 0.351, unresolved 0.357 -> 0.355; lock 0.40/0.37 kept).

The A/B task check now looks for gold files at the task's pinned sha (the tree
the runner checks out) instead of HEAD; the review-path inventory is regenerated
(the closure lost exactly the eight moved files, all present under new paths).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tractors/registry

cgis.languages holds one manifest per pack and imports a pack only when a file of
its language is met; extractors(roots) is a Mapping that builds each extractor on
first lookup. CLI, MCP server, auto-refresh, guardian, mermaid and the scripts go
through the registry; the surface-packs allowlist is empty.

Each manifest loads its pack with a static import inside a function rather than
importlib: the review-fingerprint walk cannot follow a dynamic import
(test_no_dynamic_imports_in_the_review_path), and mypy now checks each PACK
against the LanguagePack protocol. Extraction domain re-measured with the
registry in it: drift 0.315, unresolved 0.346 (lock 0.40/0.37).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rammar

The walk's test-file skip comes from the registry manifests (skips), the
pipeline fetches only the extractor a file needs, and the workspace/tsconfig
readers ask for the TS extractor only when a package.json or tsconfig exists.
Pinned by a fresh-interpreter CLI ingest with a README in the repo (a file no
pack reads is what made a value-walking lookup build every extractor), and a
TS control. Each of the three laziness sites was mutation-checked against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Sonar flag

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 1 switched _node_slug to pack_for_node, which reads Node.language. A
directory row from `cgis structure` and a TS node stored before #513 both carry
the "python" default, so their ids lost the package prefix (cgis_query → query,
Button → components_Button). Back to the path lookup main used; pinned by a
package-row test and an old-database TSX test (final review I1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements Step 1 of the Language Packs design by moving the Python and TypeScript extractors into a lazy, manifest-driven registry under cgis.languages and moving the base extractor contract to cgis.core.extractor. All surfaces, scripts, and tests have been updated to interact solely with the registry, allowing language packs to be loaded lazily only when their respective file extensions are encountered. Additionally, the pipeline and workspace/tsconfig readers have been optimized to prevent eagerly loading the TypeScript grammar in Python-only repositories. No review comments were provided, so I have no feedback to provide on the review itself.

@OverRide

Sonar S1172 read PythonPack.code_fence(path) and extractor(extension, ...) as
unused parameters; they are the protocol's signature. Packs now subclass the
protocol and mark each method @OverRide, which Sonar, ruff (ARG002 — the noqa
goes) and mypy all read as an override.

The protocol moves from the registry to cgis.core.extractor beside
BaseExtractor: the registry imports each pack, so a pack importing the protocol
from the registry was a cycle (test_cgis_has_no_circular_dependencies).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@zaebee
zaebee merged commit 64e05c6 into main Oct 14, 2026
4 checks passed
@zaebee
zaebee deleted the feat/language-packs-step1 branch October 14, 2026 10:46
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.

1 participant