Scaffolds a complete graphlens language adapter package from scratch. Use when asked to create an adapter, add language support, scaffold an adapter, or implement a new language adapter for graphlens. Produces all 5 source modules, pyproject.toml, and test stubs.
インストール
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
Scaffolds a complete graphlens language adapter package from scratch. Use when asked to create an adapter, add language support, scaffold an adapter, or implement a new language adapter for graphlens. Produces all 5 source modules, pyproject.toml, and test stubs.
compatibility
Requires Python 3.13+, tree-sitter>=0.24, a tree-sitter-<lang> grammar package, uv workspace
allowed-tools
Bash Read Write Edit WebSearch
graphlens Adapter Generator
Generates a production-ready graphlens-<lang> adapter package following the exact architecture of graphlens-python.
Quick Start
WebSearch the target language (Step 0) — extensions, package managers, stdlib names
Tree-sitter only — every adapter must use tree-sitter as its parser (no stdlib ast, no regex)
Pure data producers — adapters return a GraphLens; they never write to files, databases, or any backend
Entry points — adapters register via importlib.metadata entry points; callers use adapter_registry.load()
Deterministic node IDs — always use make_node_id(project_name, qualified_name, kind.value) (SHA-256[:16])
1-based spans — tree-sitter positions are 0-based; always add +1 to row and col when constructing Span
ImportClassifier pre-pass — build ImportClassifier(stdlib, third_party, internal) before visiting any file; every IMPORT node must have metadata["origin"] set
Three stacks — visitor maintains _scope_stack, _container_stack, _kind_stack for scope tracking
dep_parsers constructor param — adapters accept a custom parser list so callers can inject non-standard package managers
name_span on structural nodes — visitor records metadata["name_span"] (Span of the name token) on every CLASS, FUNCTION, METHOD, VARIABLE, ATTRIBUTE, TYPE_ALIAS, PARAMETER node so the SpanIndex can map definition positions back to node IDs
OccurrenceRef collection — visitor collects OccurrenceRef objects (role, 1-based position of the name token, enclosing node ID) for every use-site but does not emit CALLS, REFERENCES, HAS_TYPE, or INHERITS_FROM edges directly — those are produced by the post-visit resolution pass
SymbolResolver — each adapter ships a SymbolResolver subclass (_resolver.py) that wraps a type-aware engine; it must never raise — all errors return None/[]
Post-visit resolution pass, ONCE for the whole analyze() call, never per sub-root — after every sub-root's structure is built: build SpanIndex from the now-complete graph, call resolver.prepare(project_root, all_files)once, rooted at the top-level project_root with the union of every sub-root's files, then for each OccurrenceRef call resolver.definition_at() and emit the correct edge or fall back to EXTERNAL_SYMBOL. Calling resolver.prepare() inside the per-sub-root loop is a bug: it scopes the resolver's workspace to just that one sub-root (so cross-sub-root references can never resolve) and pays a full subprocess-spawn + re-index cost per sub-root instead of once.
Step-by-Step Generation Process
Step 0 — Research the language (WebSearch)
Before collecting anything from the user, perform web searches to build accurate language knowledge:
File extensions — search "{language} source file extensions" and "tree-sitter-{lang} grammar". Identify all commonly used extensions (e.g. .ts, .tsx, .d.ts for TypeScript). Include declaration/header files if they contain importable symbols.
All mainstream package managers (e.g. npm, yarn, pnpm for Node; cargo for Rust; go mod for Go)
The manifest file name(s) each one uses
Where declared dependencies live inside each manifest (key paths)
Whether dev/test groups are separate keys
Module system — search "{language} import system" and "{language} module resolution". Understand:
How file paths map to importable names
Relative vs absolute import syntax
How the language's equivalent of __init__.py / index.ts works
Standard library / built-ins — search "{language} standard library modules list". Collect the top-level names callers import from the stdlib.
Document findings before proceeding to Step 1. This research drives file_extensions(), {LANG}_MARKERS, DependencyFileParser implementations, and get_stdlib_names().
Step 1 — Collect inputs from user
Required:
Language name (e.g. typescript, rust, go) — used for {lang} placeholder
tree-sitter grammar package (e.g. tree-sitter-typescript) — PyPI package name
{Lang}ASTVisitor with visit() dispatch via getattr(self, f"_visit_{node.type}", None)
_visit_children() for default traversal
Three stacks initialized in __init__: _scope_stack, _container_stack, _kind_stack
occurrences: list[OccurrenceRef] field on the visitor — filled during traversal
Handlers for class/struct, function/method, import, variable, attribute nodes
Every structural node (CLASS, FUNCTION, METHOD, VARIABLE, ATTRIBUTE, TYPE_ALIAS, PARAMETER) records metadata["name_span"] = Span of the name token
Call sites, read/write uses, type annotations, base classes → append OccurrenceRef to self.occurrences; do not emit CALLS/REFERENCES/HAS_TYPE/INHERITS_FROM edges
Collect all OccurrenceRef objects from each visitor into a flat list
Link PROJECT → top-level modules via CONTAINS
Return (project_id, project_name, occurrences, ...) — do not resolve anything here
Phase 2 — in analyze(), once for the whole call, after every sub-root's
Phase 1 has run:
11. Build SpanIndex(graph) from the now-complete graph (spans every sub-root)
12. Call resolver.prepare(project_root, all_files)once, rooted at the
top-level project_root, with the union of every sub-root's files
13. For each OccurrenceRef (across every sub-root) call
resolver.definition_at(file, line, col) → use SpanIndex.at() to find
the target node → emit the correct edge (CALLS/REFERENCES/HAS_TYPE/
INHERITS_FROM) or fall back to _get_or_create_external_symbol()
Monorepo rule: one resolver session for the whole analyze() call
A monorepo can yield dozens of sub-roots (e.g. a split-package repo with a
manifest per component — laravel/framework's illuminate/* sub-packages
is the canonical example). Calling resolver.prepare() once per sub-root,
scoped to that sub-root's own directory, is wrong twice over: (1) the
resolver's workspace never contains sibling sub-roots' files, so
cross-sub-root references can never resolve no matter how good the resolver
is, and (2) each sub-root pays its own subprocess spawn + full re-index from
zero, which dominates wall-clock on a repo with many sub-roots. This applies
unconditionally — don't try to detect whether sub-roots are "really the same
codebase" first; merging unrelated sub-roots into one resolver session costs
a bit of extra indexing but is never incorrect. See
packages/graphlens-rust/src/graphlens_rust/_adapter.py's analyze() for
the reference implementation.
Step 10 — Generate __init__.py
"""graphlens_{lang} — {Language} language adapter for graphlens."""from graphlens_{lang}._adapter import {Lang}Adapter
from graphlens_{lang}._resolver import {Lang}Resolver
__all__ = ["{Lang}Adapter", "{Lang}Resolver"]
Create packages/graphlens-{lang}/ruff.toml. The key rule: [lint.per-file-ignores] must relax annotation, docstring, and security rules for tests/** so pytest code doesn't require full production-grade typing.
test_{lang}_deps.py — each parser's can_parse and parse, get_stdlib_names
test_{lang}_visitor.py — visitor unit tests per node type; assert occurrences collected, name_span recorded; assert no CALLS/REFERENCES/HAS_TYPE/INHERITS_FROM edges emitted by visitor
test_{lang}_resolver.py — {Lang}Resolver.definition_at() and infer_type_at(); assert never raises
test_{lang}_adapter.py — end-to-end: real source snippets → correct graph structure; assert resolved CALLS/REFERENCES/HAS_TYPE/INHERITS_FROM edges point to real declaration nodes