sentinel-ir-mcp 0.4.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  MCP server for [`sentinel-ir`](https://www.npmjs.com/package/sentinel-ir):
4
4
  it lets AI agents in Claude Desktop, Cursor, Windsurf or any other
5
5
  [Model Context Protocol](https://modelcontextprotocol.io) client read a
6
- compact **structural IR** of JavaScript, TypeScript and Python files instead of the raw source.
6
+ compact **structural IR** of JavaScript, TypeScript, Python and Rust files instead of the raw source.
7
7
 
8
8
  - npm: <https://www.npmjs.com/package/sentinel-ir-mcp>
9
9
  - GitLab (main repository): <https://gitlab.com/jakubrojicek11/sentinel-ir-mcp>
@@ -18,15 +18,28 @@ hundred characters. The agent can triage a folder first and then read the raw
18
18
  source only of the files that matter.
19
19
 
20
20
  **Supported:** JavaScript (`.js`, `.mjs`, `.cjs`), TypeScript (`.ts`, `.mts`,
21
- `.cts`, `.tsx`; since 0.2.0) and Python (`.py`, `.pyi`; since 0.3.0). The
21
+ `.cts`, `.tsx`; since 0.2.0), Python (`.py`, `.pyi`; since 0.3.0) and Rust
22
+ (`.rs`; since 0.5.0). The
22
23
  language is picked from the file extension; `analyze_code` also takes `target`
23
- or an explicit `language` (`javascript` | `typescript` | `tsx` | `python`). Every result carries `language` and
24
+ or an explicit `language` (`javascript` | `typescript` | `tsx` | `python` | `rust`). Every result carries `language` and
24
25
  `analysisStatus`; a file that does not parse cleanly (including JSX in a `.ts`
25
26
  file) is `"incomplete"`, and a low risk on an incomplete result is not evidence
26
27
  of safety. Other languages are not supported yet. What is detected in Python
27
- (and what is not) is listed in the
28
+ and Rust (and what is not) is listed in the
28
29
  [sentinel-ir README](https://gitlab.com/jakubrojicek11/sentinel-ir#python).
29
30
 
31
+ **0.5.0** reads Rust (`.rs`) with `sentinel-ir` 0.5.2: `unsafe` and FFI,
32
+ `std::process::Command`, file deletes and writes, HTTP clients and routes
33
+ (`axum`, `actix-web`, ...). A `build.rs` or procedural macro that touches
34
+ files, the network or secret environment variables is at least `high`,
35
+ because it runs on your machine during `cargo build`.
36
+
37
+ - `analyze_directory` skips Cargo's build folder: a `target` folder with
38
+ Cargo's `CACHEDIR.TAG` file, listed in `skipped.cargoTarget`. A plain folder
39
+ named `target` (e.g. in a JS project) is still scanned.
40
+ - `setup` writes `sentinel-ir-mcp@0.5`; `doctor` asks entries pinned to 0.4
41
+ or older to run `setup` again.
42
+
30
43
  **0.4.6** is listed in the official
31
44
  [MCP Registry](https://registry.modelcontextprotocol.io) as
32
45
  `io.github.jakcmencz/sentinel-ir-mcp`, so clients that browse the registry
@@ -41,7 +54,7 @@ can check later which version of a file the agent was shown (see
41
54
  - **One question in `setup`:** *"Keep a log of what the agent asks for (file
42
55
  names and results, never your code)? [y/N]"*. Or `setup --audit-log`;
43
56
  `--no-audit-log` turns it off. Running `setup` again keeps the setting.
44
- - **One command to read it:** `npx -y sentinel-ir-mcp@0.4 log` prints the last
57
+ - **One command to read it:** `npx -y sentinel-ir-mcp@0.5 log` prints the last
45
58
  50 entries as plain lines, `--all` everything, `--json` for scripts:
46
59
 
47
60
  ```text
@@ -170,9 +183,9 @@ nothing to compile (the parser is WebAssembly).
170
183
  Run it in your project folder:
171
184
 
172
185
  ```bash
173
- npx -y sentinel-ir-mcp@0.4 setup --client cursor # or windsurf, claude-desktop, vscode, claude-code
174
- npx -y sentinel-ir-mcp@0.4 doctor --client cursor # after restarting the client
175
- npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS.md / client rules
186
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor # or windsurf, claude-desktop, vscode, claude-code
187
+ npx -y sentinel-ir-mcp@0.5 doctor --client cursor # after restarting the client
188
+ npx -y sentinel-ir-mcp@0.5 guide # optional: text for AGENTS.md / client rules
176
189
  ```
177
190
 
178
191
  `setup` (without `--client` it asks which client):
@@ -186,7 +199,7 @@ npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS
186
199
  touched;
187
200
  - gives the agent one folder (`--project`, default: the current folder) and
188
201
  refuses your home folder or a whole disk;
189
- - writes `sentinel-ir-mcp@0.4`, so clients get 0.4.x fixes but no unexpected
202
+ - writes `sentinel-ir-mcp@0.5`, so clients get 0.5.x fixes but no unexpected
190
203
  new features;
191
204
  - asks which files or folders the agent must not read (`--exclude <name>`,
192
205
  repeatable, skips the question; see [Excluding files](#excluding-files));
@@ -201,7 +214,7 @@ npx -y sentinel-ir-mcp@0.4 guide # optional: text for AGENTS
201
214
  | `vscode`, `claude-code` | nothing; prints the command to run |
202
215
 
203
216
  On Windows the entry is `"command": "cmd", "args": ["/c", "npx", "-y",
204
- "sentinel-ir-mcp@0.4", "<folder>"]`: there `npx` is `npx.cmd`, and starting it
217
+ "sentinel-ir-mcp@0.5", "<folder>"]`: there `npx` is `npx.cmd`, and starting it
205
218
  without a shell failed with `ENOENT` on a real Windows runner. For VS Code and
206
219
  Claude Code, `setup` prints a PowerShell and a Command Prompt version of the
207
220
  command. What was measured on which system: [COMPATIBILITY.md](COMPATIBILITY.md).
@@ -281,7 +294,7 @@ Settings → Developer → Edit Config opens `claude_desktop_config.json`
281
294
  "mcpServers": {
282
295
  "sentinel-ir": {
283
296
  "command": "npx",
284
- "args": ["-y", "sentinel-ir-mcp@0.4", "/Users/you/projects/my-app"]
297
+ "args": ["-y", "sentinel-ir-mcp@0.5", "/Users/you/projects/my-app"]
285
298
  }
286
299
  }
287
300
  }
@@ -300,7 +313,7 @@ project entry has to be switched on once in Cursor Settings → Customize → MC
300
313
  "mcpServers": {
301
314
  "sentinel-ir": {
302
315
  "command": "npx",
303
- "args": ["-y", "sentinel-ir-mcp@0.4", "${workspaceFolder}"]
316
+ "args": ["-y", "sentinel-ir-mcp@0.5", "${workspaceFolder}"]
304
317
  }
305
318
  }
306
319
  }
@@ -316,8 +329,8 @@ with your project's absolute path.
316
329
  #### Other clients
317
330
 
318
331
  Any MCP client that starts stdio servers works with
319
- `command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.4", "<folder>"]`
320
- (on Windows: `command: "cmd"`, `args: ["/c", "npx", "-y", "sentinel-ir-mcp@0.4", "<folder>"]`).
332
+ `command: "npx"`, `args: ["-y", "sentinel-ir-mcp@0.5", "<folder>"]`
333
+ (on Windows: `command: "cmd"`, `args: ["/c", "npx", "-y", "sentinel-ir-mcp@0.5", "<folder>"]`).
321
334
  Without folder arguments the server uses the client's
322
335
  [MCP roots](https://modelcontextprotocol.io/specification/latest/client/roots)
323
336
  if the client offers them; if it offers none, only `analyze_code` works.
@@ -325,7 +338,7 @@ if the client offers them; if it offers none, only `analyze_code` works.
325
338
  ### Excluding files
326
339
 
327
340
  ```bash
328
- npx -y sentinel-ir-mcp@0.4 setup --client cursor --exclude secrets --exclude "*.min.js"
341
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor --exclude secrets --exclude "*.min.js"
329
342
  ```
330
343
 
331
344
  | you write | excludes |
@@ -350,8 +363,8 @@ other tools of its client is told about them, but this server cannot stop it.
350
363
  ### Audit log
351
364
 
352
365
  ```bash
353
- npx -y sentinel-ir-mcp@0.4 setup --client cursor --audit-log # or answer "y" in setup
354
- npx -y sentinel-ir-mcp@0.4 log # what the agent asked for
366
+ npx -y sentinel-ir-mcp@0.5 setup --client cursor --audit-log # or answer "y" in setup
367
+ npx -y sentinel-ir-mcp@0.5 log # what the agent asked for
355
368
  ```
356
369
 
357
370
  | system | folder |
@@ -508,7 +521,10 @@ A second CI job runs on a real Windows runner (Node 20/22/24): the full test
508
521
  suite, the packed package with `setup`/`doctor` against the real `%APPDATA%`,
509
522
  starting the published package through `npx`, and the printed VS Code/Claude
510
523
  Code commands pasted into PowerShell 5.1, PowerShell 7 and cmd.exe. See
511
- [COMPATIBILITY.md](COMPATIBILITY.md).
524
+ [COMPATIBILITY.md](COMPATIBILITY.md). On every push it runs on GitHub Actions
525
+ in the mirror (`.github/workflows/windows.yml`); on GitLab it runs on release
526
+ tags before `publish`, and by hand on other branches. The informational
527
+ large-file size probe runs on release tags only.
512
528
 
513
529
  Release: bump `version` in `package.json` and both `version` fields in
514
530
  `server.json` (a test checks they match), push a protected tag `vX.Y.Z`;
@@ -12,7 +12,7 @@ const { normalizePattern, ExcludeError, MAX_PATTERNS } = require("../src/exclude
12
12
  const { AuditLogError, defaultAuditDir, normalizeAuditDir, assertOutsideProjects } = require("../src/auditLog");
13
13
  const { version } = require("../package.json");
14
14
 
15
- const USAGE = `sentinel-ir-mcp ${version} — MCP server for sentinel-ir (JavaScript, TypeScript and Python)
15
+ const USAGE = `sentinel-ir-mcp ${version} — MCP server for sentinel-ir (JavaScript, TypeScript, Python and Rust)
16
16
 
17
17
  Usage: sentinel-ir-mcp [options] [folder ...]
18
18
  sentinel-ir-mcp setup | doctor | guide | log [--help]
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "sentinel-ir-mcp",
3
- "version": "0.4.6",
3
+ "version": "0.5.0",
4
4
  "mcpName": "io.github.jakcmencz/sentinel-ir-mcp",
5
- "description": "MCP server for sentinel-ir: lets AI agents (Claude Desktop, Cursor, Windsurf, ...) read a compact structural IR of JavaScript, TypeScript and Python files instead of the raw source. Read-only, sandboxed to allowed folders.",
5
+ "description": "MCP server for sentinel-ir: lets AI agents (Claude Desktop, Cursor, Windsurf, ...) read a compact structural IR of JavaScript, TypeScript, Python and Rust files instead of the raw source. Read-only, sandboxed to allowed folders.",
6
6
  "main": "src/server.js",
7
7
  "bin": {
8
8
  "sentinel-ir-mcp": "bin/sentinel-ir-mcp.js"
@@ -20,11 +20,12 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@modelcontextprotocol/sdk": "^1.32.1",
23
- "sentinel-ir": "^0.4.5",
23
+ "sentinel-ir": "^0.5.2",
24
24
  "zod": "^4.1.0"
25
25
  },
26
26
  "keywords": [
27
27
  "python",
28
+ "rust",
28
29
  "mcp",
29
30
  "model-context-protocol",
30
31
  "mcp-server",
package/src/analyze.js CHANGED
@@ -87,6 +87,25 @@ function realNative(p) {
87
87
  }
88
88
 
89
89
  const SKIP_DIRS = new Set(["node_modules", ".git", "__pycache__", "venv", "site-packages"]);
90
+ // Cargo marks its build folder with a CACHEDIR.TAG (cache directory tagging
91
+ // spec); a plain folder named "target" in a JS project is still scanned.
92
+ const CACHEDIR_SIGNATURE = "Signature: 8a477f597d28d172789f06886806bc55";
93
+ async function isCargoTarget(dir) {
94
+ let handle;
95
+ try {
96
+ const tag = path.join(dir, "CACHEDIR.TAG");
97
+ if (!(await fs.lstat(tag)).isFile()) return false;
98
+ handle = await fs.open(tag, OPEN_FLAGS);
99
+ const buffer = Buffer.alloc(CACHEDIR_SIGNATURE.length);
100
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
101
+ return buffer.toString("latin1", 0, bytesRead) === CACHEDIR_SIGNATURE;
102
+ } catch {
103
+ return false;
104
+ } finally {
105
+ await handle?.close();
106
+ }
107
+ }
108
+
90
109
  const RISK_ORDER = { critical: 0, high: 1, medium: 2, low: 3 };
91
110
 
92
111
  async function analyzeCode(code, target, language) {
@@ -102,7 +121,7 @@ async function analyzeFile(requested, roots, { maxFileBytes, excluder = NONE, ha
102
121
  const stat = await fs.stat(real);
103
122
  if (!stat.isFile()) throw new AccessError(`Not a file: ${requested}`);
104
123
  if (!isSourceFile(real)) {
105
- throw new AccessError(`Unsupported file type: ${requested} (only .js, .mjs, .cjs, .ts, .mts, .cts, .tsx, .py and .pyi)`);
124
+ throw new AccessError(`Unsupported file type: ${requested} (only .js, .mjs, .cjs, .ts, .mts, .cts, .tsx, .py, .pyi and .rs)`);
106
125
  }
107
126
  if (stat.size > maxFileBytes) {
108
127
  throw new AccessError(`File too large: ${requested} is ${stat.size} bytes, limit is ${maxFileBytes}`);
@@ -112,11 +131,12 @@ async function analyzeFile(requested, roots, { maxFileBytes, excluder = NONE, ha
112
131
  }
113
132
 
114
133
  // Breadth-first, sorted walk. Symlinks are never followed; node_modules, .git,
115
- // __pycache__, venv, site-packages and hidden folders are skipped. Excluded
134
+ // __pycache__, venv, site-packages, hidden folders and Cargo build folders
135
+ // (target/ with a CACHEDIR.TAG, listed in skipped.cargoTarget) are skipped. Excluded
116
136
  // files and folders are only counted: their names can be private too.
117
137
  async function collectFiles(dir, root, { maxFiles, maxFileBytes, recursive, excluder = NONE }) {
118
138
  const files = [];
119
- const skipped = { excluded: 0, tooLarge: [], symlinks: 0, unreadable: [], hardlinks: [] };
139
+ const skipped = { excluded: 0, tooLarge: [], symlinks: 0, unreadable: [], hardlinks: [], cargoTarget: [] };
120
140
  // Exclusions match the real names: on Windows dir may be spelled with
121
141
  // short names (SRC~1) that the patterns do not know.
122
142
  const prefix = excluder.active ? path.relative(realNative(root), realNative(dir)) : "";
@@ -142,7 +162,9 @@ async function collectFiles(dir, root, { maxFiles, maxFileBytes, recursive, excl
142
162
  } else if (entry.isSymbolicLink()) {
143
163
  skipped.symlinks++;
144
164
  } else if (entry.isDirectory()) {
145
- if (recursive && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) queue.push(full);
165
+ if (!recursive || SKIP_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
166
+ if (entry.name === "target" && await isCargoTarget(full)) skipped.cargoTarget.push(rel);
167
+ else queue.push(full);
146
168
  } else if (entry.isFile() && isSourceFile(entry.name)) {
147
169
  let size;
148
170
  try {
package/src/clients.js CHANGED
@@ -4,7 +4,7 @@ const os = require("os");
4
4
  const path = require("path");
5
5
 
6
6
  const SERVER_NAME = "sentinel-ir";
7
- const PACKAGE_SPEC = "sentinel-ir-mcp@0.4";
7
+ const PACKAGE_SPEC = "sentinel-ir-mcp@0.5";
8
8
  const WORKSPACE_VAR = "${workspaceFolder}";
9
9
 
10
10
  function homeOf(env) {
package/src/doctor.js CHANGED
@@ -84,8 +84,8 @@ function checkConfig(client, file, explicit, project, add, platform, env) {
84
84
  const runs = `${label}: "${SERVER_NAME}" runs ${entry.command} ${args.join(" ")}`;
85
85
  if (entry.command === "npx" && platform === "win32") {
86
86
  add("warn", `${client}.entry`, runs, `On Windows a client may not find "npx" without a shell. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
87
- } else if (args.some(a => /^sentinel-ir-mcp@0\.[0-3](\.|$)/.test(a))) {
88
- add("warn", `${client}.entry`, runs, `This entry stays on an older version without native-code detection, whose parser stops working in long sessions. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
87
+ } else if (args.some(a => /^sentinel-ir-mcp@0\.[0-4](\.|$)/.test(a))) {
88
+ add("warn", `${client}.entry`, runs, `This entry stays on an older version without Rust support. Run: npx -y ${PACKAGE_SPEC} setup --client ${client}`);
89
89
  } else if (entry.command === "npx" || isWindowsNpxWrapper(entry)) {
90
90
  add("ok", `${client}.entry`, runs);
91
91
  } else {
package/src/paths.js CHANGED
@@ -7,7 +7,8 @@ const { fileURLToPath } = require("url");
7
7
  const JS_EXTENSIONS = new Set([".js", ".mjs", ".cjs"]);
8
8
  const TS_EXTENSIONS = new Set([".ts", ".mts", ".cts", ".tsx"]);
9
9
  const PY_EXTENSIONS = new Set([".py", ".pyi"]);
10
- const SOURCE_EXTENSIONS = new Set([...JS_EXTENSIONS, ...TS_EXTENSIONS, ...PY_EXTENSIONS]);
10
+ const RS_EXTENSIONS = new Set([".rs"]);
11
+ const SOURCE_EXTENSIONS = new Set([...JS_EXTENSIONS, ...TS_EXTENSIONS, ...PY_EXTENSIONS, ...RS_EXTENSIONS]);
11
12
 
12
13
  class AccessError extends Error {}
13
14
 
package/src/server.js CHANGED
@@ -39,10 +39,10 @@ const RISK_NOTE =
39
39
  "is unknown: e.g. readFileSync(req.query.f) is low. Start with critical files, and report a problem only after reading the code.";
40
40
 
41
41
  const INSTRUCTIONS =
42
- "sentinel-ir-mcp turns JavaScript, TypeScript and Python source into a compact structural IR (risk level, dangerous " +
42
+ "sentinel-ir-mcp turns JavaScript, TypeScript, Python and Rust source into a compact structural IR (risk level, dangerous " +
43
43
  "capabilities, routes, exports, imports, env reads, network/filesystem/process/crypto calls). " +
44
44
  "Prefer analyze_file or analyze_directory over reading raw source when you only need structure " +
45
- "or risk, then read the raw source of the files that matter. Supported: JavaScript (.js, .mjs, .cjs), TypeScript (.ts, .mts, .cts, .tsx) and Python (.py, .pyi); the language is picked from the file extension. " +
45
+ "or risk, then read the raw source of the files that matter. Supported: JavaScript (.js, .mjs, .cjs), TypeScript (.ts, .mts, .cts, .tsx), Python (.py, .pyi) and Rust (.rs); the language is picked from the file extension. " +
46
46
  "Rely on analysisStatus, world.risk, forbidden and facts; analysisStatus \"incomplete\" means the file did not parse cleanly. " +
47
47
  "analyze_directory lists such files separately in 'incomplete'; their low risk is not evidence of safety. " +
48
48
  "Suggested order: analyze_directory first, check 'incomplete', 'skipped.unreadable' and 'notAnalyzed' (set when the time budget ran out), pick the relevant files, " +
@@ -131,45 +131,45 @@ function createServer(options = {}) {
131
131
  }
132
132
 
133
133
  server.registerTool("analyze_code", {
134
- title: "Analyze JavaScript, TypeScript or Python source",
134
+ title: "Analyze JavaScript, TypeScript, Python or Rust source",
135
135
  description:
136
- "Analyze JavaScript, TypeScript or Python source text and return its compact structural IR: " +
136
+ "Analyze JavaScript, TypeScript, Python or Rust source text and return its compact structural IR: " +
137
137
  "{analysisStatus, language, mission, world.risk, constraints, forbidden, summary, facts}. " +
138
138
  "Use analyze_file instead when the code is on disk, so the source does not have to pass " +
139
139
  "through the conversation. " + UNTRUSTED_NOTE,
140
140
  inputSchema: {
141
- code: z.string().max(limits.maxFileBytes).describe("JavaScript, TypeScript or Python source"),
142
- target: z.string().max(1024).optional().describe("Optional file name shown as mission.target; its extension (.ts, .tsx, .py, ...) selects the language"),
143
- language: z.enum(["javascript", "typescript", "tsx", "python"]).optional().describe("Overrides the language picked from target (default javascript)")
141
+ code: z.string().max(limits.maxFileBytes).describe("JavaScript, TypeScript, Python or Rust source"),
142
+ target: z.string().max(1024).optional().describe("Optional file name shown as mission.target; its extension (.ts, .tsx, .py, .rs, ...) selects the language"),
143
+ language: z.enum(["javascript", "typescript", "tsx", "python", "rust"]).optional().describe("Overrides the language picked from target (default javascript)")
144
144
  },
145
- annotations: { title: "Analyze JavaScript, TypeScript or Python source", ...READ_ONLY }
145
+ annotations: { title: "Analyze JavaScript, TypeScript, Python or Rust source", ...READ_ONLY }
146
146
  }, ({ code, target, language }) => guarded(() => analyzeCode(code, target, language), auditor("analyze_code", { code, target, language })));
147
147
 
148
148
  server.registerTool("analyze_file", {
149
- title: "Analyze a JavaScript, TypeScript or Python file",
149
+ title: "Analyze a JavaScript, TypeScript, Python or Rust file",
150
150
  description:
151
- "Read one .js/.mjs/.cjs/.ts/.mts/.cts/.tsx/.py/.pyi file inside the allowed folders and return its compact structural IR. " +
151
+ "Read one .js/.mjs/.cjs/.ts/.mts/.cts/.tsx/.py/.pyi/.rs file inside the allowed folders and return its compact structural IR. " +
152
152
  `Relative paths are resolved against the first allowed folder. Files over ${limits.maxFileBytes} bytes are refused. ` +
153
153
  UNTRUSTED_NOTE + excludeNote,
154
154
  inputSchema: {
155
155
  path: z.string().min(1).describe("File path, absolute or relative to the first allowed folder")
156
156
  },
157
- annotations: { title: "Analyze a JavaScript, TypeScript or Python file", ...READ_ONLY }
157
+ annotations: { title: "Analyze a JavaScript, TypeScript, Python or Rust file", ...READ_ONLY }
158
158
  }, async ({ path }) => {
159
159
  const hashes = auditLog.active ? new Map() : null;
160
160
  return guarded(async () => analyzeFile(path, await allowedRoots(), { ...limits, excluder, hashes }), auditor("analyze_file", { path, hashes }));
161
161
  });
162
162
 
163
163
  server.registerTool("analyze_directory", {
164
- title: "Analyze a folder of JavaScript, TypeScript and Python files",
164
+ title: "Analyze a folder of JavaScript, TypeScript, Python and Rust files",
165
165
  description:
166
- "Scan a folder inside the allowed folders and return one short entry per JavaScript, TypeScript or Python file, " +
166
+ "Scan a folder inside the allowed folders and return one short entry per JavaScript, TypeScript, Python or Rust file, " +
167
167
  "sorted from highest to lowest risk, plus risk counts. By default (detail \"summary\") each entry is " +
168
168
  "{path, risk, analysisStatus, language, lines, operations, riskSignals, moreRiskSignals?, parseError?}: " +
169
169
  "riskSignals are up to 3 'kind:code@line' signals (code cut to 120 characters) that set the file's risk level (empty when the risk comes from " +
170
170
  "the text fallback of an incomplete file or from a path containing 'crypto'), moreRiskSignals counts the rest. " +
171
171
  "detail \"full\" adds forbidden, objective and all facts per file instead (much larger). " + RISK_NOTE + " " +
172
- "Skips node_modules, .git, __pycache__, venv, site-packages, hidden folders " +
172
+ "Skips node_modules, .git, __pycache__, venv, site-packages, hidden folders, Cargo build folders (target/ with a CACHEDIR.TAG, listed in skipped.cargoTarget) " +
173
173
  `and symlinks. Analyzes at most max_files files (default ${limits.maxFiles}); 'truncated' tells ` +
174
174
  "you when there were more. 'incomplete' lists files whose analysis did not finish (e.g. parse errors); " +
175
175
  "their risk is not reliable, check them separately. statusCounts counts complete vs incomplete. " +
@@ -186,7 +186,7 @@ function createServer(options = {}) {
186
186
  detail: z.enum(["summary", "full"]).optional()
187
187
  .describe("\"summary\" (default): short entries; \"full\": all facts per file, much larger")
188
188
  },
189
- annotations: { title: "Analyze a folder of JavaScript, TypeScript and Python files", ...READ_ONLY }
189
+ annotations: { title: "Analyze a folder of JavaScript, TypeScript, Python and Rust files", ...READ_ONLY }
190
190
  }, async ({ path, max_files, recursive, detail }) => {
191
191
  const hashes = auditLog.active ? new Map() : null;
192
192
  return guarded(async () => analyzeDirectory(path ?? ".", await allowedRoots(), {