Language Extension API

For supported formats and a custom-language registration example, see Language Support.

Registry

contextcraft.default_registry() → LanguageRegistry

Returns a new registry containing the built-in language classes. Register your own language before passing it to ContextCraft(registry=registry).

class contextcraft.LanguageRegistry

Register language metadata and resolve paths to ordered parser candidates.

Multiple languages may share an extension or filename. Registration order determines parser preference; duplicate IDs and aliases are rejected.

detect_candidates(path: str) → list[type[Language]]

Prefer exact filenames over case-insensitive extensions; return a new list.

get(language_id: str) → type[Language] | None
property ignore_patterns: tuple[str, ...]
property languages: tuple[type[Language], ...]
register(language: type[Language]) → None
  • register() accepts a Language subclass, not an instance. Duplicate IDs or aliases raise ValueError; multiple languages may share an extension.

  • get() resolves an ID or alias, returning None for an unknown name.

  • detect_candidates() prefers an exact, case-sensitive filename match over an extension match. Extensions are case-insensitive. Candidates retain registration order; an unknown path returns an empty list.

  • languages is a tuple of registered classes. ignore_patterns combines their ignore patterns without duplicates.

Language definition

The following types live in contextcraft.languages.base.

class contextcraft.languages.base.LanguageSpec(id: 'str', extensions: 'frozenset[str]' = frozenset(), filenames: 'frozenset[str]' = frozenset(), ignore_patterns: 'tuple[str, ...]' = (), parser_id: 'str | None' = None, aliases: 'tuple[str, ...]' = ())

Field

Meaning

id

Unique registry ID

extensions

Filename suffixes including the leading dot, such as .py

filenames

Exact filenames such as Dockerfile

ignore_patterns

Language-specific scanner exclusions

parser_id

Grammar name passed to tree-sitter-language-pack, or None

aliases

Additional names accepted by registry lookup

class contextcraft.languages.base.Language
classmethod build_scopes(root: Node, source: bytes) → ScopeGraph
classmethod build_selected(parsed: ParsedAst, symbols: list[Symbol]) → str

Render a subset, falling back to body-elided native source when needed.

classmethod parse(root: Node, source: bytes) → ParsedAst

A subclass supplies four class attributes: spec, extractor, skeleton_builder, and signatures_builder. The extractor and builders are classes, not instances. parse() and build_scopes() delegate to a fresh extractor instance.

Extractors

class contextcraft.languages.base.ASTExtractor(*args, **kwargs)
build_scopes(root: Node, source: bytes) → ScopeGraph
extract(root: Node, source: bytes) → ParsedAst

This protocol accepts a tree-sitter root node and the original UTF-8 bytes. extract() returns a ParsedAst; build_scopes() returns a ScopeGraph.

class contextcraft.languages.base.BaseExtractor
build_scopes(root: Node, source: bytes) → ScopeGraph
extract(root: Node, source: bytes) → ParsedAst

BaseExtractor.extract() creates a ParsedAst using build_scopes() and the root’s error status. Override build_scopes() at minimum; override extract() to add symbols and imports.

contextcraft.ast.parser.parse(source: bytes, language: type[Language]) → ParsedAst

This helper loads the configured grammar and calls the language extractor. With parser_id=None, it returns a source-only AST. Grammar-loading failures return a source-only AST with has_errors=True.

Builders

class contextcraft.languages.base.SkeletonBuilder(spec: LanguageSpec)
abstractmethod build(parsed: ParsedAst) → str
class contextcraft.languages.base.SignaturesBuilder(spec: LanguageSpec)
abstractmethod build(parsed: ParsedAst) → str
build_selected(parsed: ParsedAst, symbols: list[Symbol]) → str

Render selected declarations with their enclosing type context.

Both builders receive a LanguageSpec at construction. Implement build(parsed) to return text; token counting and tier fallback are handled by Elider. Builders should use the supplied AST rather than reparsing the source.

SignaturesBuilder.build_selected(parsed, symbols) renders a subset of declarations. Its default implementation includes their ancestors and calls build() with a filtered symbol list, preserving the original AST. Override it if a custom builder needs additional context.

Language.build_selected() uses the signature builder, falling back to a source-preserving skeleton when needed.

Built-in signature builders trim known unused named imports using their grammar. This covers Python, JavaScript/TypeScript/TSX, Rust, Go, Java, C#, Kotlin, Scala, Swift, and PHP where the declaration identifies a binding. Trimming preserves whole grouped declarations if any binding is needed. Runtime loads such as Ruby require, R library, shell source, C/C++ headers, namespace imports, wildcards, and other unclassifiable forms remain intact. Imports and source ranges on the original AST are never changed.

Scope data

These types live in contextcraft.ast.scope.

class contextcraft.ast.scope.ScopeGraph(scopes: 'list[Scope]' = <factory>, bindings: 'list[Binding]' = <factory>, references: 'list[Reference]' = <factory>)

scopes, bindings, and references hold the extracted lexical scope graph.

class contextcraft.ast.scope.Scope(start_byte: 'int', end_byte: 'int', parent: 'Scope | None' = None, kind: 'str' = 'block', bindings: 'dict[str, Binding]'=<factory>, references: 'list[Reference]' = <factory>)
resolve(name: str) → Binding | None

resolve(name) searches the current scope and then its parents, returning the nearest Binding or None.

class contextcraft.ast.scope.Binding(name: 'str', start_byte: 'int', end_byte: 'int', scope: 'Scope')
class contextcraft.ast.scope.Reference(name: 'str', start_byte: 'int', end_byte: 'int', scope: 'Scope')

Bindings and references carry a name, half-open byte offsets, and their scope.