The Compliance Scanner Now Reads TypeScript, JavaScript, Java and Kotlin, With the Same Verdict as Python

Until 1.17.0, air-blackbox comply read .py files and nothing else. A TypeScript team could install it, scan a repository full of AI calls, and get a report that said there was nothing to look at. This release fixes that, and this post is about the part that matters more than the language list: how we know the new languages get the same answers.

Key Takeaways

What changed

Most compliance checks are about naming and structure: is there a require_approval gate before the dangerous tool, is the model call wrapped in error handling, does the code log what the agent did. The practices are the same in every language; the spelling is not. A TypeScript developer writes requireApproval, wraps calls in try/catch and documents with JSDoc. A Kotlin developer writes runCatching and KDoc and validates with @field:NotBlank.

1.17.0 handles both halves. Names are matched across naming styles, so requireApproval counts as require_approval without anyone maintaining two lists. Idioms with no Python spelling get their own adapters: try/catch and .catch(), JSDoc, Javadoc and KDoc, TypeScript and JSDoc types, npm and JVM loggers (with console.log and println excluded, as print is), Zod, Ajv and Bean Validation, the Vercel AI SDK, LangChain4j, Spring AI, Koog, and prompt interpolation in template literals and string templates.

The scanner also knows what not to read: declaration files, minified bundles, tool configs, Gradle build scripts, node_modules and build output, and each ecosystem's test-file conventions, so a *.test.ts file does not count against your documentation coverage any more than test_*.py does.

Jupyter notebooks are read as Python: code cells in order, IPython magics commented out, non-Python kernels skipped. A notebook gets exactly the result its code gets in a .py file. If your AI work lives in notebooks, it was invisible to the scanner before; it is not now.

How "same verdict" was proven

A scanner that reads six languages is only useful if it reads them equally. A check that passes a Python repository and warns on the identical TypeScript repository is not a compliance tool, it is a source of arguments.

So the claim is tested the way a claim should be. The scanner's existing evaluation corpus holds 72 labelled Python programs: each one either has a practice or deliberately lacks it. We added 72 more for the checks the corpus did not cover (documentation, unsafe input, OAuth and delegation, the hiring checks, GDPR, the US state laws), then had all 144 translated into idiomatic TypeScript, JavaScript, Java and Kotlin by people who were not allowed to see the scanner. The translations use the real libraries a developer in each language would reach for, not Python spellings in another syntax.

Every one of the 144 gets the same status in every language it can be written in. Three cases cannot be written in Java or Kotlin at all, because those languages cannot express untyped parameters; the corpus says so rather than pretending. The parity run is part of the test suite, so a future change that makes one language drift fails the build. The numbers are in SCANNER_EVAL.md, and the corpus is in the repository for anyone who wants to argue with a specific case.

The corpus earned its keep before release. It caught that LangChain4j's model.chat() was not recognised as a model call, that the JavaScript rate-limiter and guardrail packages were unknown to the scanner, and that Google Cloud DLP was not counted as PII detection. Each is a gap a real team would have hit.

What existing Python users see

Nothing, by design, except two fixes. Python output was snapshotted on 76 targets before the work started and compared byte for byte at every step; the language work changed none of it. The two deliberate changes:

One engine everywhere

The web console and the MCP server each carried their own copy of the checks. Copies drift: the console had fewer checks and the hiring bug the CLI had already fixed, and both were Python-only, so the same code could get a different verdict from Claude than from the terminal. Both now call the SDK's scanner. Paste Kotlin into the console, or ask Claude to scan a TypeScript file through the MCP server with air-blackbox-mcp[full], and you get the verdict the CLI gives.

What it still does not do

Try it

pip install -U air-blackbox
air-blackbox comply --scan . -v

Point it at a TypeScript, Java or Kotlin repository and compare the report with what you expected. If a check disagrees with your reading of the code, the corpus format makes it easy to send a minimal case: the same program in two languages with different verdicts is a bug, and we will treat it as one.

Full notes are in the changelog.