`.mts` (ESM) and `.cts` (CommonJS) are the TypeScript counterparts of the
already-supported `.mjs`/`.cjs`, but graphify handled them in neither the
code-extension detection nor the TypeScript grammar path — so they were either
skipped entirely (classified as non-code) or, once detected, parsed with the plain
JavaScript grammar, which silently drops every `type`/`interface`/type-alias
declaration.
Detection — add `.mts`/`.cts` alongside their siblings in:
- detect.CODE_EXTENSIONS (the code / non-code gate)
- analyze._LANG_FAMILY (cross-language edge family → "js")
- extract._JS_RESOLVE_EXTS (import target resolution)
- extract._JS_CACHE_BYPASS_SUFFIXES
- build.py per-node language map (→ "js")
TypeScript grammar — route `.mts`/`.cts` to the plain TypeScript grammar (like
`.ts`; neither is JSX), so their TS-only syntax is captured:
- extract_js() (grammar selector: .mts/.cts → _TS_CONFIG)
- extract._DISPATCH (.mts/.cts → extract_js)
- the use_ts tree-sitter selector
- the typescript_member_calls resolver frozenset
Result: a `.mts`/`.cts` file now extracts identically to the equivalent `.ts`.
Verified on a real 47 KB `.mts` source — 36 nodes for `.mts`/`.cts`, matching `.ts`
exactly (was 22 with the JS grammar; the 14 dropped nodes were TS type/interface
declarations). No new dependency — same tree-sitter-typescript grammar as `.ts`.
Tests: tests/test_typescript_module_extensions.py — membership regression locks for
the extension sets, dispatch routing, and end-to-end parity asserting `.mts`/`.cts`
capture the TS `type`/`interface` nodes and match the `.ts` node set.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>