NoWreck v0.3.0 β JavaScript Support π
NoWreck v0.3.0 β JavaScript Support π
Deterministic verification for AI code changes β now for JavaScript too.
What's new in v0.3.0
The headline feature is JavaScript support β NoWreck can now scan, parse, and verify JavaScript files alongside Python files in the same repository. This is the first time the pipeline has been extended beyond Python, and it's designed so that adding future languages follows the same pattern.
JavaScript scanning via Tree-sitter
JavaScript files (.js) are now parsed using Tree-sitter with the tree-sitter-javascript grammar, producing the same Symbol / SymbolType data shapes as the Python parser. The rest of the pipeline β change detection, claim verification, and reporting β never knows or cares which language produced the data.
What the JS scanner captures:
| Pattern | Example | Status |
|---|---|---|
| Function declarations | function greet() {} |
β |
| Arrow functions (assigned) | const greet = () => {} |
β |
| Classes | class Calculator {} |
β |
| Class methods | add(a, b) { ... } |
β |
export function |
export function greet() {} |
β |
export class |
export class Calculator {} |
β |
export const (arrow) |
export const greet = () => {} |
β |
| Function calls | greet() in function bodies |
β (simple calls only) |
Mixed-language repos
NoWreck handles repositories with both .py and .js files in a single scan. The pre/post summaries show separate file counts for each language:
Scan Summary
ββββββββββββ
Python: 5 files (3 success, 0 failed)
JavaScript: 3 files (3 success, 0 failed)
Symbols: 22 total (12 functions, 4 classes, 6 methods)
Same 7 claim types β unchanged
No new claim types were added. The same 7 deterministic claims work across both languages:
ADD_FUNCTION/REMOVE_FUNCTIONADD_CLASS/REMOVE_CLASSFILE_CREATED/FILE_DELETEDCALLS_FUNCTION
A claim about a Python function and a claim about a JavaScript function are verified identically β the verifier doesn't need to know which language produced the data.
Lazy dependency loading
The tree-sitter-javascript dependency is loaded lazily β it's only imported when a JavaScript file is actually scanned. This means:
- Pure-Python repos work without
tree-sitter-javascriptinstalled - No cascading import failures in the Python-only test suite
- All 388 existing pytest tests pass without the JS grammar
Full changelog
Added
- New module:
nowreck/scanner/javascript_scanner.pyβ Tree-sitter-based JavaScript parser with lazy grammar loadingscan_js_file(path)β parses a.jsfile and returnslist[Symbol]scan_js_calls(source_code, symbols)β extractscall_expressionnodes and returnslist[DetectedChange]forCALL_DETECTED_get_js_language()β lazy-loadstree-sitter-javascriptwith double-checked caching
SymbolIndex.js_symbolsproperty β returns only JavaScript symbols (those without anastmodule origin)SymbolIndex.by_name(name)β language-agnostic lookup (finds symbols across both languages)RepositoryScannerextended β discovers.jsfiles, routes them tojavascript_scanner, populatesScanResult.js_filesChangeDetector.detect()extended β extracts JScall_expressionnodes alongside Pythonast.CallwalkingTerminalReporterextended β shows Python vs JS file counts and language-specific symbol breakdowns in scan summaries_strip_markdown_fence()inclaims/parser.pyβ handles```json...```from model output
Changed
- Bumped version from v0.2.0 β v0.3.0
- Updated
pyproject.tomlβ addedtree-sitter-javascript>=0.25dependency - Updated
.gitignoreβ added_binary_test.binunder test artifacts - Updated
README.mdβ JS integration documented in scan stage, limitations, roadmap, troubleshooting, and tips - Updated
use.mdβ version references, JSON schema examples - Fixed pre-existing ruff issues in
tests/test_picker.pyandtests/test_picker_integration.py(unused imports, line lengths) - Fixed pre-existing type errors in
nowreck/picker.py(missingquestionarystub) - Fixed ordering-sensitive flaky test (
test_empty_prompt_flow) intest_picker_integration.py
Known limitations (v0.3.0)
- Generator functions (
function*) β not captured (deferred) export defaultβ not captured (deferred)- IIFEs β not captured (deferred)
- TypeScript β not yet supported (separate scope, deferred)
- Attribute calls (e.g.,
console.log(),obj.method()) β excluded fromCALLS_FUNCTIONdetection; only simplename()calls are tracked - All existing Python limitations still apply (no dynamic behavior, no cross-file resolution beyond direct name matching, no semantic analysis)
Test statistics
| Suite | Tests | Result |
|---|---|---|
| Python unit tests (pytest) | 388 | β 388/388 pass |
| JS multi-round (repeatability, stress, chaos) | 80 | β 80/80 pass |
| JS comprehensive (core, edge cases, negatives, error handling) | 78 | β 78/78 pass |
| Milestone 1 β pure Python repo | 10 | β Deterministic (3x) |
| Milestone 1 β pure JS repo | 10 | β Deterministic (3x) |
| Milestone 1 β mixed repo | 10 | β Deterministic (3x) |
| Full pipeline determinism (mixed repo, 5 runs) | 1 | β Identical every run |
| Phase 4a end-to-end demo (14 hand-written claims) | 14 | β Pipeline clean |
| Phase 4d live-model hallucination-catch (real API) | 1 | β Hallucination caught |
| Total | ~590 | β All pass |
| Linting (ruff) | β | β 0 issues |
| Type checking (basedpyright) | β | β 0 errors, 0 warnings, 0 notes |
How it works
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Prompt mode β
β β
β Your prompt βββΊ AI model βββΊ diff + claims β
β β β
β βΌ β
β Pre-scan βββΊ Symbol index βββΊ Change Detector β
β Post-scan βββΊ Symbol index βββββββββ β
β β β
β Claims βββΊ Claim Verifier ββββββββββββββ β
β β pure comparison β no AI judgment β
β βΌ β
β Verification Report β
β β CONFIRMED β CONTRADICTED ? UNVERIFIABLE β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Scan stage (now with dual-language support)
- Discover β recursively finds
.pyand.jsfiles in both snapshots - Parse β routes each file to the correct parser:
.pyβ Python's built-inastmodule.jsβ Tree-sitter withtree-sitter-javascriptgrammar
- Index β builds a unified
SymbolIndexfrom both languages
Both parsers produce identical Symbol / SymbolType data shapes (using LanguageAdapter patterns anticipated in earlier architecture). The rest of the pipeline is language-agnostic.
Quick start with JavaScript
# Create a JS test repo
mkdir -p /tmp/js-app/pre /tmp/js-app/post
cat > /tmp/js-app/pre/greeter.js << 'EOF'
function greet(name) {
return "Hello, " + name + "!";
}
EOF
cat > /tmp/js-app/post/greeter.js << 'EOF'
function greet(name) {
return "Hello, " + name + "!";
}
const farewell = (name) => "Goodbye, " + name + "!";
EOF
# Detect changes
nowreck fix --pre /tmp/js-app/pre --post /tmp/js-app/post
# Or with claims that include a hallucinated call
nowreck fix \
--pre /tmp/js-app/pre \
--post /tmp/js-app/post \
--claims '{
"claims": [
{
"type": "ADD_FUNCTION",
"symbol_name": "farewell",
"file_path": "greeter.js",
"confidence": 0.99,
"explanation": "Added farewell arrow function."
},
{
"type": "CALLS_FUNCTION",
"symbol_name": "farewell",
"file_path": "greeter.js",
"caller_name": "farewell",
"called_name": "notify",
"confidence": 0.85,
"explanation": "farewell calls notify to send the message."
}
]
}'
# Expected: ADD_FUNCTION CONFIRMED, CALLS_FUNCTION CONTRADICTED
# (farewell exists, but it doesn't call notify)Mixed Python + JavaScript
nowreck fix --pre ./before --post ./after
# β Shows Python: 3 files, JavaScript: 2 files in scan summaryDesign decisions
Why Tree-sitter?
JavaScript has no equivalent of Python's built-in ast module, so an external parser was required. Tree-sitter was chosen because:
- Mature JS grammar β well-maintained
tree-sitter-javascriptwith broad coverage - Reusable pattern β same parser family would be used for Go, Rust, etc.
- Concrete syntax trees β provides real source positions for accurate claim-to-diff line mapping
Why lazy imports?
The tree-sitter-javascript grammar is imported lazily (only when a .js file is actually scanned). This ensures:
- The Python-only test suite doesn't cascade-fail on import
- Pure-Python repos work without the JS grammar installed
- The dependency is a hard install requirement (in
pyproject.toml) but a soft runtime requirement
Why no new claim types?
The existing 7 claim types (ADD_FUNCTION, REMOVE_FUNCTION, ADD_CLASS, REMOVE_CLASS, FILE_CREATED, FILE_DELETED, CALLS_FUNCTION) are language-agnostic by design. Adding JS-specific claim types (e.g., around export/import patterns) is a deliberate future-scope decision, not something to add mid-build because it seems easy.
Architecture summary
| Component | v0.2.0 (Python only) | v0.3.0 (Python + JS) |
|---|---|---|
| Scanner | ast only |
ast + Tree-sitter |
| Symbol index | Python symbols only | Unified index (both languages) |
| Change detector | ast.Call walking |
ast.Call + Tree-sitter call_expression |
| Claim verifier | Language-agnostic | Unchanged β still language-agnostic |
| Reporter | Python-only summaries | Python + JS breakdowns |
| CLI interface | β | Unchanged β fully backwards compatible |
Credits
Built using Tree-sitter and the tree-sitter-javascript grammar. All existing Python infrastructure remains unchanged β this was purely additive.
NoWreck v0.3.0 β July 2026