Plan
codellm-devkit/.github → docs/design/roadmap.md — candidate 1 of the 2026-08-05 JavaScript/HTML planning pass.
Problem
cants analyzes no JavaScript at all. src/syntactic_analysis/discovery.ts:5 restricts discovery to TypeScript extensions:
const SOURCE_EXTS = new Set([".ts", ".tsx", ".mts", ".cts"]);
A pure-JS project therefore produces an empty result and exits 0 with no warning at default verbosity. Measured on OWASP NodeGoat (50 .js, 24 .html):
$ cants -i nodegoat -a 1 -v
symbol table: 0 built, 0 cached, 0 modules
call graph (jelly): no first-party source files to analyze
$ cat analysis.json
{"symbol_table":{},"call_graph":[],"external_symbols":{},"synthesized_callables":{}}
Everything downstream of discovery is already prepared for JS and is simply never handed a file:
allowJs: true is already the default compiler option (src/syntactic_analysis/symbolTable.ts:115-126), and a missing tsconfig.json is a silent fallback, not an error (src/build/materialize.ts:54).
- Jelly itself accepts
.js/.mjs/.cjs/.jsx, but is fed project.getSourceFiles() (src/semantic_analysis/jellyProvider.ts:217-222), which only ever contains the four TS extensions.
-t/--target-files does not rescue it: resolveTargetFiles has no extension filter (discovery.ts:66-76), but symbolTable.ts:97 only resolves files discovery already added, so a .js path silently continues.
README.md:7 and README.md:18 both advertise "TypeScript/JavaScript static analyzer". That claim is currently false.
Confirming a rename is sufficient: copying NodeGoat and renaming its 50 .js → .ts (no other edit) produced 27 modules, 24 callables, 170 call-graph edges, and full L4 — cfg/ddg on 24/24 callables, 22 summaries, 38 param_in, 20 param_out. The pipeline works on this code; only the extension gate stops it.
Scope boundary
Targets the 0.x maintenance line only — branch off tag v0.5.0, release as 0.6.0. That is where consumers actually are: python-sdk/pyproject.toml:43 pins codeanalyzer-typescript==0.4.3 and cldk/models/typescript/models.py:24 is still the v1 flat TSApplication{symbol_table, call_graph} shape, so nothing shipped on main since schema v2 reaches any consumer.
This issue does not:
- model CommonJS
require / module.exports / exports.x — separate work item, roadmap candidate 4;
- touch HTML, templates, or any
.html/.hbs handling — roadmap candidates 6–8;
- change output shape in any way (
v0.5.0 emits the v1 schema, which has no can:// ids and no manifest language field — there is no vocabulary at stake here);
- forward-port to
main — separate PR on the 1.x line;
- bump the
python-sdk pin — separate PR in that repo, once 0.6.0 is published.
Goals
Caveats and known risks
- The tsc resolver leg degrades sharply on untyped JS. Measured on the renamed NodeGoat: 20 resolved vs 174 unresolved call sites, 0 RTA expansions; Jelly supplied 131 of the 170 union edges. Mitigation: leave
--call-graph-provider defaulted to union and do not document --tsc-only as viable for JS. This is inherited imprecision, not a defect introduced here.
- Imports and exports will be empty on CommonJS input.
builders.ts:862-927 reads only getImportDeclarations()/getExportDeclarations(); measured 0 imports and 0 exports across 27 NodeGoat modules. Documented gap, deliberately deferred to the CommonJS work item rather than silently absorbed.
- Callable coverage is thin on idiomatic Express code — 24 callables across 27 modules. The cause is not JavaScript: methods assigned as
this.x = fn inside a constructor function land in local_variables rather than becoming callables, and object-literal methods are not materialized at all. Both are missed identically in TypeScript — NodeGoat renamed .js → .ts yields the same 24 callables — so this is a language-neutral symbol-table gap, tracked separately. It dominates recall on this codebase: 11% of call sites resolve here against 81% for idiomatic TypeScript (sample-app). Jelly recovers much of the call graph regardless (88 synthesized callables), so edges stay usable where the symbol table is sparse.
- Vendored or bundled JS.
SKIP_DIRS covers dist/build/out/vendor, but a JS project may keep third-party code in lib/ or public/. Risk of analyzing minified vendor bundles; mitigation is to leave SKIP_DIRS unchanged and document the exposure rather than guess at new exclusions.
dist/cants in the repo is stale (dated Jul 1, predates schema v2 — it emits the v1 flat shape). Unrelated to this change, but it will mislead anyone verifying by running the checked-in binary rather than bun run src/index.ts.
Definition of done
- On OWASP NodeGoat unmodified,
cants -i nodegoat -a 2 yields a symbol_table whose key set equals the set of .js files found by find app server.js -name '*.js', hand-checked — not merely "non-empty".
- The new JS fixture has a checked-in expected symbol table; the test fails on the parent commit (empty) and passes after.
call_graph on the JS fixture is non-empty under the default union provider, and the tsc-only and jelly-only edge counts are both asserted so a future regression in either leg is visible.
- Existing TypeScript fixtures produce byte-identical output before and after, demonstrated by running the current determinism gate — the extension change must not perturb any TS result.
cants -i <dir-with-no-sources> emits a visible warning and a non-zero-information message rather than a silent empty success.
Plan
codellm-devkit/.github→docs/design/roadmap.md— candidate 1 of the 2026-08-05 JavaScript/HTML planning pass.Problem
cantsanalyzes no JavaScript at all.src/syntactic_analysis/discovery.ts:5restricts discovery to TypeScript extensions:A pure-JS project therefore produces an empty result and exits 0 with no warning at default verbosity. Measured on OWASP NodeGoat (50
.js, 24.html):Everything downstream of discovery is already prepared for JS and is simply never handed a file:
allowJs: trueis already the default compiler option (src/syntactic_analysis/symbolTable.ts:115-126), and a missingtsconfig.jsonis a silent fallback, not an error (src/build/materialize.ts:54)..js/.mjs/.cjs/.jsx, but is fedproject.getSourceFiles()(src/semantic_analysis/jellyProvider.ts:217-222), which only ever contains the four TS extensions.-t/--target-filesdoes not rescue it:resolveTargetFileshas no extension filter (discovery.ts:66-76), butsymbolTable.ts:97only resolves files discovery already added, so a.jspath silentlycontinues.README.md:7andREADME.md:18both advertise "TypeScript/JavaScript static analyzer". That claim is currently false.Confirming a rename is sufficient: copying NodeGoat and renaming its 50
.js→.ts(no other edit) produced 27 modules, 24 callables, 170 call-graph edges, and full L4 —cfg/ddgon 24/24 callables, 22 summaries, 38param_in, 20param_out. The pipeline works on this code; only the extension gate stops it.Scope boundary
Targets the 0.x maintenance line only — branch off tag
v0.5.0, release as0.6.0. That is where consumers actually are:python-sdk/pyproject.toml:43pinscodeanalyzer-typescript==0.4.3andcldk/models/typescript/models.py:24is still the v1 flatTSApplication{symbol_table, call_graph}shape, so nothing shipped onmainsince schema v2 reaches any consumer.This issue does not:
require/module.exports/exports.x— separate work item, roadmap candidate 4;.html/.hbshandling — roadmap candidates 6–8;v0.5.0emits the v1 schema, which has nocan://ids and no manifestlanguagefield — there is no vocabulary at stake here);main— separate PR on the 1.x line;python-sdkpin — separate PR in that repo, once0.6.0is published.Goals
SOURCE_EXTSincludes.js,.jsx,.mjs,.cjs(discovery.ts:5)discovery.ts:26— currently/\.(test|spec)\.(ts|tsx|mts|cts)$/)SKIP_DIRS(discovery.ts:7-19) still excludes generated output for JS-shaped repos (dist,build,out,vendor,coverage)test/fixtures/—test/fixtures/is currently 100%.tsv0.5.0; release0.6.0Caveats and known risks
--call-graph-providerdefaulted tounionand do not document--tsc-onlyas viable for JS. This is inherited imprecision, not a defect introduced here.builders.ts:862-927reads onlygetImportDeclarations()/getExportDeclarations(); measured 0 imports and 0 exports across 27 NodeGoat modules. Documented gap, deliberately deferred to the CommonJS work item rather than silently absorbed.this.x = fninside a constructor function land inlocal_variablesrather than becoming callables, and object-literal methods are not materialized at all. Both are missed identically in TypeScript — NodeGoat renamed.js→.tsyields the same 24 callables — so this is a language-neutral symbol-table gap, tracked separately. It dominates recall on this codebase: 11% of call sites resolve here against 81% for idiomatic TypeScript (sample-app). Jelly recovers much of the call graph regardless (88 synthesized callables), so edges stay usable where the symbol table is sparse.SKIP_DIRScoversdist/build/out/vendor, but a JS project may keep third-party code inlib/orpublic/. Risk of analyzing minified vendor bundles; mitigation is to leaveSKIP_DIRSunchanged and document the exposure rather than guess at new exclusions.dist/cantsin the repo is stale (dated Jul 1, predates schema v2 — it emits the v1 flat shape). Unrelated to this change, but it will mislead anyone verifying by running the checked-in binary rather thanbun run src/index.ts.Definition of done
cants -i nodegoat -a 2yields asymbol_tablewhose key set equals the set of.jsfiles found byfind app server.js -name '*.js', hand-checked — not merely "non-empty".call_graphon the JS fixture is non-empty under the defaultunionprovider, and the tsc-only and jelly-only edge counts are both asserted so a future regression in either leg is visible.cants -i <dir-with-no-sources>emits a visible warning and a non-zero-information message rather than a silent empty success.