Date: 2026-11-15
OS: Windows Server 2022 (x64). I used Git Bash for most steps and PowerShell for pktmon, links, the VHD and the desktop apps.

Source (GitLab, Apache-2.0): https://gitlab.com/jakubrojicek11/sentinel-ir
npm: https://www.npmjs.com/package/sentinel-ir
Playground (runs in your browser): https://sentinel-ir-playground-dc30c8.gitlab.io/

Node: 20.20.2 (npm 10.8.2), 22.23.5 (npm 10.9.9), 24.22.2 (npm 11.19.1)
Other tools: MCP client @modelcontextprotocol/sdk 1.32.2 (Client + StdioClientTransport), TypeScript 5.9.3 and 7.0.2, VS Code (portable zip, latest), Cursor (latest installer), Claude Desktop 0.14.10.
Source: only the published npm packages, installed or run through npx. No repo checkout, no code changes, no MR.
Not tested: macOS and Linux. This session ran only on the Windows machine.

Summary

There are no blockers. All 8 regression checks for 0.5.9 pass on Windows on Node 20, 22 and 24. Check 8 (README) has small gaps: the aliases are missing from one paragraph and not-a-folder is missing from the audit-log reasons. The MCP results were identical on all three Node versions. A diff of the normalized output for every call showed no differences.

Not verified: whether a desktop client actually starts the server from its config.

  • VS Code accepted the entry. Starting it from MCP: List Servers turned the status to Error with an empty server log, and no sentinel-ir-mcp process appeared. VS Code was unauthenticated and still in onboarding, so I can't blame the package (see U1).
  • Cursor and Claude Desktop stop at their login screens.
  • The same command line started fine through the real SDK client.

Issues found

# Severity Package Title
1 bug (docs/behaviour mismatch) sentinel-ir An explicit language: "ruby" returns language: "ruby" and reason unsupported language: ruby, but the README says "unknown" / unsupported file type: .rb
2 papercut sentinel-ir-mcp analyze_code rejects language: "JS" (-32602); the library accepts any case
3 papercut sentinel-ir-mcp doctor prints [ok] analyze_directory works ... (0 file(s) checked)
4 papercut sentinel-ir-mcp doctor stale-cache warning names the copy sentinel-ir-mcp@^0.5.8 under npm 10 but sentinel-ir-mcp@0.5 under npm 11
5 docs sentinel-ir-mcp The README upgrade text ("npx keeps starting the copy it downloaded first for a range such as @0.5") is true for npm 10 but not for npm 11.19.1
6 papercut sentinel-ir examples/basic.js (the README's CLI route) prints a raw Node stack trace for a missing file
7 docs sentinel-ir The "Fewer tokens" table is stale (raw sizes are 2-4x smaller than the shipped files)
8 docs sentinel-ir-mcp The "Supported" paragraph lists language values without the aliases, and the audit-log refusal reasons don't include not-a-folder
9 papercut sentinel-ir Rust: use std::process; process::exit(0) in a broken file is critical with child_process; a valid file with std::process::exit is low but forbidden: ["process.exit"]
10 papercut (transient) sentinel-ir-mcp doctor once printed [skip] No usable answer from the npm registry ... while the network was busy

Issues in detail

1. bug: explicit unsupported language doesn't match the README (sentinel-ir)

  • Node: 20/22/24 (all identical)
  • Repro:
 mkdir lib && cd lib && npm init -y && npm install sentinel-ir@0.5.9 node -e 'const s=require("sentinel-ir");(async()=>{await s.initializeWasm(); for (const o of [{language:"ruby"},{target:"x.rb"}]) { const r=s.compress(s.analyze("puts 1",o)); console.log(JSON.stringify(o), r.analysisStatus, r.language, r.facts.reason) }})()' 
  • Expected (README, "Supported languages"): "An explicit unsupported language (e.g. "ruby") or, since 0.5.8, a target with any other extension ... is reported as analysisStatus: "incomplete" with language: "unknown" and facts.reason: "unsupported file type: .rb (…)"."
  • Actual:
 {"language":"ruby"} incomplete ruby unsupported language: ruby (supported: javascript, typescript, tsx, python, rust) {"target":"x.rb"} incomplete unknown unsupported file type: .rb (supported: .js .mjs .cjs .jsx .ts .mts .cts .tsx .py .pyi .rs) 

The status is correctly incomplete, but language echoes the caller's string. Code that switches on language === "unknown" misses this case. Either always return "unknown" or change the README sentence.

2. papercut: MCP analyze_code language is case-sensitive, the library isn't

  • Node: 20/22/24, MCP client SDK 1.32.2, server npx -y sentinel-ir-mcp@stable-0.5 <folder> (reports 0.5.9)
  • Repro: call analyze_code with { "code": "eval(x)", "language": "JS" }.
  • Expected: the same result as js, because the library accepts JS and Python. The 0.5.9 changelog says "analyze_code takes the js, ts, py and rs aliases like sentinel-ir".
  • Actual:
 MCP error -32602: Input validation error: Invalid option: expected one of "javascript"|"typescript"|"tsx"|"python"|"rust"|"js"|"ts"|"py"|"rs" at language 
  • Note: the error is clear and lists the valid values, so this is minor. The lowercase aliases js/ts/py/rs all work.

3. papercut: doctor says [ok] when 0 files were checked

  • Node: 24.22.2
  • Repro: use a project folder where the first file the walk reaches is skipped. My fixture had a 1.6 MB big/huge.js, symlinks and a hard link before src/; a folder where exclusions leave nothing analyzable also works. Then:
 npx -y sentinel-ir-mcp@stable-0.5 setup --client cursor --yes --exclude nomatch-folder --exclude "*.rb" --audit-log npx -y sentinel-ir-mcp@stable-0.5 doctor --client cursor 
  • Expected: a [warn] or [skip], for example "nothing analyzable found in the first N files", or the check keeps walking until it analyzes one file.
  • Actual:
 [ok] analyze_directory works on C:\t59\Projekt Žluťoučký kůň\myapp (0 file(s) checked) 

The same folder without the Cursor config gives (1 file(s) checked). "Works" next to "0 files checked" reads as a contradiction.

4. papercut: the stale-npx-cache warning labels the copy differently on npm 10 and npm 11

  • Repro (npm 10, Node 20.20.2, npm_config_cache=C:\t59\c20):
 npx -y sentinel-ir-mcp@0.5.8 --version # puts 0.5.8 in the cache (key 46f9bf218df4163a) npx -y sentinel-ir-mcp@0.5 doctor # runs 0.5.8 (README behaviour) npx -y sentinel-ir-mcp@stable-0.5 doctor # runs 0.5.9 
  • Actual (npm 10): the warning names the cached copy sentinel-ir-mcp@^0.5.8. On npm 11.19.1 the same situation reads:
 [warn] The npx cache still holds sentinel-ir-mcp@0.5 (sentinel-ir-mcp 0.5.8, sentinel-ir 0.5.8) in C:\Users\Administrator\AppData\Local\npm-cache\_npx\46f9bf218df4163a; a client entry with that version or range keeps starting it, and sentinel-ir older than 0.5.9 misses security fixes -> Close the client, delete that folder (npm 11: npm cache npx rm 46f9bf218df4163a), then run: npx -y sentinel-ir-mcp@stable-0.5 setup 
  • Cause (from the cache files): npm 10's _npx/<hash>/package.json is only {"dependencies":{"sentinel-ir-mcp":"^0.5.8"}}. npm 11 adds "_npx":{"packages":["sentinel-ir-mcp@0.5"]}. doctor falls back to the dependency range, which the user never typed.
  • Expected: a label the user recognises on both npm versions. When _npx.packages is missing, "a cached copy of sentinel-ir-mcp 0.5.8" would be enough. Apart from the label, both warnings are correct and actionable, and the folder hash is right.

5. docs: the README upgrade claim depends on the npm version

  • Repro (Node 24.22.2 / npm 11.19.1): put 0.5.8 in the cache under both @0.5 and @stable-0.5, then run npx -y sentinel-ir-mcp@stable-0.5 doctor and then npx -y sentinel-ir-mcp@0.5 doctor.
  • Actual: the second command also ran 0.5.9. Its cache folder _npx/46f9bf218df4163a/package.json now reads {"dependencies":{"sentinel-ir-mcp":"^0.5.9"},"_npx":{"packages":["sentinel-ir-mcp@0.5"]}}. On npm 10.8.2 the @0.5 copy stayed on 0.5.8, as the README says.
  • Caveat: I made the 0.5.8 cache entry with --before. A cache from a real earlier download may behave differently.
  • Suggestion: say "npm 10 keeps starting ...; npm 11 may re-resolve". The advice to use @stable-0.5 is right either way.

6. papercut: examples/basic.js prints a raw stack trace for a missing file

  • Node: 24.22.2 (same on 20/22)
  • Repro: node node_modules/sentinel-ir/examples/basic.js does/not/exist.js. The README gives this as the command-line route, and the package has no bin.
  • Actual (exit 1):
 node:fs:483 return binding.readFileUtf8(path, stringToFlags(options.flag)); ^ Error: ENOENT: no such file or directory, open 'C:\t59\Projekt Žluťoučký kůň\lib20\does\not\exist.js' at Object.readFileSync (node:fs:483:20) at Object.<anonymous> (C:\t59\Projekt Žluťoučký kůň\lib20\node_modules\sentinel-ir\examples\basic.js:7:17) at Module._compile (node:internal/modules/cjs/loader:1929:14) at Object..js (node:internal/modules/cjs/loader:2060:10) at Module.load (node:internal/modules/cjs/loader:1651:32) at Module._load (node:internal/modules/cjs/loader:1443:12) at wrapModuleLoad (node:internal/modules/cjs/loader:261:19) at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5) at node:internal/main/run_main_module:33:47 { errno: -4058, code: 'ENOENT', syscall: 'open', path: 'C:\\t59\\Projekt Žluťoučký kůň\\lib20\\does\\not\\exist.js' } 
  • Expected: a one-line message such as basic.js: cannot read does/not/exist.js (ENOENT). Optionally, ship a small bin, because newcomers look for npx sentinel-ir <file>.

7. docs: the "Fewer tokens" table is stale (sentinel-ir README)

file README raw chars actual 0.5.9 raw chars
src/ast-facts.js 21,823 96,827
src/sentinel-ir.js 9,123 20,424
src/tree-parser.js 4,393 13,181

Measuring examples/basic.js on itself gives an IR of 552 characters; the README says 461. The claim still holds, and the real savings are larger, but the numbers are from an older release.

8. docs: gaps in the sentinel-ir-mcp README

  • The "Supported" paragraph says "an explicit language (javascript | typescript | tsx | python | rust)" without the aliases. The Tools table and the changelog do list js/ts/py/rs.
  • The audit-log section lists the refusal reasons excluded, outside, not-found, too-large, hardlink, symlink, unsupported, not-a-file, url, no-folders. analyze_directory on a file logs not a folder, so not-a-folder should be added.
  • The minimum sentinel-ir version is documented ("Needs sentinel-ir 0.5.9"), and doctor checks it. OK.

9. papercut: Rust std::process false positives (documented behaviour)

  • use std::process; fn main() { process::exit(0) (missing }) gives critical, forbidden: ["child_process","process_spawn"]. The README describes the module-only use std::process heuristic, but this is a very common idiom.
  • A valid fn main(){ std::process::exit(1); } gives risk low, but forbidden: ["process.exit"]. A reader who takes forbidden to mean "dangerous capability" may be confused.

10. papercut (transient): doctor update check

While a large download was running, one doctor run printed [skip] No usable answer from the npm registry about a newer sentinel-ir-mcp. The next run printed [ok] sentinel-ir-mcp 0.5.9 is the newest stable-0.5 on npm. The fallback is correct (skip, not fail). I'm noting it only for completeness.

Unverified / blocked

U1. Desktop clients starting the server from their config: not verified.

client config written by config accepted? server process started?
VS Code (portable zip, latest) code --add-mcp '<json printed by setup --client vscode>' -> User\mcp.json, non-ASCII path intact yes, listed in MCP: List Servers no: Start Server sets the status to Error; mcpServer.mcp.config.usrlocal.sentinel-ir.log is empty; no sentinel-ir-mcp process. VS Code was unauthenticated and in onboarding; its renderer log shows Could not list installed MCP servers. (refused: policy-context-unavailable)
Cursor (latest) setup --client cursor --yes -> <project>\.cursor\mcp.json (cmd /c npx -y sentinel-ir-mcp@stable-0.5 ... ${workspaceFolder}) file is valid; the app won't go past the login modal not observed
Claude Desktop 0.14.10 setup --client claude-desktop --yes -> %APPDATA%\Claude\claude_desktop_config.json file is valid; the app stops at "Get started" not observed
  • I couldn't tell whether the VS Code Error comes from the package or from the client state.
  • The same command line, run as cmd /c npx -y sentinel-ir-mcp@stable-0.5 "C:\t59\Projekt Žluťoučký kůň\myapp", starts and answers the handshake through the real SDK client. doctor also performs its own handshake.
  • Recommendation: repeat with a signed-in VS Code or Cursor.

U2. There is no second physical disk. I used a 512 MB NTFS VHD mounted as V:.

U3. macOS and Linux were not tested in this session (Windows machine only).

The 8 regression checks for 0.5.9

# Claim Result
1 UNC \\server\share\x.js, //server/share/x.js, file://server/share/x.js refused before any file system or network access PASS on Node 20, 22 and 24. pktmon filter: TCP 445/139, UDP 137/138, 5355, IP 10.255.255.1. During every UNC and remote-URL call: 0 packets on each Node version. Control Test-Path \\10.255.255.1\share\x.js: 28 packets (SYN to :445) after 22 s. Replies: Access denied ... outside the allowed folders (UNC) and path must be a file path, not a URL (remote file://)
2 file:src/x.js and http(s):// refused as URLs; local file:/// inside the folder still works PASS. path must be a file path, not a URL: file:src/server.js; file:///C:/t59/Projekt%20Žluťoučký%20kůň/myapp/src/server.js (encoded and raw) gives complete javascript; file:/// outside gives Access denied
3 Broken Python/Rust is critical only when forbidden names the capability PASS (library and MCP). subprocess, os.system, std::process::Command, runpy, spec_from_file_location(...).loader.exec_module, from ctypes import * give critical with forbidden. std::process::exit, std::process::id, importlib.reload, from ctypes import c_int are not critical. See issue 9 for the use std::process; idiom
4 analyze_directory summary lists forbidden for broken files with a dangerous capability PASS (broken Python, Rust and JS fixtures)
5 analyze_code aliases js, ts, py, rs PASS for lowercase. Uppercase is rejected, see issue 2
6 log wording PASS. pasted code (12 bytes, unknown): low (incomplete: unsupported language); REFUSED file http://example.com/x.js: not a local file path; ... outside the allowed folders; REFUSED file links/hard-out.js: file has several hard links; folder line 1 not read (too large or unreadable), 1 not read (several hard links); REFUSED folder src/server.js: not a folder; Not a file: src
7 doctor warns on unmatched exclusions; stale-cache warning reads right for a range and for a tag PASS with papercuts. setup and doctor both warn "matches nothing there yet (typo?)". The stale-cache warning is correct and actionable, but the label differs between npm 10 and npm 11 (issue 4)
8 README documents the language option and aliases, every refusal reason, and the minimum sentinel-ir version Mostly PASS. Aliases are in the Tools table and changelog but not in the "Supported" paragraph; not-a-folder is missing from the audit-log reasons (issue 8)

0.5.8 fixes rechecked

Check Result
Same Access denied for existing and missing outside paths, .., \\?\C:\... PASS
Symlink outside refused (incl. dangling symlink and junction); symlink inside works PASS
Unknown extension (.rb, .go) gives incomplete / unknown PASS (but see issue 1 for an explicit language)
doctor per client (--client cursor; Claude Desktop and Windsurf paths) PASS
`log \ head -1` exits 0 without EPIPE
TypeScript declarations under strict (tsc 5.9.3 and 7.0.2); the union on facts.ast narrows PASS
Rust and TSX parse-error positions (missing / unclosed with line and column) PASS
.jsx accepted PASS

Verified working

  • sentinel-ir install: npm install sentinel-ir in a fresh project on Node 20, 22 and 24 takes 2-3 s. npm 11 prints the documented allow-scripts notice.
  • README API example: output is byte-identical to the README. initializeWasm() works. Outputs are identical across Node versions.
  • Languages: JS, TS, TSX, Python and Rust files analysed. Broken files in each language give incomplete with parse-error positions. Aliases and case-insensitive names work in the library.
  • Module formats: ESM default import and CJS require both work.
  • sentinel-ir-mcp CLI entry points:
    • guide
    • setup for every client (dry run, --yes, --audit-log, --exclude); re-running keeps existing settings
    • whole-disk refusal: Refusing to give the agent access to a whole disk (V:\). Pass a project folder with --project.
    • doctor, doctor --client cursor
    • log, log --all, and guidance when the audit log is off
  • MCP over stdio with a real client (identical on Node 20, 22, 24):
    • handshake {"name":"sentinel-ir-mcp","version":"0.5.9"}
    • tools analyze_code, analyze_file, analyze_directory
    • refusals: outside root, .., symlink, hard link (has 2 hard links; files with more than one are refused), oversized file (File too large: big/huge.js is 1600000 bytes, limit is 1048576), unsupported extension, Not a file, Not a folder
    • directory summary with skipped.tooLarge, skipped.symlinks, skipped.hardlinks and riskCounts
  • Audit log: JSONL with a start record (version, folders, exclusions) and one line per call.
  • Paths: C:\t59\Projekt Žluťoučký kůň\myapp (spaces and diacritics) and V:\Další projekt on a second volume. setup and doctor are fine on both, and generated configs keep the non-ASCII characters intact.
  • Upgrade path: I read the actual _npx/<hash>/node_modules/sentinel-ir-mcp/package.json in each case.
    • npm 10: @0.5 keeps running 0.5.8, and 0.5.8's own doctor warns This is sentinel-ir-mcp 0.5.8, npm has 0.5.9 (stable-0.5).
    • @stable-0.5 runs 0.5.9, and its doctor names the stale folder with the delete command.
    • npm 11: see issue 5.

Independent Engineering Projects

Built as independent engineering experiments across AI, software analysis, security, and autonomous systems.

Evaluated by reproducible evidence, not by the number of layers.

Explore the projects:

Sentinel Ecosystem: AutoDoc-Sentinel, Sentinel-IR, Sentinel-IR-MCP and related tools.
Agentix Lite: Independent security and system-observation project.
Find the projects: GitLab repositories | NPM packages

Support the work: ⭐ Star the repositories on GitLab.

Connect: Follow me here on DEV Community or explore the Playground.

Each project is distributed under its own license. Check the corresponding repository or NPM package for licensing terms.

Copyright © 2026 jackymenCZ. All rights reserved. Written for the community.