Plugin contract
Framework awareness is pluggable. A plugin contributes exactly four things;
auto-detection makes it zero-config. Types live in src/plugins/types.ts; the
registry in src/plugins/registry.ts.
The contract
Section titled “The contract”interface FrameworkPlugin { name: string; detect(ctx: RepoContext): boolean; entryPatterns(ctx: RepoContext): EntrySpec[]; resolveEdges(ctx: RepoContext, graph: SymbolGraph): SyntheticEdge[]; taintRules(ctx: RepoContext): TaintRule[];}detect— is the framework present? ReadsRepoContextprobes:hasDep([...]),hasConfig([...]),packageJsonHas(key).entryPatterns— roots that are alive by definition, each anEntrySpec { glob, kind: "prod" | "test" }.resolveEdges— synthetic edges the static graph can’t see (e.g. jest__mocks__/foo↔foo).taintRules— regions to downgrade tomayberather than flag.
RepoContext
Section titled “RepoContext”createRepoContext(root) reads package.json and lists the root once, exposing
the three probes above. detectPlugins(plugins, ctx) returns the matching
plugins; resolveEntries aggregates their entryPatterns. When no plugin
matches, the entry set is empty — the signal to degrade candidates to maybe
rather than kill them.
Worked example: the test-runner plugin
Section titled “Worked example: the test-runner plugin”src/plugins/test-runner/ is the reference implementation. It detects
vitest/jest/mocha/playwright, resolves the real test config
(config resolution), marks test files /
setup / config as test entries, links jest __mocks__ siblings via
resolveEdges, and taints non-literal jest.mock(...) calls.
Writing a new plugin
Section titled “Writing a new plugin”- Implement the four methods.
- Make
detectcheap and precise (deps + config presence). - Return
EntrySpecglobs for roots; emitSyntheticEdges only for links the compiler genuinely can’t see. - Prefer taint over false removal when something is ambiguous.
- Register it in the engine’s plugin list.
Plugins for Next.js, NestJS, and template frameworks are planned.