Skip to content

NoWreck v0.3.0 β€” JavaScript Support πŸŽ‰

Choose a tag to compare

@AstralXVoid AstralXVoid released this 29 Jul 10:21
· 16 commits to main since this release
4feefdc

NoWreck v0.3.0 β€” JavaScript Support πŸŽ‰

Deterministic verification for AI code changes β€” now for JavaScript too.

Python
License: FSL-1.1-MIT

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_FUNCTION
  • ADD_CLASS / REMOVE_CLASS
  • FILE_CREATED / FILE_DELETED
  • CALLS_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-javascript installed
  • 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 loading
    • scan_js_file(path) β€” parses a .js file and returns list[Symbol]
    • scan_js_calls(source_code, symbols) β€” extracts call_expression nodes and returns list[DetectedChange] for CALL_DETECTED
    • _get_js_language() β€” lazy-loads tree-sitter-javascript with double-checked caching
  • SymbolIndex.js_symbols property β€” returns only JavaScript symbols (those without an ast module origin)
  • SymbolIndex.by_name(name) β€” language-agnostic lookup (finds symbols across both languages)
  • RepositoryScanner extended β€” discovers .js files, routes them to javascript_scanner, populates ScanResult.js_files
  • ChangeDetector.detect() extended β€” extracts JS call_expression nodes alongside Python ast.Call walking
  • TerminalReporter extended β€” shows Python vs JS file counts and language-specific symbol breakdowns in scan summaries
  • _strip_markdown_fence() in claims/parser.py β€” handles ```json...``` from model output

Changed

  • Bumped version from v0.2.0 β†’ v0.3.0
  • Updated pyproject.toml β€” added tree-sitter-javascript>=0.25 dependency
  • Updated .gitignore β€” added _binary_test.bin under 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.py and tests/test_picker_integration.py (unused imports, line lengths)
  • Fixed pre-existing type errors in nowreck/picker.py (missing questionary stub)
  • Fixed ordering-sensitive flaky test (test_empty_prompt_flow) in test_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 from CALLS_FUNCTION detection; only simple name() 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)

  1. Discover β€” recursively finds .py and .js files in both snapshots
  2. Parse β€” routes each file to the correct parser:
    • .py β†’ Python's built-in ast module
    • .js β†’ Tree-sitter with tree-sitter-javascript grammar
  3. Index β€” builds a unified SymbolIndex from 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 summary

Design 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-javascript with 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