Repository navigation
v0.1.0 — TypeScript support, breaking-change diffs, sandboxed validation
Pre-releaseMODScan scans a codebase and generates everything you need to write plugins and mods for it — it finds the seams a modder can actually hook into, writes grounded documentation for them, and proves each generated example by loading it into the target.
Now on PyPI: pypi.org/project/modscan
pip install modscan[anthropic] # or [openai], [gemini], [typescript]
export ANTHROPIC_API_KEY=sk-ant-...
modscan ./your-project # -> modding-docs/ + extension-points.json
modscan scaffold "pkg.mod:Symbol" # -> a ready-to-edit plugin skeletonWant to look before spending anything? The deterministic layers need no API key
at all:
from modscan import parse_codebase, build_graph, detect_extension_points
cb = parse_codebase("./your-project")
for p in detect_extension_points(build_graph(cb), min_score=0.5)[:10]:
print(f"{p.score:.2f} {p.category} {p.seam.module}:{p.seam.name}")Marked a pre-release: this is a 0.x, the API can still move, and two known
limitations are documented below rather than hidden.
Everything since the MVP: more languages, more surfaces, safer execution, and a
substantial internal cleanup.
Added
- TypeScript/JavaScript front-end (experimental, via tree-sitter). Parses
.ts/.tsx/.js/.jsxinto the shared model, so the graph, detector and docs work
on JS/TS too.--language typescript|javascript; optional dependency
modscan[typescript]. Example execution-validation remains Python-only. - Pluggable language front-ends —
LanguageParserprotocol plus a registry,
so a new language plugs in without touching the graph, detector or generator. modscan diff— compare twoextension-points.jsonmanifests and report
removed / changed / added extension points. Exits non-zero on breaking
changes, so it can gate CI. A copy-paste workflow that comments the diff on a
pull request ships inexamples/ci/breaking-change.yml.modscan config— detect config/data-driven modding surfaces (manifest
files, drop-in directories) that the AST detector cannot see.modscan scaffold --all— generate plugin skeletons for every point in a
manifest in one command.- Subprocess sandbox (
--sandbox) — validate generated examples in an
isolated child process with a timeout, containing hangs and crashes when the
target is less trusted. - On-disk LLM response cache (
--cache-dir) — memoises responses by
provider, endpoint, model and prompt, making re-runs free and offline. - Native Gemini provider (
--provider gemini), alongside Anthropic and the
OpenAI-compatible adapter. - Concurrency for the LLM fan-out (
--concurrency, default 1). A run is
dominated by1 + points x (1..retries)sequential network round-trips; this
is where the wall-clock time is. Output is unaffected — results are collected
in input order and in-process validation is serialised. executedexample status — hook/registration examples are now executed,
not merely compile-checked, so bad imports and runtime errors are caught.- Continuous integration across Python 3.10–3.13, and an opt-in live smoke test
(MODSCAN_LIVE=1) that is the only test allowed to make a real API call. - Contributor documentation (
CONTRIBUTING.md) and README badges.
Changed
- Single source of truth for code execution. Loading a target, executing an
example, finding a concrete subclass and instantiating it previously existed
three times — in the validator, in the generator, and again as a Python string
inside the sandbox runner. It now lives once inmodscan/execution.py, and the
sandbox child imports that module instead of carrying a copy. docgensplit into a package (pipeline,examples,render,types).
The public API is unchanged.- Domain types moved to
models.py(Seam,ExtensionPoint), so layers no
longer import each other merely to name a type. Old import paths still resolve. - Example statuses are an enum (
models.ExampleStatus) instead of strings
re-spelled across four modules. It subclassesstr, so the JSON manifest is
byte-for-byte unchanged. - One filesystem convention —
SKIP_DIRS, source walking and slug
generation live inmodscan/fsutil.py. - Library-safe logging: a
NullHandleron the package root, plus debug logs
where failures were previously swallowed without a trace.
Fixed
- Crash on
typing.Protocolseams. Scanning a codebase that defines a
runtime_checkableProtocol with a data member aborted the run, because
issubclassraises for those and the call sat outside the guard. Found by
running MODScan on itself. - Divergent directory skipping.
SKIP_DIRSwas defined three times with
three different values, so the same tree produced different results depending
on which scanner asked —dist/was walked by the Python parser but skipped
elsewhere,.venv/was walked by the TypeScript one. Now unified. This is a
deliberate behavior change: build output and virtualenvs are consistently
skipped. - More dynamic-import calls detected —
import_string,load_entry_point,
iter_entry_points,load_module,find_specandget_loadernow register
as plugin-loader seams. Thanks to @AjibadeHassan
for the first external contribution (#18). - Silent point loss. Point selection relied on
zip()truncating against a
shorter validation list; the limit is now applied explicitly and the zip is
strict, so a mismatch fails loudly. javascriptexamples are written as.jswith ajavascriptcode fence
(previously.ts/typescript).- Cache keys include the provider and endpoint, so the same model and prompt
against two different backends can no longer collide. - Removed a dead import left behind by the language-front-end integration.
Known limitations
- Imported target modules are never unloaded, so a long-lived process
accumulates them and a second scan of a different tree using the same package
name reuses the first. Restoringsys.modulessafely needs its own change. - Generating docs into the tree being scanned makes the next run pick up its
own generated examples. Write the output elsewhere, or ignore the output
directory.
Full changelog: CHANGELOG.md · Compare: v0.0.1...v0.1.0
Contributions welcome — there are good first issues open, and CONTRIBUTING.md explains the layered architecture. Tests are framework-free and offline: no API key needed to hack on it.