DEV Community

jackymenCZ (jackymenCZ)
jackymenCZ (jackymenCZ)

Posted on

Sentinel-IR 0.1.1 Is on npm: Give AI Agents Structural Facts, Not the Entire Codebase

The 20-second version

AI agents do not always need an entire source file to reason about its structure.

Sentinel-IR turns JavaScript into a compact structural representation containing facts such as routes, imports, exports, environment variables, filesystem operations, network activity, and risk signals.

Version 0.1.1 makes the packaged WebAssembly parser available through an asynchronous API, exposes whether analysis completed successfully, and ships a deliberately limited set of package files.

One detail about how this release was built: I developed and tested it using Termux on an Android phone. No desktop workstation was required for my local development and testing workflow.

And yes, some of that work happened from the comfort of my bed. ๐Ÿ˜‚

I think there's something worth sharing here, especially with younger developers. In the evening, when you're already lying in bed and might otherwise spend another hour scrolling through TikTok or Instagram, you could also spend some of that time creating something of your own. You don't need a perfect setup to start experimenting, learning, or building a small tool that solves a real problem.

That doesn't mean every late-night idea becomes a useful product. Mine still had to survive tests, package checks, and a clean-install smoke test before I considered the release ready.

The important idea is not that a smaller prompt magically makes an AI safer. It is that an agent should be given useful evidence, and the system should be able to say when that evidence is incomplete.

This release is published. The limitations are published too.๐Ÿ˜

The important idea is not that a smaller prompt magically makes an AI safer. It is that an agent should be given useful evidence, and the system should be able to say when that evidence is incomplete.

What changed, why it matters, how the JavaScript-to-IR pipeline works, and what the release actually proves.

Release: ""sentinel-ir@0.1.1"" (https://www.npmjs.com/package/sentinel-ir/v/0.1.1)
Source: "GitLab repository" (https://gitlab.com/jakubrojicek11/sentinel-ir)
Release tag: ""v0.1.1"" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/tree/v0.1.1)
Reading time: Approximately 20 minutes, including code examples.

AI agents do not always need an entire source file to reason about its structure.

Sentinel-IR turns JavaScript into a compact structural representation containing facts such as routes, imports, exports, environment variables, filesystem operations, network activity, and risk signals.

Version 0.1.1 makes the packaged WebAssembly parser available through an asynchronous API, exposes whether analysis completed successfully, and ships a deliberately limited set of package files.

The important idea is not that a smaller prompt magically makes an AI safer. It is that an agent should be given useful evidence, and the system should be able to say when that evidence is incomplete.

This release is published. The limitations are published too.


Part I: The architectural idea

  1. The problem is not always a smarter model

There is a familiar approach to building AI coding agents.

Give the model a repository. Add instructions. Connect tools. Let it inspect files, decide what to change, run commands, and try again when something fails.

When the result is wrong, the instinct is often to improve the prompt, use a larger model, add more context, or give the agent another tool.

Sometimes that helps.

But there is another question worth asking:

What information does the agent actually need before making a decision?

Imagine asking somebody to inspect a building. You could hand them every document associated with the property, including old emails, maintenance notes, invoices, photographs, and unrelated correspondence.

Or you could first provide a structural report:

  • Which doors and access points exist?
  • Which systems connect to the outside?
  • Where are electrical and water systems located?
  • Which components present obvious risks?
  • Which parts of the report could not be inspected?

The report is not the building. It does not tell you everything happening inside it. It gives you a map that helps determine what deserves closer inspection.

Code analysis can benefit from the same distinction.

An AI agent often needs to know what a file imports, which endpoints it registers, whether it reads environment variables, whether it writes files, and whether it uses dangerous dynamic execution.

It does not necessarily need every comment, every string body, and every line of function implementation in the first reasoning step.

That is the problem Sentinel-IR explores.

  1. What exactly is Sentinel-IR?

Sentinel-IR is a JavaScript library that converts source code into a compact structural intermediate representation, or IR.

Its processing pipeline is:

"JavaScript source โ†’ tree-sitter AST โ†’ structural facts โ†’ IR โ†’ compressed IR"

The resulting information can include:

  • Routes and exports.
  • Imported modules.
  • Environment variable references.
  • Recognized network, filesystem, process, and cryptographic operations.
  • Dangerous syntax and associated risk signals.
  • A summary of the source file.
  • An explicit status indicating whether analysis completed.

The library uses tree-sitter to parse JavaScript and extract structural information from the resulting syntax tree.

That distinction matters.

A source-code scanner that simply searches for words can confuse a function named "evaluate" with the JavaScript "eval" function. It can find the word "fetch" inside a comment and conclude that the file uses networking.

An AST-based analyzer has a more useful starting point: it can examine the syntactic structure and determine which patterns are actually present.

This does not make it omniscient. It makes the evidence more structured.

Sentinel-IR currently supports JavaScript, including CommonJS and ESM. It does not claim general support for TypeScript, JSX-heavy projects, or arbitrary programming languages.

It also analyzes one file at a time. It does not reconstruct a complete application's behavior across every module.

Those boundaries are part of the design, not fine print to hide after installation.

  1. Why give an agent a structural representation?

There are three main reasons.

Less irrelevant context

Consider a source file containing thousands of characters of implementation details.

If the task is to understand its routes, imported modules, environment variables, and risky operations, sending the entire file may be unnecessary for the first step.

Sentinel-IR can provide those structural facts in a smaller representation.

That can reduce the amount of source material placed into a reasoning prompt.

There is a limit, though. A tiny file can be almost as short as its IR. In that case, generating an intermediate representation might not save anything.

Compression is useful when the representation removes information that is irrelevant to the current task. It is not automatically useful just because the output looks more technical.

Less untrusted text in the initial prompt

Code contains comments, string literals, documentation, and other text. Some of that text may contain instructions written by someone other than the agent's operator.

If an agent blindly places the entire source file into an instruction-heavy prompt, it introduces more untrusted text into that context.

A structural representation that omits function bodies, comments, and most string content can reduce that exposure.

But it does not eliminate prompt injection.

Identifiers, route paths, import names, environment variable names, and other selected literals can still originate from untrusted code. Furthermore, the IR itself does not enforce what a downstream model may do with its conclusions.

It is a reduction in exposed text, not a complete security boundary.

A production agent still needs explicit instruction boundaries, tool permissions, validation, and controls around proposed actions.

More explicit evidence

A useful structural report can associate a finding with the syntax that produced it and the line where it appeared.

For example:

"dynamic_code:eval@2"

That is more actionable than a generic statement that a source file looks dangerous.

It gives the next stage something to inspect and makes it easier to challenge the finding.

The broader principle is simple: a model should receive evidence that can be checked, rather than relying entirely on a narrative summary.

  1. The most important refinement: incomplete means incomplete

The part of this release I care about most is not the presence of a WebAssembly binary.

It is the explicit "analysisStatus" field.

A static analyzer has an uncomfortable failure mode. Parsing can fail, unsupported syntax can appear, a parser may not initialize, or the analysis may fall back to weaker text-based heuristics.

Imagine the following output:

{
"risk": "low"
}

What does "low" mean?

Perhaps the code was parsed successfully, the analyzer examined the supported structures, and no recognized higher-risk patterns were found.

Or perhaps the parser was unavailable, leaving the analyzer with only a limited fallback.

Those are not equivalent situations.

Version 0.1.1 exposes two states:

  • "complete": AST extraction succeeded and the source was parsed without syntax errors.
  • "incomplete": AST extraction was unavailable or the source could not be parsed cleanly.

This is not a claim that complete analysis discovers every security issue. It means the consumer has information about whether the supported structural analysis completed.

When the status is incomplete, a downstream agent should not silently treat a low risk estimate as proof that the source is safe.

It should be able to abstain, retry with an appropriate parser, request a stronger review, or require human approval.

This is an architectural choice: an analyzer should communicate the quality of its evidence instead of making the consumer guess.

I would rather have a system that says, "I could not establish this reliably," than one that produces a reassuring number and hides the reason it may be wrong.

  1. What changed in 0.1.1?

The release concentrates on making the analysis usable as a package, particularly where native dependencies are unavailable.

The significant changes are:

A bundled WebAssembly parser. The JavaScript grammar is shipped with the package so the library can initialize the WebAssembly parser without requiring the consumer to supply a separate grammar file.

An asynchronous entry point. "analyzeAsync()" initializes the WebAssembly parser and then performs analysis.

Explicit parser initialization. "initializeWasm()" allows the caller to initialize the parser separately when needed.

Shared initialization. Concurrent requests can share the same in-progress WASM initialization rather than independently initializing duplicate parsers.

A more explicit analysis result. "analysisStatus" is exposed in both the full and compressed representations.

A controlled package surface. The package explicitly lists the files intended for distribution, including source modules, the example, the WebAssembly grammar, documentation, and license notices.

These changes do not turn Sentinel-IR into an autonomous security product. They make its existing role as a structural analysis component more portable and its analysis state easier for other systems to interpret.

That is a smaller claim, and a more useful one.

  1. What this release does not promise

Let us draw the boundary before moving into the implementation.

Sentinel-IR is not a full semantic analyzer.

It does not resolve every alias, understand every value flowing between modules, or reconstruct the behavior of an entire application.

It does not prove that a file is safe.

It does not guarantee that a model receiving its output will make a correct decision.

It does not eliminate prompt injection.

It does not automatically send code to an LLM or decide which model to use. The application integrating the library controls what it does with the result.

Those limitations do not make the project pointless. They define where the library belongs in a larger system.

A structural analyzer can be useful without pretending to be the last word in code security.

The goal is to provide a better input for reasoning and a clearer signal about when the input is incomplete.

Now we can look at how the package actually works.


Part II: For developers

  1. Install and run it

Sentinel-IR 0.1.1 is available through the public npm registry and requires Node.js 20 or newer.

Install it:

npm install sentinel-ir

The package exposes these main functions:

const {
analyze,
analyzeAsync,
compress,
compressFacts,
expandDecision,
initializeWasm,
DEFAULTS
} = require("sentinel-ir");

Use the asynchronous API when you want the bundled WebAssembly parser:

const { analyzeAsync, compress } = require("sentinel-ir");

const code = `
const express = require("express");
const fs = require("fs");

const app = express();

app.post("/upload", (req, res) => {
fs.writeFileSync("/tmp/upload", req.body);
res.end();
});

app.listen(process.env.PORT);
`;

async function main() {
const ir = await analyzeAsync(code, {
target: "src/server.js"
});

console.log(JSON.stringify(compress(ir), null, 2));
}

main().catch(error => {
console.error(error);
process.exitCode = 1;
});

The code above is deliberately small. It demonstrates the interface without introducing a framework, model provider, external service, or agent runtime.

The library analyzes the supplied string and returns an object. The caller decides whether to store that result, display it, send the compressed representation to a model, or use it to inform another stage.

  1. Understanding the output

A shortened example of the compressed output looks like this:

{
"analysisStatus": "complete",
"mission": {
"target": "src/server.js",
"objective": "filesystem_simplification"
},
"world": {
"pressure": 0.5,
"confidence": 0.75,
"budget": 0.015,
"risk": "medium"
},
"constraints": [
"preserve_api",
"avoid_breaking_changes"
],
"forbidden": [],
"summary": {
"size": 205,
"lines": 7,
"hasCrypto": false,
"hasFilesystem": true,
"hasNetwork": true
},
"facts": {
"ast": true,
"routes": ["POST /upload"],
"imports": ["express", "fs"],
"env": ["PORT"],
"operations": ["inboundHttp", "diskWrite"]
}
}

This illustrates the shape and purpose of the IR. Exact sizes, risk estimates, and line numbers depend on the actual input.

The "facts" object carries the compact structural findings.

The "summary" gives a quick overview.

The "mission" and "world" objects contain the library's task-oriented metadata and risk estimate. Values such as "pressure", "confidence", and "budget" are supplied through options or defaults. They are not calibrated probability measurements or an independently validated prediction of model behavior.

That distinction is important when integrating the library into a decision system.

A value named "confidence" is not automatically a statistically calibrated confidence score. An estimate named "risk" is not a guarantee.

Treat these fields according to their actual implementation and the evidence behind them.

  1. The parser boundary

The release supports two parser paths: optional native tree-sitter packages and a bundled WebAssembly path.

The native dependencies are optional:

{
"dependencies": {
"web-tree-sitter": "^0.25.10"
},
"optionalDependencies": {
"tree-sitter": "^0.21.1",
"tree-sitter-javascript": "^0.21.4"
}
}

The WebAssembly grammar is included in the published package.

The choice of API matters.

Synchronous analysis

"analyze(content, options)" performs synchronous analysis. If a parser has not been initialized, it attempts to initialize the native parser.

It does not automatically switch to WebAssembly when native initialization fails.

If structural extraction is unavailable, the resulting analysis is marked incomplete and may use text-based fallback heuristics.

Asynchronous analysis

"analyzeAsync(content, options)" initializes the bundled WebAssembly parser before analyzing the source.

For platforms where the native parser is unavailable, that is the intended one-step workflow.

You can initialize the parser separately:

const { initializeWasm } = require("sentinel-ir");

initializeWasm().catch(error => {
console.error(error);
process.exitCode = 1;
});

Once initialized, the parser instance can be reused.

The implementation stores the in-progress initialization promise. If several callers arrive while initialization is underway, they can await that shared promise instead of initiating redundant setup.

If initialization fails, the cached promise is cleared so that a later attempt is not permanently tied to the failed initialization.

This is a small implementation detail with an important practical consequence: asynchronous initialization should be handled as a shared resource, not as work that every concurrent request independently repeats.

It is not a benchmark claim. The implementation and the concurrent-call checks are the evidence for the behavior being described.

  1. Parsing large input

The parser implementation also distinguishes between smaller source strings and larger inputs.

For inputs up to 32 KiB, it passes the source directly to tree-sitter. For larger inputs, it supplies chunks of up to 8 KiB through the parser's input callback.

Why do that?

Some parsing interfaces support reading the input incrementally. A caller does not have to create one additional full-size buffer solely to satisfy the parser interface.

This does not mean that the entire analysis uses constant memory. The source still exists in memory, AST nodes and extracted facts still consume resources, and the parser itself has its own costs.

It is a specific input-handling decision, not a promise that large files become cheap to analyze.

The implementation also normalizes named-node metadata across parser backends, rather than assuming every backend exposes the same property name.

These are examples of the sort of portability work that rarely makes an exciting headline but makes a library less fragile at the boundaries between dependencies and platforms.

  1. Why "analysisStatus" belongs in the public API

The status is additive: existing fields remain, but the full and compressed results now expose "analysisStatus".

Conceptually:

const ir = await analyzeAsync(source, options);

if (ir.analysisStatus !== "complete") {
// Do not treat this as a complete structural review.
// Retry, escalate, or abstain according to your application's policy.
}

The point of the example is not to prescribe a universal error policy.

A local developer tool may display a warning and continue. A CI security gate may reject an incomplete result. An autonomous agent with write access may need to stop before proposing or executing a change.

The correct behavior depends on the consequences of being wrong.

For a system that uses static analysis to influence automated decisions, silently treating an incomplete result as a complete one is a poor default.

A reliable consumer should make the distinction explicit in its own control flow.

One compatibility note: consumers that validate exact output object keys may need to account for the additive "analysisStatus" field.

That is worth checking if you already consume an earlier version of the package.

  1. What the fallback does, and why it is labeled

Text-based fallbacks are useful because they can provide limited information even when parsing is unavailable.

They are also less reliable than structural analysis.

For example, simple substring checks can confuse a mention of a function with a real call, or miss a behavior expressed through an unexpected pattern.

Sentinel-IR therefore exposes whether structural facts were extracted successfully.

When the AST is unavailable, compressed facts return a structure similar to:

{
"ast": false,
"reason": "source did not parse; IR fell back to text heuristics"
}

The precise reason depends on the failure.

That flag changes how a consumer should interpret the output. A missing route in successfully parsed source is different from a missing route in source that could not be structurally analyzed.

Even the complete state has a bounded meaning: it refers to successful parsing and extraction for the supported patterns in the supplied file. It does not mean semantic understanding of every possible behavior.

For example, a helper imported from another module could perform filesystem operations that are invisible to a single-file analyzer unless the supported structural rules identify them directly.

The status communicates the parsing boundary. It does not erase the boundary.

  1. What was actually checked for 0.1.1?

A release article should distinguish between code that exists, tests that pass, and claims that have not been established.

For this release, the recorded validation included the following.

Unit tests

The local test suite passed:

36 tests passed, zero failures.

The suite covers the implemented analysis behavior and the changes around asynchronous analysis and status reporting.

Passing tests establish that the tested cases behave as expected. They do not establish exhaustive correctness across all JavaScript syntax, all package consumers, or all runtime environments.

WebAssembly initialization

Ad hoc concurrency checks exercised multiple simultaneous calls to "initializeWasm()" and "analyzeAsync()".

The checks returned successfully, including a case where invalid source was followed by valid source.

These were release-validation checks, not a claim that every possible race condition has been proven impossible.

Invalid and minimal input

Checks included empty input, a minimal statement, and invalid source.

The important result is that invalid input must not silently become evidence of a complete structural analysis.

Package contents

The package uses an explicit allowlist rather than publishing the entire development workspace.

The intended published files are:

  • The main library module.
  • The AST fact extractor.
  • The tree-parser adapter.
  • The basic example.
  • The bundled JavaScript WebAssembly grammar.
  • The README.
  • The license and notice files.

The generated package contained nine files.

Development tests, backups, temporary package-smoke directories, and local verification artifacts were not part of the intended npm distribution.

Clean installation smoke test

The generated tarball was installed into a separate temporary project.

The smoke test checked that the public exports were available, the bundled WASM file existed, the asynchronous analysis path reported a complete result for valid source, the example worked, and the expected "POST /upload" route could be extracted.

That validates a useful release boundary: the published package is not dependent on the developer's working directory accidentally supplying a file that was omitted from the tarball.

Publication

The Git tag "v0.1.1" points to commit "23aed2b".

The tagged GitLab pipeline ran the publication process, and the npm registry now returns:

sentinel-ir@0.1.1
https://registry.npmjs.org/sentinel-ir/-/sentinel-ir-0.1.1.tgz

The registry's "latest" tag points to "0.1.1".

That is the evidence that publication succeeded. A green local test suite alone would not prove it.

The release pipeline can be inspected here:

"Sentinel-IR release pipeline" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/pipelines/2960529443)

  1. Reproduce the basic checks

A developer starting from the tagged source can run:

git clone https://gitlab.com/jakubrojicek11/sentinel-ir.git
cd sentinel-ir

git checkout v0.1.1
npm ci
npm test
npm run example

To inspect the actual published package rather than the Git working tree:

npm pack sentinel-ir@0.1.1

To inspect the resulting archive:

tar -tzf sentinel-ir-0.1.1.tgz

These commands test different things.

"npm test" checks the source test suite.

"npm pack" retrieves and packages the registry release.

Listing the archive shows what consumers actually receive.

A reproducible release process should not assume these are interchangeable checks.

  1. What remains out of scope?

There are several boundaries that should remain visible for anyone considering this library for an AI agent.

Language coverage: JavaScript only. TypeScript, JSX-heavy code, and other languages are not supported claims.

Single-file analysis: facts from different files are not combined into a whole-program model.

Semantic resolution: aliases, computed properties, and behavior passed through other modules may not be resolved.

Security completeness: recognized patterns are not the same as all possible vulnerabilities.

Prompt injection: removing comments and most string bodies from the representation reduces the amount of untrusted text exposed, but identifiers and selected literals can remain. The IR does not eliminate prompt injection.

Model correctness: a compact representation can be misunderstood. A downstream model's answer still requires validation.

Automated action: the library does not itself provide a complete policy engine or sandbox for an autonomous coding agent.

These aren't reasons to stop building. They are reasons to keep the responsibility of each component clear.

A parser can establish syntax. A structural extractor can report supported facts. A policy layer can decide how to treat an incomplete result. A model can suggest a change. A separate validation process can determine whether that change is acceptable.

Combining those responsibilities into one mysterious confidence score makes failures harder to diagnose.

Keeping them distinct gives the larger system a chance to fail visibly.

  1. Why I wanted this release to be small

It is tempting to turn every engineering improvement into another layer, another service, another autonomous process, or another large refactor.

That is not what this release needed.

The objective was to package the existing structural analysis function, provide a usable WASM path, make incomplete analysis visible, test the published package boundary, and release a specific version.

That is enough for 0.1.1.

It does not establish that the library will reduce production token costs by a particular percentage for every project. It does not demonstrate that a coding agent becomes secure merely by using structural IR. It does not validate every possible JavaScript program.

Those are different questions and require different experiments.

For example, a meaningful token-savings study would compare equivalent tasks using raw source and compressed IR, record the model and tokenizer, measure both input and output costs, and check whether the compressed path preserves enough information to achieve the same objective.

A meaningful safety evaluation would need adversarial source files, known expected findings, missed detections, false positives, and downstream behavior. It would also need to distinguish a parser's accuracy from the agent's response to its output.

Those measurements can be done later, with explicit test conditions.

For the current release, the claim is narrower: the npm package is published, its intended parser path is available, its output exposes analysis status, and the release checks passed.

That is a foundation, not a final verdict on every system that might use it.

Conclusion: make uncertainty part of the interface

The most useful question in an AI architecture is not always, "How much more can the model do?"

Sometimes it is, "What can the model actually know from the evidence we gave it, and how do we know when that evidence is incomplete?"

Sentinel-IR 0.1.1 is a small attempt to make that question concrete.

It turns JavaScript into structural facts. It provides a compact representation for downstream reasoning. It offers a portable asynchronous parser path. It reports whether analysis completed. And it keeps its documented limitations visible.

The npm package is available now:

""npm install sentinel-ir"" (https://www.npmjs.com/package/sentinel-ir)

Source and release details:

"GitLab repository" (https://gitlab.com/jakubrojicek11/sentinel-ir) ยท "v0.1.1 tag" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/tree/v0.1.1)

I would be interested in feedback from developers who work on coding agents, static analysis, prompt construction, or tools that need to distinguish a confident-looking result from a complete one.

In particular: where would an explicit incomplete-analysis state fit into your pipeline, and what evidence would you require before letting an agent act on the result?

I'd rather test those questions against real integration problems than claim the architecture has already solved them.๐Ÿค”

Top comments (0)