# Signalint — complete documentation Version 1.2.1 · Check schema 1.4 · Node.js >= 22.12.0 # Meet Signalint Source: https://tranqui004.github.io/signalint-site/docs/ ## What Signalint does Your linters and type checker find the issues. Signalint makes that feedback easier to consume: 1. **Check** the project with Oxlint, TypeScript, and optionally Biome or flat-config ESLint. 2. **Normalize** diagnostics into a shared format with file locations, rules, severity, and issue IDs. 3. **Cluster and rank** repeated rules using a deterministic priority ladder, returning up to 10 multi-issue clusters and 100 flat remaining records. 4. **Reuse unchanged checks** with a content-hash cache backed by Node's built-in SQLite. 5. **Track recurring diagnostics** across checks and restore valid session history when the server restarts. 6. **Verify edits** with retained snapshots and diagnostic deltas; optionally ingest external reports or preview caller-provided patches. Clustering is a rule-and-scope heuristic, not semantic proof that every grouped issue has the same underlying cause. Use `get_issue_detail` to investigate before editing. ## Choose your workflow | You want to… | Start with | | --- | --- | | Give a coding agent repository diagnostics | [Connect your MCP client](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/) | | See the complete agent feedback loop | [Quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/) | | Run a check from the terminal | [CLI reference](https://tranqui004.github.io/signalint-site/docs/guides/cli/) | | Compare a baseline with a scoped edit | [Snapshots & delta](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/) | | Integrate host events, workspaces or supplied reports | [Hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/), [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/) and [live diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/) | | Stage and explicitly apply replacement content | [Transactions and limits](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/) | | Implement a client or parse responses | [MCP tools](https://tranqui004.github.io/signalint-site/docs/guides/tools/) and [response schema](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/) | ## Requirements - **Node.js 22.12.0 or newer.** The cache uses the built-in `node:sqlite` module. - **A JavaScript or TypeScript project.** For the default `.` workflow, enable TypeScript only when the project has a root `tsconfig.json`. - **Your diagnostic toolchain.** Since 1.1.0, Oxlint and TypeScript are optional peers, not guaranteed bundled engines. Install the engines you need in this project. - **Recommended project-local setup.** Install `signalint-mcp` and run `init` in the repository you want to check. These docs describe **Signalint 1.2.1** and check-response **schema 1.4**. ## What changed in 1.2.1 The **13 MCP tools** and check schema stay the same. The patch release requires a check ID for issue detail, isolates stored snapshots with deep clones, mirrors structured JSON in the text fallback, improves YAML/glob workspace discovery, and adds apply-time path/hash preflight plus an optional explicit baseline. Hook adapters and supplied external reports remain available. The check schema remains **1.4**; source-writing apply is an explicit new permission boundary. ### Since 1.0 Schema 1.4 adds flat `remainingIssues`, shortened summary IDs, and explicit omitted counts. File checks filter displayed diagnostics to requested paths; TypeScript cache state additionally fingerprints source-file metadata. You can choose [MCP payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) or opt into compact check keys. See the [changelog](https://tranqui004.github.io/signalint-site/docs/about/changelog/) before upgrading a parser. ## Know the boundaries Signalint is a local **stdio MCP server**, not a hosted API or an IDE extension. It does not generate automatic fixes or scan for security vulnerabilities. Checks use diagnostic rather than fix modes; **explicit `apply_diagnostic_fix` can write caller-provided source replacements**. Cache and session artifacts default under `.signalint/` with supported state overrides. TypeScript build mode can also write build-info files outside that directory; engine configurations and plugins are not sandboxed. For monorepos, cache-invalidation caveats, and engine-specific constraints, read the [known limitations](https://tranqui004.github.io/signalint-site/docs/reference/limitations/). ## Documentation for agents Use [llms.txt](https://tranqui004.github.io/signalint-site/llms.txt) for a compact documentation index, or [llms-full.txt](https://tranqui004.github.io/signalint-site/llms-full.txt) for the complete plain-text guide. Both are generated from these pages. --- # Installation Source: https://tranqui004.github.io/signalint-site/docs/getting-started/installation/ ## Before you begin You need **Node.js ≥ 22.12.0** and a JavaScript or TypeScript repository. Run the commands below **from that project's root**, not from a shared tools directory. ```sh node --version ``` The default `.` workflow requires a root `tsconfig.json` for TypeScript; advanced [first-target selection](https://tranqui004.github.io/signalint-site/docs/guides/engines/#typescript-configuration-selection) is different. For a JavaScript-only project, `init` leaves TypeScript disabled when no root config is detected. ## 1. Add the package Install [`signalint-mcp`](https://www.npmjs.com/package/signalint-mcp) as a development dependency. The commands below currently resolve npm’s latest version; these docs audit **1.2.1**. Add `@1.2.1` to the package name if you need that exact edition: ### npm ```sh npm install --save-dev signalint-mcp ``` ### pnpm ```sh pnpm add -D signalint-mcp ``` ### yarn ```sh yarn add -D signalint-mcp ``` ### Provide the engines you need Since 1.1.0, **Oxlint and TypeScript are optional peer dependencies**, not runtime dependencies bundled by a default Signalint install. Biome is also optional; ESLint must be installed in the project. Reuse your project's compatible toolchain, or add the engines you intend to enable: ```sh npm install --save-dev oxlint typescript ``` The published peer declarations are `oxlint@1.86.0`, `typescript@7.0.2`, and optional `@biomejs/biome@2.5.15`; those are the versions used in the source audit. Check compatibility before changing an existing project's compiler/linter versions. A fresh Signalint-only install does not supply them; unresolved enabled engines report `disabled`, and no successful engine yields `nothing_checked`, not `clean`. ## 2. Initialize the project ```sh npx signalint-mcp init ``` The setup command: - Detects TypeScript, Oxlint, Biome, flat ESLint, and Prettier configuration. - Creates `signalint.config.json` if it does not already exist. - Appends `.signalint/` to `.gitignore` when no exact `.signalint` or `.signalint/` line exists. The v1 CLI does this automatically, including non-interactive runs; it does not normally prompt for this write. - Detects nearby MCP clients and asks before updating a selected client configuration. TypeScript is enabled when a root `tsconfig.json` exists. Biome and ESLint follow their detected root configs. In v1, Oxlint is enabled when a root `.oxlintrc` or `.oxlintrc.*` file is detected **or no Biome config exists**; it can therefore coexist with ESLint. Review the generated file before your first check. > No client detected? If a client cannot be selected safely, `init` prints copyable configuration snippets. Follow the guide for [your client](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/) rather than creating a global entry with a fixed working directory. ## 3. Inspect the setup The package provides two executables: `signalint` is the explicit terminal CLI; `signalint-mcp` starts stdio when launched by an MCP client. Since 1.1.1 the MCP entry point also dispatches recognized CLI verbs, so `signalint-mcp doctor` now works. A bare interactive TTY launch prints guidance and exits `2`, rather than waiting for JSON-RPC. ### npm ```sh npx --no-install signalint doctor ``` ### pnpm ```sh pnpm exec signalint doctor ``` ### yarn ```sh yarn run signalint doctor ``` Doctor checks the root, configuration-file existence, JS/TS markers, package metadata for **all four engines**, active payload mode, and some stale `cwd` entries. It now warns about unavailable linters or TypeScript, but warnings do not necessarily make it fail. Config parse errors can still be swallowed; `PASSED` does not prove config validity, engine execution, or an MCP connection. Review the output, then verify `ping` using the default `both` mode. > Upgrading from 1.0.0 Install selected engines explicitly if you relied on bundled Oxlint/TypeScript. Update response consumers for schema 1.4, missing inactive warnings, optional engine-map entries, and flat remaining records. The explicit `signalint` CLI remains a clear choice; recognized `signalint-mcp` CLI verbs no longer hang as they did in 1.0. ## 4. Connect and check Open your [client setup guide](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/), restart or reconnect the MCP client, then follow the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/). You can also check the repository without an MCP client: ```sh npx --no-install signalint check . ``` The CLI prints clustered JSON. An exit code of `1` is expected when issues are found with the default settings. The JSON client-file merger uses strict JSON, not JSONC. Back up existing settings: comments or trailing commas can make `init` fail; the selected Signalint entry is replaced, so recheck any custom `env` or arguments. See [client setup](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/#what-the-registry-does-and-does-not-prove). Keep `signalint.config.json` in version control if it is part of your team's setup. **Do not commit `.signalint/`**: it contains local cache and session state. --- # Quickstart Source: https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/ Complete [installation](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) and [client setup](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/) first. The examples below show MCP tool arguments and representative structured results—not shell commands. IDs and paths will differ in your project. ## 1. Confirm the project with ping Ask your agent to call `ping` with an empty object: ```json {} ``` The text response is `pong`. In the default `both` mode its `structuredContent` confirms the canonical project root (`text` mode omits it): ```json { "pong": true, "projectRoot": "/path/to/your-project" } ``` **Check the path before editing.** If it names another repository, fix the client configuration using the [multiple-projects guide](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/). ## 2. Check the project Call `check_project`: ```json { "paths": ["."] } ``` Omitting `paths` also checks `.`. A representative result looks like this: ```json { "schemaVersion": "1.4", "status": "issues_found", "projectRoot": "/path/to/your-project", "engines": { "oxlint": { "status": "ok" }, "tsc": { "status": "ok" } }, "totalIssues": 21, "clusters": [ { "clusterId": "c4588ddaf", "rootCauseSummary": "21 TS2322 issues across 11 files", "ruleIds": ["TS2322"], "issueCount": 21, "fileCount": 11, "priority": 1, "sampleIssueIds": [ "b2dbbc348dd5" ] } ], "remainingIssues": [], "omittedIssueCount": 0, "truncated": false, "checkId": "7e3c2bd2" } ``` Start with the lowest priority number: **1 is most urgent, 5 is least urgent**. Always read `status` and each engine's outcome; an engine failure can coexist with useful diagnostics from completed engines. ## 3. Request the details you need Single-rule occurrences appear in `remainingIssues`, and clusters containing only 2 issues omit sample IDs. Use a returned `clusterId` or `issueId` as appropriate. Call `get_issue_detail` with an `issueId` from `sampleIssueIds` when present. **`checkId` is required in 1.2.1** and selects the originating retained snapshot. It is not a unique invocation ID; identical diagnostic fields can produce the same value across calls: ```json { "issueId": "b2dbbc348dd5", "checkId": "7e3c2bd2" } ``` The structured result wraps the matching diagnostics in `issues`: ```json { "issues": [ { "issueId": "b2dbbc348dd564e942cc317d434c39d4ac3a1a925d3d53fe23d5d184ce8b820a", "file": "src/file01.ts", "line": 3, "col": 14, "engine": "tsc", "rule": "TS2322", "severity": "error", "message": "Type 'string' is not assignable to type 'number'.", "fixable": false, "clusterId": "c4588ddaf" } ] } ``` Use `clusterId` instead of `issueId` to retrieve matching normalized diagnostics for a cluster. Supply **exactly one** of the two. In `both`/`structured`, the first JSON text block mirrors the `{ issues: [...] }` wrapper; only `text` mode returns the array itself; see the [MCP tool reference](https://tranqui004.github.io/signalint-site/docs/guides/tools/) for transport details. References are not guaranteed unique or permanent; see [ID limitations](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/#reference-identity-limits). Pin the originating `checkId` to query an older retained snapshot; unknown/expired snapshots or unmatched IDs are stale. An unpinned detail request is refused; there is no latest-snapshot fallback. ## 4. Edit, then recheck Signalint reports diagnostics; your agent or editor makes the changes. After an edit, call `check_files` with every changed file: ```json { "files": ["src/file01.ts", "src/shared-types.ts"] } ``` File-local engines reuse unchanged snapshots. **TypeScript still runs its configured program**, then `check_files` filters displayed diagnostics to requested paths. Positive filtered-count aliases and `nextStep` point to other diagnostics you should inspect with `check_project`. The TypeScript metadata fingerprint introduced in 1.2.0 can invalidate cache for unrequested source edits inside its walk. It does not track every external dependency, symlink, nested-config input, or same-size/mtime edit; see [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/). Use `check_project` when you need a broader snapshot that fits the [current scan limits](https://tranqui004.github.io/signalint-site/docs/reference/limitations/#project-scan-coverage-and-path-limits), and follow the [cache caveats](https://tranqui004.github.io/signalint-site/docs/reference/caching/) after changes outside tracked inputs. ## 5. Watch for a recurring issue Check responses may include `loopWarning` or `fileRuleChurnWarning`. To inspect the current session state, call `get_loop_status` with `{}`: ```json { "looping": false, "signatures": [], "fileChurning": false, "fileRuleChurns": [] } ``` A warning is a reason to investigate the approach, not a guarantee that Signalint knows the correct fix. Read [loop and churn detection](https://tranqui004.github.io/signalint-site/docs/reference/loop-detection/) for the exact behavior. ## Next steps - [MCP tools](https://tranqui004.github.io/signalint-site/docs/guides/tools/) — accepted arguments, outputs, and error codes. - [Configuration](https://tranqui004.github.io/signalint-site/docs/guides/configuration/) — engine selection and timeouts. - [Clustering and priorities](https://tranqui004.github.io/signalint-site/docs/reference/priority-ladder/) — how findings are grouped and ordered. ## Retain a baseline for your next edit Save the check ID, engine outcomes and scope in the current 1.2.1 workflow. After an edit, `after_edit_check` can run a configured file check and compare it to the baseline. Read [snapshots & delta](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/): scoped absence is not proof of resolution, and delta advice does not certify engine coverage. Checking still does not edit source. The separate [preview/apply tools](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/) accept caller-provided replacement content and can explicitly write it; do not enable apply without reviewing its permissions and limitations. --- # Connect your client Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/ Signalint's setup registry recognizes **Claude Code, Cursor, Codex CLI, Antigravity, VS Code, Windsurf, and Zed**. Its snippets launch `signalint-mcp` over stdio. Signalint itself requires no hosted endpoint or API key; model/provider requirements belong to the client. ## Choose a setup guide Project paths below are relative to the repository root. User paths describe the locations recognized by Signalint's client registry, not a guarantee that every client version supports identical settings. ## Recommended setup 1. [Install Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want to check. 2. Run `npx signalint-mcp init` there. 3. Choose a detected client and confirm the configuration write, or copy the printed snippet. 4. Restart or reconnect the MCP client. 5. Call `ping` and confirm `structuredContent.projectRoot` before the first check. `init` orders detected project candidates before user candidates and asks before writing a selected client entry in an interactive run. Non-interactive runs print snippets rather than updating a client file. Configuration and `.gitignore` writes are separate setup effects. ## Payload-mode choice Use the default `both` mode while connecting and verifying `ping`'s structured project root. `text` mode omits that ping object; `structured` mode requires the host to expose structured diagnostic data to the model. Registry-generated snippets are not proof of support for every mode/editor version. Read [MCP payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) before opting in. ## What the registry does and does not prove The registry is authoritative for **Signalint's generated targets and shapes**, not an external editor's compatibility contract. A marker such as `.cursor`, `.agents`, or `GEMINI.md` is a detection hint, not proof that a client is installed or consumes the emitted path. Check your client's active MCP settings and trusted-workspace requirements. - JSON merging uses strict JSON, not JSONC. Comments/trailing commas can fail; formatting is rewritten. - Other server entries are retained in parseable JSON, but the selected `signalint` entry is replaced, including custom `env` or arguments. - Codex block replacement is textual, not a complete TOML parser. - Back up settings before accepting a write, and verify `ping` afterward. - Antigravity paths marked “legacy” by Signalint are not automatically proven obsolete for your installed editor. Canonical emitter: [`src/clients/registry.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/clients/registry.ts) and [`src/init.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/init.ts). ## Project scope or user scope? **Project scope** is the safest place for repository-specific configuration. Claude Code, Cursor, and Codex entries can include a project `cwd`. Signalint's registry does not emit `cwd` for Antigravity or VS Code; verify their inherited root after connecting. **User scope** is shared. Windsurf and Zed use user-scoped locations in the registry. Shared targets outside the project normally omit a fixed repository `cwd`, which avoids silently checking the same old repository in every workspace—but the client still needs to launch the process in the correct place. The emitter uses path containment, so an unusual user target inside the project can still receive `cwd`; inspect the result. > One shared entry must not point at yesterday's project A stale `cwd` or `SIGNALINT_PROJECT_ROOT` can direct the agent to another repository. No client configuration is safe until you confirm the returned project root. Read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before reusing a global entry. ## Windows and runtime paths Windows examples use `cmd /c npx --no-install signalint-mcp` to handle npm shims. Ensure the client sees **Node.js ≥ 22.12.0**, and that `--no-install` can find the project-local package. If the package cannot be resolved or a linked shim fails, see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/). Then continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/). --- # Claude Code Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/claude-code/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want Claude Code to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## Project setup Run `npx signalint-mcp init` to let Signalint detect the client and ask before updating its configuration. The v1 registry targets **`.mcp.json`** in the project root. Merge the example into the configuration your client actually uses, preserving unrelated servers. ### macOS / Linux ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ], "cwd": "/path/to/your-project" } } } ``` ### Windows ```json { "mcpServers": { "signalint": { "command": "cmd", "args": [ "/c", "npx", "--no-install", "signalint-mcp" ], "cwd": "C:\\path\\to\\your-project" } } } ``` Replace the example `cwd` with the absolute path to this repository. A fixed `cwd` belongs in a project-specific configuration, not a shared user entry. ## User setup The v1 registry also lists **`~/.claude.json`** as a user target. Omit a repository-specific `cwd` so one shared entry does not pin every workspace to the same project: ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` For Windows, use the `cmd /c` wrapper shown in the project tab, without a fixed user-level `cwd`. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # Cursor Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/cursor/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want Cursor to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## Project setup Run `npx signalint-mcp init` to let Signalint detect the client and ask before updating its configuration. The v1 registry targets **`.cursor/mcp.json`** in the project root. Merge the example into the configuration your client actually uses, preserving unrelated servers. ### macOS / Linux ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ], "cwd": "/path/to/your-project" } } } ``` ### Windows ```json { "mcpServers": { "signalint": { "command": "cmd", "args": [ "/c", "npx", "--no-install", "signalint-mcp" ], "cwd": "C:\\path\\to\\your-project" } } } ``` Replace the example `cwd` with the absolute path to this repository. A fixed `cwd` belongs in a project-specific configuration, not a shared user entry. ## User setup The v1 registry also lists **`~/.cursor/mcp.json`** as a user target. Omit a repository-specific `cwd` so one shared entry does not pin every workspace to the same project: ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` For Windows, use the `cmd /c` wrapper shown in the project tab, without a fixed user-level `cwd`. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # Codex CLI Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/codex-cli/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want Codex CLI to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## Project setup Run `npx signalint-mcp init` to let Signalint detect the client and ask before updating its configuration. The v1 registry targets **`.codex/config.toml`** in the project root. Merge the example into the configuration your client actually uses, preserving unrelated servers. ### macOS / Linux ```toml [mcp_servers.signalint] command = "npx" args = ["--no-install", "signalint-mcp"] startup_timeout_sec = 20 cwd = "/path/to/your-project" ``` ### Windows ```toml [mcp_servers.signalint] command = "cmd" args = ["/c", "npx", "--no-install", "signalint-mcp"] startup_timeout_sec = 20 cwd = "C:\\path\\to\\your-project" ``` Replace the example `cwd` with the absolute path to this repository. A fixed `cwd` belongs in a project-specific configuration, not a shared user entry. ## User setup The v1 registry also lists **`~/.codex/config.toml`** as a user target. Omit a repository-specific `cwd` so one shared entry does not pin every workspace to the same project: ```toml [mcp_servers.signalint] command = "npx" args = ["--no-install", "signalint-mcp"] startup_timeout_sec = 20 ``` For Windows, use the `cmd /c` wrapper shown in the project tab, without a fixed user-level `cwd`. > Project configuration must be active Make sure Codex is using the repository configuration and that any required workspace trust is granted. If it uses a user fallback instead, remove stale root settings there and reconnect. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # Antigravity Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/antigravity/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want Antigravity to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## Project setup Run `npx signalint-mcp init` to let Signalint detect the client and ask before updating its configuration. The v1 registry targets **`.agents/mcp_config.json`** in the project root. Merge the example into the configuration your client actually uses, preserving unrelated servers. ### macOS / Linux ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` ### Windows ```json { "mcpServers": { "signalint": { "command": "cmd", "args": [ "/c", "npx", "--no-install", "signalint-mcp" ] } } } ``` The Signalint registry does not emit `cwd` for this client. Confirm the inherited project root with `ping` after connecting. If the client starts the process elsewhere, use its environment settings to select `SIGNALINT_PROJECT_ROOT` for this workspace. ## User setup The v1 registry also lists **`~/.gemini/config/mcp_config.json`** as a user target. Omit a repository-specific `cwd` so one shared entry does not pin every workspace to the same project: ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` For Windows, use the `cmd /c` wrapper shown in the project tab, without a fixed user-level `cwd`. > Review legacy entries Signalint labels `~/.gemini/antigravity/mcp_config.json` as legacy, but that label does not prove which path your installed Antigravity version uses. Confirm the active path in the client's MCP settings before migrating or removing anything; never delete an active entry solely because doctor calls it legacy. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # VS Code Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/vscode/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want VS Code to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## Project setup Run `npx signalint-mcp init` to let Signalint detect the client and ask before updating its configuration. The v1 registry targets **`.vscode/mcp.json`** in the project root. Merge the example into the configuration your client actually uses, preserving unrelated servers. ### macOS / Linux ```json { "servers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` ### Windows ```json { "servers": { "signalint": { "command": "cmd", "args": [ "/c", "npx", "--no-install", "signalint-mcp" ] } } } ``` The Signalint registry does not emit `cwd` for this client. Confirm the inherited project root with `ping` after connecting. If the client starts the process elsewhere, use its environment settings to select `SIGNALINT_PROJECT_ROOT` for this workspace. ## User setup Signalint 1.2.1's registry declares the workspace target, not a VS Code user-level path. For a user-level setup, follow your installed client's own configuration editor/docs; this source audit does not infer an additional registry target. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # Windsurf Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/windsurf/ Install and [initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want Windsurf to check. The MCP entry point is `signalint-mcp`; the registry emits `--no-install`. Install the package and verify that the client can resolve it from its launch environment; that flag is not a project-root selector. **Source scope:** The paths and command shapes below describe Signalint **1.2.1's registry/emitter**, not an integration test of every client version. Confirm the active configuration path and accepted keys in your client's own settings/docs before copying. A registry marker only helps `init` select a candidate; it does not prove that an editor is installed or reading that file. The v1 JSON merger requires strict JSON (not comments/trailing commas) and replaces the selected Signalint entry, including custom `env` or arguments. Back up and review settings before accepting a write. TOML setup replaces its Signalint block rather than providing a full configuration parser. ## User setup The Signalint registry supports Windsurf through the user configuration at **`~/.codeium/windsurf/mcp_config.json`**. Open the client's MCP settings or merge this entry manually: ### macOS / Linux ```json { "mcpServers": { "signalint": { "command": "npx", "args": [ "--no-install", "signalint-mcp" ] } } } ``` ### Windows ```json { "mcpServers": { "signalint": { "command": "cmd", "args": [ "/c", "npx", "--no-install", "signalint-mcp" ] } } } ``` Do not add a hardcoded repository `cwd` to this shared entry. The process must inherit the workspace root, or use an explicit environment override deliberately for the active project. ## Verify the connection 1. Restart or reconnect the client's MCP server connection. 2. Call `ping` with `{}`. The text is `pong`; inspect `structuredContent.projectRoot`. 3. Confirm that the root is this repository, then call `check_project` with `{}`. If the local package cannot be found, check the client's working directory and Node PATH. For a fixed project, an absolute `node` entry-point path can bypass package-manager shims; see [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/#windows-startup-fails-or-closes-immediately). Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/), or read [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before sharing one configuration across repositories. --- # Zed Source: https://tranqui004.github.io/signalint-site/docs/getting-started/clients/zed/ [Install and initialize Signalint](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) in the repository you want to check. Signalint 1.2.1's registry targets the user-scoped `~/.config/zed/settings.json` file. This describes its emitter, not proof that every Zed version/platform uses that location or accepts the same schema. Zed settings can contain JSONC. The v1 automatic merger uses strict `JSON.parse` and can fail on comments or trailing commas; use the client's settings editor and preserve existing configuration. Back up before replacing the Signalint entry. ## Configure the context server Merge the entry under `context_servers`, preserving any existing servers: ```json { "context_servers": { "signalint": { "command": { "path": "npx", "args": ["--no-install", "signalint-mcp"] } } } } ``` Signalint emits the nested `command.path` and `command.args` shape for Zed. Do not replace it with another client's `mcpServers` shape. For a Windows client using npm shims, the corresponding command is: ```json { "context_servers": { "signalint": { "command": { "path": "cmd", "args": ["/c", "npx", "--no-install", "signalint-mcp"] } } } } ``` The settings location can vary with your platform and Zed version; use Zed's settings editor when the registry path is not the active one. ## Verify the workspace 1. Restart or reconnect the context server. 2. Call `ping` with `{}` and inspect `structuredContent.projectRoot`. 3. Confirm the root is the open repository before calling `check_project`. This is a shared user entry without a project `cwd`. The process must inherit the correct workspace root. If it does not, use a deliberate project-root environment override in your launcher rather than assuming it follows the active editor automatically. If `--no-install` cannot find Signalint, verify the working directory and Node PATH. See [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/) for an absolute Node entry-point fallback. Continue with the [quickstart](https://tranqui004.github.io/signalint-site/docs/getting-started/quickstart/) and [multiple-projects guide](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/). --- # Multiple projects Source: https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/ Install and initialize Signalint separately in each repository. The local server checks the root selected when it starts; opening another editor tab does not automatically change an already-running server's root. ## The global configuration trap A shared MCP entry with a fixed path can silently point every workspace at the same repository: ```json { "mcpServers": { "signalint": { "command": "npx", "args": ["--no-install", "signalint-mcp"], "cwd": "/path/to/an-old-project" } } } ``` The JSON above illustrates the problem—**do not copy it into a user/global configuration**. The same risk applies to a stale `SIGNALINT_PROJECT_ROOT` environment value. ## Prefer project-specific entries For clients that support it, place the entry in the repository's own MCP configuration. A `cwd` then belongs to that project alone. The v1 emitter decides `cwd` from path containment and the registry's `supportsCwd` flag. Normal user targets outside the project omit it and receive a warning. An unusual home/user config path inside the project boundary can still receive `cwd`, even though its registry scope is user. Inspect the actual entry; the warning is not proof that the field was omitted. A global entry without `cwd` avoids pinning the old repository, but correctness still depends on the directory chosen by the client. Always verify it. ## How the server selects a root For a normally launched MCP server: 1. A valid `SIGNALINT_PROJECT_ROOT` environment value selects an explicit root. 2. Otherwise, the server uses the process working directory chosen by the client. The root is canonicalized. An invalid environment override logs a warning and falls back to the process working directory. Embedded API callers can also supply an explicit server `cwd` option, which takes precedence. The server writes the resolved path to stderr at startup and exposes it in `ping`'s structured result. ## Verify every connection Call `ping` with `{}`: ```json { "pong": true, "projectRoot": "/path/to/the-current-project" } ``` These ping-root instructions assume default `both` mode: `text` mode omits ping's structured root, and compact checks omit their root. Use full output while confirming your target. Then run the CLI doctor from that project: ```sh npx --no-install signalint doctor ``` Doctor can flag stale `cwd` entries in detected configs. Reconnect the MCP process after changing roots or client settings, and discard IDs from the previous connection. ## Checklist when switching repositories - Is Signalint installed in the new project? - Does that project have its own reviewed `signalint.config.json`? - Does the MCP configuration launch in the correct directory? - Are any shared `cwd` or environment overrides stale? - Does `ping` return the new canonical root? Use the [client-specific guides](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/) for supported file locations and command shapes. --- # Configuration Source: https://tranqui004.github.io/signalint-site/docs/guides/configuration/ Signalint reads `signalint.config.json` in the checked project root. Create it with `npx signalint-mcp init`, then review the detected settings. Engine-native rules remain in their own files, such as `tsconfig.json` or `eslint.config.js`. ## A complete example ```json { "engines": { "oxlint": true, "tsc": true, "biome": false, "eslint": false }, "ignore": ["node_modules/**", "dist/**", ".signalint/**"], "timeoutsMs": { "oxlint": 30000, "tsc": 60000, "biome": 30000, "eslint": 30000 }, "mcpPayload": "both" } ``` This example assumes a TypeScript project with a root `tsconfig.json` and the selected engines installed. Oxlint and TypeScript are optional peers in 1.2.1, not bundled runtime dependencies. Set `tsc` to `false` for a JavaScript project without that config. ## Engine selection | Key | Accepted value | Omitted-key behavior | | --- | --- | --- | | `engines.oxlint` | Boolean | Enabled | | `engines.tsc` | Boolean | Enabled; requires TypeScript configuration | | `engines.biome` | Boolean, or `{ "includeFormatter": boolean }` | Disabled | | `engines.eslint` | Boolean | Detected from a root flat ESLint config when loading a project | `init` chooses settings based on detected tools, so its generated configuration may differ from these parser defaults. Set each engine explicitly if you need predictable behavior across repositories. ### Include Biome formatter diagnostics Biome lint diagnostics are enabled by `true`. Formatter diagnostics are suppressed unless you opt in: ```json { "engines": { "oxlint": false, "tsc": true, "biome": { "includeFormatter": true }, "eslint": false } } ``` Biome is an optional peer dependency. For this documented release, install the optional peer `@biomejs/biome@2.5.15` before enabling it. Clear the diagnostic cache after changing `includeFormatter`: v1 does not hash this setting, so existing hits can retain the old behavior. ESLint needs a project-local installation and a flat config; see [diagnostic engines](https://tranqui004.github.io/signalint-site/docs/guides/engines/). ## MCP payload mode `mcpPayload` accepts `"both"` (default), `"text"`, or `"structured"`. A non-empty `SIGNALINT_MCP_PAYLOAD` environment value overrides it; invalid names throw an error. Restart the server after changing transport mode. This is independent of `SIGNALINT_COMPACT=1`, which abbreviates check keys and omits the check root. Read [MCP payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) before opting in: text mode omits structured data/output schemas, and structured mode needs host support. ## Ignore paths `ignore` accepts non-empty strings, interpreted against normalized project-relative paths. The built-in matcher handles a limited `*` / `**` / `?` syntax, not a full glob library. Prefer simple explicit directory prefixes such as `src/generated/**`; wildcard prefixes combined with a trailing `/**` can behave differently from standard glob libraries. ```json { "ignore": ["node_modules/**", "dist/**", ".signalint/**", "src/generated/**"] } ``` An explicit array **replaces** the default array. The project walker still skips child entries named `node_modules`, `.git`, `.signalint`, and `dist`; changing this array does not remove that walker behavior. Preserve any default exclusions you still want. Diagnostics whose path contains a `node_modules` segment are also excluded independently of this setting. > **TypeScript stays whole-program.** Ignored paths do not trigger an incremental file check and their diagnostics are removed from the result, but the TypeScript compiler still receives the configured program. `ignore` is not a substitute for `include` and `exclude` in `tsconfig.json`. ## Execution timeouts Values in `timeoutsMs` must be positive integers, in milliseconds: | Engine | Default | | --- | --- | | Oxlint | 30,000 ms | | TypeScript | 60,000 ms | | Biome | 30,000 ms | | ESLint | 30,000 ms | Signalint attempts to terminate a timed-out process tree; errors are logged and cleanup is best-effort. Its engine outcome becomes `status: "error"`; completed engines' diagnostics remain available. A timeout does not mean the project is clean. ## Project-root override The MCP server normally uses the working directory chosen by your client. `SIGNALINT_PROJECT_ROOT` can explicitly select a project root when the client does not launch there. See [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) before using a fixed path in a shared configuration. `SIGNALINT_ALLOW_UNINITIALIZED=1` bypasses the MCP requirement for an existing `signalint.config.json`. It does not bypass the JS/TS marker check. Use this only deliberately; normal setup should run `init`. ## Validation and cache changes Unknown configuration keys, wrong value types, and invalid timeout values fail validation rather than being silently ignored. Tracked engine-config changes invalidate related cache entries. Not every nested, extended, or dynamic config is recognized equally. Read the [cache reference](https://tranqui004.github.io/signalint-site/docs/reference/caching/) before assuming a configuration change is covered. ## Workspace mode and state overrides `monorepoMode` accepts `off` (default), `auto`, or `strict`. `SIGNALINT_MONOREPO_MODE` overrides it. Discovery uses a limited pnpm block-list parser, not full YAML/glob support; see [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/). The MCP server can use `SIGNALINT_PROJECT_ROOT` instead of its launch directory; verify the actual canonical root with `ping`. `SIGNALINT_STATE_DIR` redirects supported SQLite/session/project-mode TypeScript storage. Prefer absolute per-project state directories; the setting does not redirect every build-mode compiler artifact or provide an OS sandbox. --- # Diagnostic engines Source: https://tranqui004.github.io/signalint-site/docs/guides/engines/ Signalint invokes existing tools and normalizes their output. Enable engines in [configuration](https://tranqui004.github.io/signalint-site/docs/guides/configuration/) and review their native configuration; Signalint does not provide an OS sandbox or reproduce every native command's defaults. ## Registered engines | Engine | Diagnostic input | How Signalint treats it | Resolution | | --- | --- | --- | --- | | **Oxlint** | JavaScript / TypeScript lint output | Requested-file batches | Optional peer, not a bundled runtime dependency; project-local preferred | | **TypeScript (`tsc`)** | TypeScript compiler output | A configured program, not just the requested files | Optional peer, not a bundled runtime dependency; project-local preferred | | **Biome** | Lint/parser output, optionally formatter output | Requested-file batches; JS/TS, JSON/JSONC, CSS, GraphQL candidates | Optional peer; local preferred, with a fallback if the peer is available beside Signalint | | **ESLint** | Output from the project's ESLint CLI | Requested-file batches | Project-local only; no bundled ESLint dependency | The 1.2.1 package declares optional peers `oxlint@1.86.0`, `typescript@7.0.2`, and `@biomejs/biome@2.5.15`; none is a required runtime dependency. Install selected engines explicitly. The resolver still has a fallback branch for Oxlint/tsc/Biome **if an engine is actually resolvable beside Signalint** (as in a source-development checkout). This is not a promise that a fresh published install contains those engines. Resolution only accepts a project-local package whose canonical path is inside the selected project root. A dependency hoisted outside a package root is not automatically accepted as that package's local engine. Use the intended repository/package root deliberately. ## Missing engines If a cache miss needs an engine that cannot be resolved, its task reports `disabled` with an install hint rather than inventing a clean result. All-disabled tasks lead to `error` / `nothing_checked`. An empty relevance set or cache hit can still avoid launching a missing engine. ## Oxlint `init` enables Oxlint when it detects a root `.oxlintrc` or `.oxlintrc.*` file, **or no Biome config exists**. This can enable it alongside ESLint. The list of files hashed for Oxlint is a separate, narrower list: `.oxlintrc`, `.oxlintrc.json`, and `oxlint.json`. Overlapping engines can emit overlapping diagnostics. The cache is not a semantic deduplication or dependency-analysis layer. ## TypeScript configuration selection For the default `check_project {}` or `signalint check .` workflow, the first target is `.` and TypeScript selects the project root's `tsconfig.json`. In single-project/root-fallback mode, for `check_project.paths` and CLI positional paths, **the first path selects one compiler configuration**: | First path | Selection | | --- | --- | | Directory | That directory's `tsconfig.json`; it does not fall back to the root if missing | | `tsconfig*.json` file | That explicit configuration | | Source file | Closest `tsconfig.json` found upward within the project boundary | Additional paths do not independently select additional TypeScript configurations. For example, `check_project` with `paths: ["src"]` can fail the TypeScript engine when `src/tsconfig.json` is absent, even if a root config exists. `check_files` still runs the configured whole TypeScript program, then filters returned diagnostics to requested paths. Positive `filteredOutIssueCount` / `filteredOutCount` warns that other diagnostics were excluded and suggests `check_project`. The default compiler runner starts from `.`; the hash/fingerprint probe can choose the nearest config to the first requested file. The [nested-config mismatch](https://tranqui004.github.io/signalint-site/docs/reference/caching/#typescript-selection-and-inspection-limits) remains. ### Compiler flags and artifacts - Normal project mode uses `--noEmit`, `--incremental`, and `.signalint/cache/tsc//tsc.tsbuildinfo`. - If effective `skipLibCheck` is omitted, Signalint adds `--skipLibCheck` in project mode. Set `skipLibCheck` explicitly if declaration-file checking is important to you. - When `tsc --showConfig` reports a `references` property, Signalint uses build mode with `--noEmit` and `--incremental`. - Build mode does **not** redirect build-info files to `.signalint/cache/`; `.tsbuildinfo` files can appear beside or in the configured output of referenced projects. Review the relevant ignore rules. The `--showConfig` inspection cache only keys the root file's contents. Changes to inherited configuration can leave inspection metadata cached until the process restarts. A solution-style root with references is one way to check a monorepo. Default workspace mode is off; opt-in pnpm planning supports only its narrow discovery shapes (see [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/)), and [project-snapshot limits](https://tranqui004.github.io/signalint-site/docs/reference/limitations/#project-scan-coverage-and-path-limits) still apply. ## Biome The documented package's optional peer range is exactly **2.5.15**: ```sh npm install --save-dev @biomejs/biome@2.5.15 ``` `"biome": true` suppresses formatter diagnostics. `"biome": { "includeFormatter": true }` opts in. **Clear the diagnostic cache after changing this flag**: v1 does not put `includeFormatter` in the cache key, so existing hits can retain the old behavior. `fixable` is inferred from advice text matching a `safe fix` substring. It is not a safety guarantee or a returned patch; for example, a substring match is not a full classification of every possible advice message. Signalint does not request a fix/write mode. If the adapter needs to run but cannot resolve Biome, it reports a disabled engine with an installation message. A cache hit or empty relevant-file set does not prove a new Biome process ran. ## ESLint Install ESLint and its required plugins in the selected project. The setup detector recognizes root flat-config filenames: ```text eslint.config.js eslint.config.mjs eslint.config.cjs eslint.config.ts eslint.config.mts eslint.config.cts ``` The supported setup workflow targets flat config. The adapter delegates to the installed ESLint CLI rather than implementing its own legacy `.eslintrc` compatibility layer or forcing a particular ESLint version. File/config discovery depends on that installed CLI; nested and dynamically imported configuration is not fully represented in the cache hash. ## Outcomes are not coverage certificates Inspect `engines` and `status`. An `ok` outcome means the configured task resolved successfully; it may have reused cache entries or had no relevant files. It is not proof that a fresh native process checked every file. Normal engine-task failures are isolated, preserving completed tasks' results and setting the check status to `error`. Configuration loading or snapshot I/O can fail before that fan-out and may surface as an MCP request error rather than a normal check payload. Deadlines default to 30 seconds per linter and 60 seconds for TypeScript. Termination is best-effort: POSIX process-group kills or Windows `taskkill /T /F` are attempted, with fallback and error logging. See [security](https://tranqui004.github.io/signalint-site/docs/about/security/) before running an untrusted project. Source: [`src/engines/`](https://github.com/TranQui004/signalint/tree/v1.2.1/src/engines), [`src/engineResolution.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/engineResolution.ts), and [`src/engineFanout.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/engineFanout.ts). --- # CLI reference Source: https://tranqui004.github.io/signalint-site/docs/guides/cli/ The npm package ships two executables: | Binary | Purpose in 1.2.1 | | --- | --- | | `signalint` | Explicit terminal CLI: init, check, stats, doctor, help, version | | `signalint-mcp` | Stdio MCP server; also dispatches recognized CLI verbs since 1.1.1 | After a local installation use `npx --no-install signalint`, `pnpm exec signalint`, or `yarn run signalint` in the intended project. `signalint-mcp doctor` and `signalint-mcp check .` now dispatch to the CLI. Recognized first arguments are `init`, `check`, `doctor`, `stats`, `help`, `--help`, `-h`, `--version`, and `-v`. A bare interactive TTY launch prints guidance to stderr and exits `2`; a non-TTY MCP client launch starts stdio normally. Unknown first arguments are not automatically treated as CLI verbs by the MCP entry point. ## init ```sh npx --no-install signalint init ``` Detects native configurations, creates a missing `signalint.config.json`, automatically appends `.signalint/` to `.gitignore` when needed, and asks before writing a selected client entry. Existing Signalint configuration is not overwritten. It warns when no linter can be resolved, or when a detected TypeScript setup has no available compiler; it does not install the engines for you. See [installation](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/) for optional-peer setup and strict JSON-merger caveats. ## check ```sh npx --no-install signalint check . ``` Checks the current project or one or more relative paths. The default output is **pretty-printed schema 1.4 JSON**, with clusters and flat remaining records, not a prose report or an MCP transport envelope. The first positional path selects one TypeScript configuration. A directory such as `src` requires `src/tsconfig.json`; additional paths do not select more programs. See [engine selection](https://tranqui004.github.io/signalint-site/docs/guides/engines/#typescript-configuration-selection). The expanded snapshot can refuse a large `.` check even when only one path was supplied. ### Output format ```sh npx --no-install signalint check . --format json npx --no-install signalint check . --format github ``` Supported formats are **`json`** and **`github`**. GitHub output emits an annotation for each collected normalized issue, including its file, line, column, rule, severity, and message; it is not restricted to summary samples. There is no `--format text` option. ### Compact keys ```sh npx --no-install signalint check . --compact ``` `--compact` or `SIGNALINT_COMPACT=1` abbreviates check keys and omits `projectRoot`. The JSON output is still pretty-printed by the CLI. `--compact` is not a `--minify` flag, and is independent of MCP payload mode. See the [key mapping](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/#compact-keys-are-a-separate-option). ### Failure threshold By default, `clean` exits `0`; non-clean results exit `1`. ```sh npx --no-install signalint check . --fail-on-priority 3 ``` In 1.2.1 this tests **returned clusters and returned flat records**. Flat priorities are derived from severity/fixability when absent. A threshold of `3` includes errors at priorities 1–3; `5` includes warnings too. The parser accepts positive integer thresholds; the semantic priority ladder itself is 1–5. > **Still inspect status.** Omitted flat records are not independently examined. Engine failure without a qualifying returned issue can exit `0` with this flag. Inspect JSON `status` and `engines`, and keep native compiler/linter CI checks when completeness matters. Invalid command usage returns `2`; unexpected CLI failures return `1` with a concise stderr message. ## doctor ```sh npx --no-install signalint doctor ``` Reports: - Working-directory root and configuration-file existence. - JS/TS project markers. - Package metadata for **Oxlint, TypeScript, Biome, and ESLint**. - Effective MCP payload mode it resolves, plus missing-linter/TypeScript hints. - Some detected stale `cwd` entries and Signalint-labeled legacy Antigravity entries. Doctor attempts to load configuration for payload mode, but **can swallow parse errors and still report `PASSED`**. Missing-engine warnings are not necessarily blocking. Package inspection is not a successful engine run, and the report does not validate every client setting, root override, or editor trust requirement. Confirm the actual MCP root and a real diagnostic result. ## stats ```sh npx --no-install signalint stats npx --no-install signalint stats --json ``` Reads retained MCP check metrics from `.signalint/session.jsonl` and `.1`, deduplicating retained overlap. `--json` returns the machine-readable aggregate instead of the human report. | JSON field | Meaning | | --- | --- | | `checks`, `payloadSamples` | Retained valid entries and entries with a non-zero normalized baseline | | `averagePayloadReductionPercent` | Arithmetic mean of per-check minified normalized-to-response byte reductions; can be negative or `null` | | `cacheHits`, `cacheMisses`, `cacheHitRatePercent` | Completed-task engine-file lookups, not database row/disk counts | | `latencySamples`, `averageLatencyMs`, `maxLatencyMs` | Retained measured check latency; missing averages are `null` | | `loopWarningsTriggered` | Distinct diagnostic signatures with retained loop warnings, not all conversations or churn events | | `malformedLinesSkipped` | Invalid retained lines skipped while reading statistics | Metrics are recorded **before MCP transport formatting**. They do not count `both` channel duplication or RPC framing, and changing `mcpPayload` is not directly reflected as measured wire savings. CLI `check` does not populate this MCP session history. Empty stats before MCP checks are expected. ## Help and version ```sh npx --no-install signalint --help npx --no-install signalint --version ``` MCP initialize now derives its version from the installed package through `resolveSignalintVersion()`. The previous hardcoded `0.4.2` mismatch is fixed in 1.2.0. ## Hooks and workspace/state controls `signalint hooks preview|install|run --runtime claude|cursor|codex|vscode` manages emitted verify-after-edit hooks. Preview first; installation can replace existing targeted hook entries. Hook decisions are JSON, not a conventional lint exit gate. Read [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/). `SIGNALINT_MONOREPO_MODE=off|auto|strict` overrides workspace planning config. `SIGNALINT_STATE_DIR` redirects supported cache/session/normal TypeScript state; use an absolute separate destination where isolation matters. `SIGNALINT_PROJECT_ROOT` is a server root override, not a replacement for choosing the intended CLI working directory. See [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/). --- # Troubleshooting Source: https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/ Start with the limited CLI doctor report **from the repository you want to check**, then confirm the actual MCP connection with `ping`: ```sh npx --no-install signalint doctor ``` ## A terminal command seems to hang `signalint-mcp` is a stdio server, so it waits for MCP input. Only `init` is specially dispatched by that executable in v1. Use the other binary for terminal commands: ```sh npx --no-install signalint doctor npx --no-install signalint check . ``` Since 1.1.1, `signalint-mcp doctor` also dispatches the CLI. A bare interactive server launch exits with guidance; a normal MCP client launches stdio with non-TTY pipes. ## Node cannot load node:sqlite Check `node --version` in the environment your **MCP client** uses, not only your terminal. Signalint requires Node.js ≥ 22.12.0. An IDE launched before a runtime update may still inherit an older executable or PATH; restart it after updating. ## project_not_initialized The MCP check resolved a project without `signalint.config.json`: ```sh npx signalint-mcp init ``` Run this from the correct root, then reconnect. `SIGNALINT_ALLOW_UNINITIALIZED=1` is an intentional escape hatch, not the normal setup path. ## The agent checks another repository Call `ping` and inspect `structuredContent.projectRoot`. Compare that path with the repository you expect. - Check for a stale project-level `cwd`. - Remove a fixed `cwd` from a shared user configuration. - Check `SIGNALINT_PROJECT_ROOT` for an old repository path. - Prefer project-scoped setup where the client supports it. See [multiple projects](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) for the safe configuration pattern. ## Windows startup fails or closes immediately For clients launching an npm `.cmd` shim, use a `cmd /c` wrapper: ```json { "command": "cmd", "args": ["/c", "npx", "--no-install", "signalint-mcp"] } ``` Linked packages can also expose a junction path that older builds misidentify. After a project-local installation, bypass the shim with the compiled entry points: ```powershell node .\node_modules\signalint-mcp\dist\src\index.js node .\node_modules\signalint-mcp\dist\src\cli.js doctor ``` The first command starts the MCP server; the second runs doctor. For an MCP client, use `command: "node"` and an **absolute path** to `dist/src/index.js` in `args`. ## An engine errors or times out Read `engines..message` and the top-level `status`. - Install required project-local dependencies and plugins. - Ensure tsc has a root `tsconfig.json`. - Ensure ESLint uses a supported flat configuration. - Install optional Biome before enabling it. - Raise a deadline only if the engine legitimately needs more time. Completed engines' diagnostics are preserved, but a partial check is not clean. See [engines](https://tranqui004.github.io/signalint-site/docs/guides/engines/) and [timeouts](https://tranqui004.github.io/signalint-site/docs/guides/configuration/#execution-timeouts). ## A project check reports too_many_paths A single `paths: ["."]` request can expand to more than 512 candidates and hit the internal resolver limit. Narrow the selected inputs or verify coverage with native tools. Non-JS files count before engine relevance filtering; see [scan limits](https://tranqui004.github.io/signalint-site/docs/reference/limitations/#project-scan-coverage-and-path-limits). ## TypeScript says a config is missing under src For a project/CLI check, a directory first target such as `src` selects `src/tsconfig.json`, not an automatic fallback to the root. Use `.` for the default root workflow or select an appropriate explicit config/source file. File checks use a root runner with a separate nearest-config hash probe; see [engine selection](https://tranqui004.github.io/signalint-site/docs/guides/engines/#typescript-configuration-selection). ## Results appear stale after a change TypeScript cache state now includes a source-file size/mtime fingerprint under the selected config directory, so some unrequested edits invalidate it. It is not a complete dependency graph: same-size/restored-mtime edits, outside/symlinked/skipped sources, and the nested-config/hash mismatch can still be missed. Use a broader check where appropriate and direct native output for uncertain contexts. If a configuration source is outside the tracked hashing behavior, **stop the MCP server**, remove `.signalint/cache.sqlite` and `.signalint/cache/`, then reconnect and check again. Build-mode `.tsbuildinfo` files may exist elsewhere. Preserve `session.jsonl` if you want to retain loop history. Removing the entire `.signalint/` directory clears that history too. See [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/) for the exact limitations. ## get_issue_detail returns stale The requested snapshot/reference is unknown, expired or unmatched. Pin the originating `checkId`: older retained snapshots remain queryable, and 1.2.1 refuses detail requests without a check ID. Do not reuse IDs after switching projects/restarting. Exact/prefix lookup can return multiple issues. `get_live_diagnostics` can enumerate latest internal/buffered issues, not every historical omitted record. See [identity and response limits](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/#reference-identity-limits). ## Antigravity reports a legacy configuration The registry recognizes `~/.gemini/config/mcp_config.json` for user setup and `.agents/mcp_config.json` for project setup. Doctor labels `~/.gemini/antigravity/mcp_config.json` as legacy; that is a Signalint label, not proof that your current editor stopped using it. Confirm the active location in your client's MCP settings before migrating or removing a **Signalint entry**. Do not delete an active entry solely because doctor labels its path legacy, or delete a whole file containing other servers. Use the [Antigravity guide](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/antigravity/) to keep one active entry. ## No engines or no linter available Since 1.1.0 Signalint no longer installs Oxlint/TypeScript as runtime dependencies. Install the engines your config enables in the intended project, then restart so memoized resolution is refreshed. Read `engines` and `status`: all-disabled tasks mean `nothing_checked`, not a clean scan. Doctor now inspects ESLint too, but missing-package warnings are not necessarily blocking and package metadata is not an engine execution test. ## Text diagnostics disappeared Check the active `mcpPayload`/`SIGNALINT_MCP_PAYLOAD` setting. A text-only host cannot expose structured-only diagnostic data; use the default `both` while diagnosing. With compact keys plus structured mode, text can incorrectly say zero despite a positive structured `total`. Read the structured fields or avoid that combination; see [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/). ## database is locked 1.2.1 waits up to 5 seconds for SQLite locks. A longer lock can still fail. Avoid long-lived exclusive locks, stop competing processes safely, and retry after contention ends; this is not permission to delete someone else's active database or server. ## stats shows no measurements Metrics accumulate through MCP checks. Terminal `signalint check` alone does not add MCP session telemetry. Connect a client, perform checks, then run `signalint stats` in the same project. If you are still blocked, include Node version, client, relevant configuration, and engine messages in a [GitHub issue](https://github.com/TranQui004/signalint/issues). Avoid posting private source code or credentials. ## Hook, live-buffer or apply results look inconsistent - Hook approval can miss grouped errors; post-edit uses default rather than configured engines. Inspect JSON and run an explicit required check. - Empty LSP ingestion does not clear old buffered records; a batch snapshot and the accumulated live view are different. Use filtered live queries and manage buffer lifecycle in the integration. - Apply `success` means writes completed, not that verification passed. Inspect `postCheckId`, `delta.error`, introduced diagnostics and the working-tree diff. Discard does not undo applied files. See [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/), [live diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/) and [transaction limits](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/). --- # Snapshots & edit verification Source: https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/ Signalint 1.2.1 retains diagnostic snapshots **in the running MCP server**. A response's `checkId` can identify an earlier retained result—not just the most recent check. ## Baseline → edit → verify 1. Call `check_project` and save its `checkId` along with the engine statuses and checked scope. 2. Make the intended edit using your normal editor or agent. Checking does not generate a patch. 3. Call `after_edit_check` with the changed files and the baseline ID. 4. Inspect the new check's `status`, `engines`, and scope **before** interpreting `delta`. ```json { "files": ["src/pricing.ts"], "baselineCheckId": "baseline-check-id" } ``` `after_edit_check` uses the configured file-check provider. With a retained baseline, its result includes introduced, resolved and unchanged normalized issue arrays, plus `errorsIntroduced`, `errorsResolved`, and `netDelta` (introduced errors minus resolved errors). Warnings appear in the arrays but not those error counters. A scoped file result and a whole-project baseline do not have identical coverage. Issues outside the requested files can appear resolved simply because they were not included in the new result. Re-run the relevant project scope before claiming project-wide resolution. ## Inspect or compare retained checks Call `get_diagnostic_snapshot`: ```json { "checkId": "baseline-check-id" } ``` It returns metadata, source (`project`, `files` or `lsp`), engine statuses, total, clusters, bounded remaining records and optional cache metrics. It **does not return the complete normalized `issues` array**. Use `get_issue_detail` for a known cluster or issue: ```json { "clusterId": "cluster-id", "checkId": "baseline-check-id" } ``` **`checkId` is required by `get_issue_detail` in 1.2.1.** Missing it is an invalid-arguments refusal; there is no implicit latest-result fallback. Keep the ID with its references when checks/ingestion overlap. The old `latestIssues` / `latestCheckId` handler state has been removed. For two already retained results, call `compare_diagnostics`: ```json { "baselineCheckId": "baseline-check-id", "currentCheckId": "current-check-id" } ``` An expired or unknown ID produces a stale result. An `after_edit_check` with a stale baseline still runs the new check and reports a stale delta; it does not resurrect historical state. ## Matching is a heuristic The delta first matches exact issue IDs, then unmatched issues by file, rule, normalized message and severity, choosing the nearest line. Semantic matching does not include engine or column. It tolerates line shifts; it is not a proof that two diagnostics have the same underlying cause. The delta's `nextStep` is advisory. In this edition it can say “Clean check” when an empty current result came from failed/disabled engines. Always inspect engine execution and actual check status independently. ## Retention and identity limits - Default snapshot capacity: **50**. Default TTL: **30 minutes**. - Snapshots are not persisted and do not survive server restart. Expired-reference markers are bounded to 200. - IDs are content-derived, not guaranteed unique per invocation. Saving the same ID replaces that map entry. - Stored snapshots are now **deep-cloned and recursively frozen**. A lookup returns a detached clone: mutating the input or a retrieved copy cannot mutate the stored record. Retrieved copies need not themselves be frozen. This is in-process data isolation, not persistence or a unique-per-run identity guarantee. ### Compact-key caveat With `SIGNALINT_COMPACT=1`, snapshot construction still reads normal-key totals/remaining records. Snapshot metadata can therefore omit the total and lose its flat summary while retaining normalized issues. Closed structured check schemas also omit compact aliases. Use normal keys with snapshots and validating structured clients; see [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/). ## Progress and cancellation When the client supplies `_meta.progressToken`, the check path can emit monotonic progress stages **0, 60, 100** with a 50 ms rate limit. Fast stages can be skipped; these are coarse milestones, not per-engine percentages. MCP cancellation is passed to the native check providers and engine processes. An aborted check is not deliberately converted into a clean check. This wiring does not establish equivalent cancellation/rollback behavior for every new tool, especially apply operations. Source: [`snapshots.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/diagnostics/snapshots.ts), [`delta.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/diagnostics/delta.ts), and the [check handlers](https://github.com/TranQui004/signalint/tree/v1.2.1/src/server/handlers). --- # Verify-after-edit hooks Source: https://tranqui004.github.io/signalint-site/docs/guides/hooks/ Signalint 1.2.1 provides hook event adapters for runtime IDs `claude`, `cursor`, `codex`, and `vscode`. The launcher parses a host event, checks relevant paths, and emits runtime-shaped decision JSON. **These are source-verified emitters, not proof that every installed host recognizes the generated configuration.** Confirm your host's current hook API and active config path. ## Preview first Use the locally installed package's **`signalint` binary**, not an unrelated package named `signalint`: ```sh npx --no-install signalint hooks preview --runtime claude npx --no-install signalint hooks preview --runtime cursor ``` The following project-relative targets are generated: | Runtime ID | Emitted target | Post-edit / stop event names | | --- | --- | --- | | `claude` | `.claude/config.json` | `PostToolUse` / `Stop` | | `cursor` | `.cursor/hooks.json` | `afterFileEdit` / `stop` | | `codex` | `.codex/hooks.json` | `PostToolUse` / `Stop` | | `vscode` | `.vscode/hooks.json` | `postToolUse` / `stop` | Preview writes nothing. Back up existing configuration and inspect the generated command before installing: ```sh npx --no-install signalint hooks install --runtime claude --confirm ``` TTY installation requires confirmation. **Non-TTY installation does not require `--confirm` in this implementation**; do not treat the flag as an enforced permission gate in automation. Merge behavior varies: Cursor and VS Code replace the targeted hook entries while retaining unrelated keys. Claude and Codex use their adapter-specific merge shapes. An existing hook on the same event can be replaced; “merge” does not mean all existing commands are preserved. Malformed config can yield a fresh preview template while actual installation refuses it. ## Run a captured event ```sh cat event.json | npx --no-install signalint hooks run --runtime claude ``` Or pass an event explicitly: ```sh npx --no-install signalint hooks run --runtime claude --payload '{"hook_event_name":"PostToolUse","tool_input":{"file_path":"src/main.ts"}}' ``` Only JavaScript/TypeScript variants and JSON/JSONC files are relevant to the hook path filter. README, lockfiles and other extensions are skipped. Unsafe paths are refused by the hook path normalization layer. The launcher communicates the decision through **JSON**, normally returning exit code **0**. Do not use its process exit code as a lint-success or merge gate. The host must understand and honor the emitted response. ## Post-edit versus stop - **Post-edit:** Calls `checkFilesWithStats` with its default engine selection, not the configured file-check wrapper. It does not consistently honor `signalint.config.json` engine/ignore/timeout settings. With no relevant files it reports a clean approval without running an engine. - **Stop:** Uses the configured project checker. Default stop blocking threshold is priority 1; `--fail-on-priority N` can change the policy threshold. - Post-edit is advisory by default. A host owns follow-up, permissions and stop/retry behavior; Signalint does not start a second autonomous repair loop. ## Important 1.2.1 policy limits The current threshold implementation examines **only returned flat remaining records**, not grouped clusters. A set of priority-1 grouped errors can therefore produce `status: issues_found` with an **approve** action. Likewise, a stop check with all engines disabled can be approved as clean. Engine errors can produce an error decision, but this does not eliminate those coverage gaps. Codex and VS Code stop formatters can request continuation/retry for a non-clean status even when the shared policy action is approve; runtime behavior is not uniform. **Use the explicit CLI check and inspect status/engine coverage for required validation. Do not rely on hooks alone to certify a clean project.** See [CLI exit behavior](https://tranqui004.github.io/signalint-site/docs/guides/cli/) and [known limitations](https://tranqui004.github.io/signalint-site/docs/reference/limitations/). Source: [`src/hooks/`](https://github.com/TranQui004/signalint/tree/v1.2.1/src/hooks). --- # Monorepos & incremental TypeScript Source: https://tranqui004.github.io/signalint-site/docs/guides/monorepos/ Workspace planning remains **off by default**. Signalint 1.2.1 improves discovery and graph handling without turning it into a complete package-manager or compiler model. ```json { "monorepoMode": "auto", "engines": { "oxlint": true, "tsc": true, "biome": false, "eslint": false } } ``` `SIGNALINT_MONOREPO_MODE=off|auto|strict` overrides the config; invalid nonempty values are errors. | Mode | Runtime behavior | | --- | --- | | `off` | Single-project path, no workspace plan | | `auto` | Attempt discovery/planning; missing, malformed or cyclic workspace input can fall back to root checking | | `strict` | Missing/malformed workspace YAML and a detected graph cycle are errors; other root/empty-target fallbacks can still occur | ## YAML parsing and supported patterns Discovery now uses the **`yaml` parser**, not the old small line parser. Inline/block lists, quotes, comments and YAML aliases are parsed before extracting the string `packages` entries. ```yaml packages: - 'packages/*' - 'apps/**' - '!apps/**/test/**' - '!packages/legacy' ``` Common literal paths, `*`, `?`, recursive `**`, and negative exclusions are supported. Matching exclusions apply to candidates and their ancestor directories. Directory walking skips common artifact/state directories such as `node_modules`, `.git`, `dist`, `build`, `.signalint`, `.next`, `.turbo`, and `.cache`. Candidates require a valid named `package.json`; canonical real paths deduplicate discovered directories. This is **not a full package-manager glob implementation**: - Braces and character classes are treated literally, not expanded. - For recursive patterns the walker starts from the prefix before `**`; a wildcard in that prefix, such as `packages/*/**`, can prevent discovery. Prefer literal-prefix recursive forms such as `packages/**`. - Exclusions are collected and applied after includes; do not assume ordered re-inclusion semantics. - Only the pnpm workspace file is discovered here; npm/Yarn workspace fields are not an interchangeable discovery source. - Workspace configuration is trusted input. Exact/parent-relative configured paths are not an OS sandbox; review the selected packages and filesystem. ## Dependency and TypeScript-reference graph The graph combines manifest dependency relationships with TypeScript project references. Dependency sections include dependencies, devDependencies, peerDependencies and optionalDependencies. Explicit `workspace:`, `file:` and `link:` protocols identify local sibling relationships. Plain versions and `*`, `^` or `~` ranges are also considered when a sibling name/version matches the runtime's static heuristic. This reduces false edges from unrelated registry names, but **does not implement full semver, registry or lockfile resolution**. The compatible-major/minor heuristic can conservatively include a sibling whose version would not satisfy npm's exact range rules. Package and reference directories are canonicalized, including directory-vs-`tsconfig.json` references. Reference extraction still strips comments with a small routine followed by `JSON.parse`; trailing commas and arbitrary TypeScript config semantics are not guaranteed. The graph orders dependencies before dependents. Scoped project checks can include transitive dependents. In `auto`, cycles fall back to the root; in `strict`, they throw with blocked package names. The reported cycle-node set is the unprocessed graph remainder, not necessarily a minimal cycle witness. Root-level configuration/lockfile changes or no matching packages can still select root fallback. ## File-check/compiler scope is a separate boundary A `check_files` result is filtered to the supplied files. Its **first requested file** selects the TypeScript hash/fingerprint target and workspace dependency directories, while the default compiler runner still uses the root `.` config. Do not assume a multi-package file list creates an independent compiler invocation per package. Compare project/package scope and returned engine status when coverage matters. A recognized solution-root config with references can select native `--build` behavior. Planning metadata alone does not certify successful typechecking. ## Build info and cache partitioning Normal TypeScript project mode uses `--noEmit`, `--incremental` and an explicit build-info path: ```text .signalint/cache/tsc//tsc.tsbuildinfo ``` The root ID is `root`; nested IDs use a relative-path slug plus an eight-character hash. `SIGNALINT_STATE_DIR` redirects this project-mode storage to `/cache/tsc//`. **Build mode differs:** native/configured `.tsbuildinfo` destinations are not redirected by that project-mode mechanism. Review ignore rules and trusted output settings. The result cache is partitioned by project ID. Supported config/reference traversal, the first recognized root lockfile, a bounded size/mtime source walk and dependency directories contribute to fingerprints. This is not complete tracking of every compiler input or same-size/same-mtime edit. See [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/). Source: [`discovery.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/workspace/discovery.ts), [`graph.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/workspace/graph.ts), [`plan.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/workspace/plan.ts), [`checkFiles.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/checkFiles.ts), and [`tsc.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/engines/tsc.ts). --- # Live & external diagnostics Source: https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/ Signalint 1.2.1 accepts **caller-supplied** LSP-shaped diagnostics. It does not install an editor extension, watch files, subscribe to an LSP server, or launch new Python/Rust/Go tools automatically. ## Ingest an LSP-shaped batch Call `ingest_diagnostics`: ```json { "source": "lsp", "serverName": "example-language-server", "diagnostics": [ { "file": "src/main.ts", "range": { "start": { "line": 0, "character": 2 }, "end": { "line": 0, "character": 7 } }, "severity": 1, "code": "example-rule", "message": "Example diagnostic" } ] } ``` Positions arrive zero-based and become clamped, one-based normalized locations. Severity 1 is an error; other/default severities become warnings. Normalized messages retain at most 120 characters. MCP input requires `file`; the library normalizer additionally understands `file://` URIs. The current handler uses engine `external-lsp` and source kind `lsp`, with optional server name. Passing `source: "vscode"` does **not** automatically change that engine to `vscode` or add a timestamped provenance object. The public provenance helper functions support richer metadata, but the handler does not wire all of them into ingestion. ## Query the live view Call `get_live_diagnostics`: ```json { "files": ["src/main.ts"], "severity": "error" } ``` Both filters are optional. The result merges issues from the newest retained non-LSP snapshot with the buffered external issues and returns clusters, flat remaining records, **and the full merged normalized `issues` array**. This full array is not subject to the check summary's 100-record cap; it can be large. Prefer filters and inspect source attribution. The merged view performs **no cross-source deduplication**. One compiler and one LSP report of the same problem can both appear. ## Buffer versus snapshot - Buffer capacity defaults to **10,000** with oldest-entry eviction. The debounce defaults to **50 ms** for event listeners; it is not an MCP push subscription. - Buffer dedup keys are file, rule, line and column. Engine and message are not part of that key, so a later report can replace an earlier one at the same location. - Batches accumulate. **An empty ingestion batch does not clear old diagnostics for a file.** There is no MCP clear-buffer tool in this edition. An in-process integration can clear/remove entries through the library; restarting the server clears in-memory state. - Each ingestion creates a snapshot of **that batch**, not of the entire accumulated buffer. Its `snapshotId` equals its `checkId`. - The old mutable latest-issue field has been removed. Live queries select the newest retained **non-LSP** snapshot and combine it with the buffer; ingestion does not deliberately become that internal snapshot. Internal records can age out/evict with snapshot retention. Detail always requires the appropriate check ID. ## External-language report adapters Public library exports normalize supplied reports from: | Language | Function | Expected report family | | --- | --- | --- | | Python | `normalizeRuffDiagnostics` | Ruff JSON diagnostics | | Python | `normalizeMypyDiagnostics` | Mypy supplied structured/JSONL diagnostics | | Rust | `normalizeCargoClippyDiagnostics` | Cargo/Clippy JSON message stream | | Go | `normalizeGolangCiLintDiagnostics` | golangci-lint JSON diagnostics | These functions attach `external-lsp` issues with adapter-specific server/provenance metadata. They do not execute the external programs, add engine-selection config keys or create a dedicated MCP tool for each report format. `ingest_diagnostics` expects the LSP-shaped input above, not an arbitrary raw Ruff/Clippy report; callers must convert it or integrate the library directly. The built-in subprocess engines remain **Oxlint, TypeScript, Biome and ESLint**. Native checking still uses JavaScript/TypeScript project markers; a Python/Rust/Go-only directory is not automatically a supported native check project. Source: [`diagnostics/`](https://github.com/TranQui004/signalint/tree/v1.2.1/src/diagnostics), [`ingestDiagnostics.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/handlers/ingestDiagnostics.ts), and [`liveDiagnostics.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/handlers/getLiveDiagnostics.ts). --- # Preview & apply transactions Source: https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/ Signalint 1.2.1 hardens the **explicit source-writing tools** introduced in 1.2.0. Diagnostic checks do not edit source, but `apply_diagnostic_fix` can. The caller supplies complete original and replacement content; Signalint does not generate a fix automatically. ## Prepare a preview Call `preview_diagnostic_fix`: ```json { "patches": [ { "file": "src/count.ts", "originalContent": "export const count: number = '1';\n", "patchedContent": "export const count: number = 1;\n", "description": "Use a numeric initializer" } ] } ``` Preview validates paths, including the deepest existing ancestor of a new path, and stages replacements in memory. The manager computes a SHA-256 staging hash from `originalContent`; an `originalHash` field is **not** an accepted MCP patch argument. No source file is written during preview. The result contains `transactionId`, `filesCount`, `summary`, `status`, and `patchesPreview`. Each preview item reports `file`, optional `description`, `originalLength`, and `patchedLength`—**character lengths, not a unified diff or added/removed line counts**. Review the actual contents separately. Default retention remains **20 previews / 15 minutes**, in memory only. A new file uses empty `originalContent`; these are full-file replacements, not automatically rebased hunks. ## Review and apply with an explicit baseline After the host obtains the appropriate permission, call: ```json { "transactionId": "transaction-id", "confirm": true, "baselineCheckId": "baseline-check-id" } ``` `confirm: false` refuses application. The boolean itself is **not proof of human consent**: a client or model can send it. Use your host's destructive-tool permission controls. The new optional `baselineCheckId` has three distinct outcomes: | Baseline | Behavior before/after source writes | | --- | --- | | Omitted | Apply and post-check; no delta is requested. A successful result includes `message: "No baselineCheckId provided; baseline delta was not requested."` | | Active retained snapshot | Resolve that baseline **before writing**, then compare it with the post-apply snapshot | | Unknown or expired | Refuse **before any writes**, with `isError: true`, `filesModified: []`, and `baseline_check_unknown` or `baseline_snapshot_expired` | A baseline refusal does not consume an otherwise active preview. Establish a fresh baseline and retry before the preview's own TTL expires. No implicit latest-snapshot baseline is selected anymore. Inspect source, scope and engine coverage; a content-derived check ID is still not a unique invocation ID. ## What the hardened apply path does 1. Revalidates every staged path against the project boundary, including symlink/ancestor checks, before writes. 2. Compares the current UTF-8 content and its SHA-256 against the preview's original content/hash. Drift or a missing expected file returns `file_drift_detected`, with no writes in that preflight. 3. Builds an **in-memory journal**, recording whether targets existed and their current original text. 4. Applies files sequentially through adjacent `.signalint-tmp-*` files and rename replacement on the same parent filesystem. 5. On a write failure, attempts reverse-order restoration of already-applied entries and removal of newly created files. It reports `rolled_back` / `transaction_aborted_and_rolled_back` and discards that transaction. 6. On successful writes, consumes the preview and runs the handler's file-check provider. This verification is separate from the write transaction. Statuses remain `success`, `rolled_back`, and `error`. The result includes `transactionId`, `filesModified`, and optional `code`, `message`, `error`, `postCheckId`, or `delta`. ## Limits that still matter - **Not filesystem-atomic across files.** Replacements are sequential; other processes can observe an intermediate multi-file state. Rename fallback for busy/existing destinations can unlink before retry, so do not assume every platform/failure path preserves an indivisible replacement. - **Rollback is best-effort.** Restoration errors are swallowed. `rolled_back` with `filesModified: []` is not an independent proof that disk was fully restored; directories may remain. - **No persistent crash recovery or durability guarantee.** The journal lives in memory. The implementation does not add a persistent journal or an explicit fsync protocol for power loss/process termination. - **Apply-time revalidation is real, not an OS sandbox.** It is a pre-write check, not a lock against an adversarial external process replacing paths/content after preflight. Use a trusted project and filesystem. - **Replacement metadata is not preserved by contract.** New temporary files replace destination inodes; do not assume original executable bits, ownership, hard-link relationships or every filesystem attribute survive. - **Verification does not roll back successful writes.** Newly introduced diagnostics are not an automatic rollback condition. A thrown post-check can still leave `status: success` with `delta.error`; that fallback does not match the ordinary advertised diagnostic-delta shape. - Check cancellation support does not imply transactional cancellation or undo. Keep a version-control diff/backup, review all replacements, and run a final check with working engines and appropriate coverage. Read [security](https://tranqui004.github.io/signalint-site/docs/about/security/) and [known limits](https://tranqui004.github.io/signalint-site/docs/reference/limitations/). ## Discard a preview ```json { "transactionId": "transaction-id" } ``` `discard_diagnostic_fix` returns `{ transactionId, discarded: boolean }` and removes in-memory preparation only. It **does not undo an applied change**. Unknown/already removed previews return `discarded: false`. Source: [`manager.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/transactions/manager.ts), [`paths.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/hooks/paths.ts), [`toolArguments.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/toolArguments.ts), and [`applyDiagnosticFix.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/handlers/applyDiagnosticFix.ts). --- # MCP tools Source: https://tranqui004.github.io/signalint-site/docs/guides/tools/ Signalint exposes **13 tools over stdio MCP**. Checks do not edit source, but can write cache, session and compiler artifacts. **`apply_diagnostic_fix` explicitly writes source files**; configure your host permissions accordingly. Tool annotations are hints, not a filesystem sandbox or proof of human consent. ## All 13 tools Every input object rejects unknown keys. File/path arrays are limited to 512 where the shared path-array schema is used; this is not a bound on every diagnostic or patch array. | Tool | Accepted arguments | Result / use | | --- | --- | --- | | `ping` | `{}` | Connection and canonical project root | | `check_project` | Optional `paths: string[]`, default `["."]` | Clustered project check | | `check_files` | Required `files: string[]` | Configured scoped file check | | `get_issue_detail` | Exactly one of `clusterId` / `issueId`; required `checkId` | Matching normalized records from a retained snapshot | | `get_loop_status` | `{}` | Oscillation and file/rule churn state | | `get_diagnostic_snapshot` | Required `checkId` | Metadata, clusters and bounded remaining records; not full issues | | `compare_diagnostics` | Required `baselineCheckId`, `currentCheckId` | Introduced, resolved and unchanged issues; error counters | | `after_edit_check` | Required `files`; optional `baselineCheckId` | New configured file check, plus optional delta | | `ingest_diagnostics` | Required LSP-shaped `diagnostics`; optional `source`, `serverName` | Batch snapshot and buffered external issues | | `get_live_diagnostics` | Optional `files`, `severity: "error" | "warning"` | Full merged normalized issue array plus clustered summary | | `preview_diagnostic_fix` | Required nonempty `patches` with `file`, `originalContent`, `patchedContent`; optional per-patch `description` | In-memory transaction and character-length summary | | `apply_diagnostic_fix` | Required `transactionId`, `confirm: boolean`; optional `baselineCheckId` | Source writes only when `confirm: true`; post-check and optional delta | | `discard_diagnostic_fix` | Required `transactionId` | `{ transactionId, discarded: boolean }`; not an undo tool | The native subprocess engine set remains Oxlint, TypeScript, Biome and ESLint. External report normalization is not registration of additional native engines. ## Ping Call `ping` with `{}`. Text content is the literal string `pong`, not a JSON string. In `both` and `structured` mode the result also includes: ```json { "pong": true, "projectRoot": "/work/my-project" } ``` Text mode omits that structured root. MCP initialize now advertises the installed package version through `resolveSignalintVersion()`; the old hardcoded `0.4.2` discrepancy is fixed in 1.2.0. ## Project and file checks Call `check_project`: ```json { "paths": ["."] } ``` Or `check_files`: ```json { "files": ["src/main.ts", "src/util.ts"] } ``` The file checker filters displayed issues to those paths. TypeScript can still compile the selected program; filtered-count aliases describe diagnostics removed by the display scope. Input paths are not native engine command-line arguments, and arbitrary priority/filter arguments are rejected. Check results use [schema 1.4](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/). Inspect both `status` and `engines`: `clean`, `issues_found`, `error`, and `stale` have different meanings. All-disabled checks produce `nothing_checked`, not a successful clean native check. Settled native task failures can remain in status/engine data without `isError: true`. Thrown provider/engine failures now become classified tool errors with `isError: true`, `code`, `engine`, `message`, `retryable` and `nextStep`; these are a different response path. ### Project root and state By default the server uses its launch working directory. `SIGNALINT_PROJECT_ROOT` can select a canonical root; an invalid override logs a warning and falls back to the launch directory. Embedders can pass an explicit `cwd` option. Verify the effective root with `ping`. The project must have recognized JS/TS ecosystem markers. Without `signalint.config.json`, MCP checks are refused unless `SIGNALINT_ALLOW_UNINITIALIZED=1`. Even with that override, a recognized project marker is still needed. Native engine configuration remains trusted executable context, not an OS sandbox. Cache/session/project-mode TypeScript state defaults beneath `.signalint/`. `SIGNALINT_STATE_DIR` redirects supported state paths; do not share one override directory indiscriminately between unrelated projects. ## Issue detail is snapshot-aware Call `get_issue_detail` with a known ID and, when possible, its originating check: ```json { "clusterId": "cluster-id", "checkId": "check-id" } ``` **`checkId` is mandatory in 1.2.1; missing it is an invalid-arguments refusal, not latest-result lookup.** An issue reference can be a full hash or returned short prefix. Multiple matching records are possible because issue IDs omit engine and column. Unknown, expired or unmatched references are stale; an older **retained** snapshot remains queryable. In `both` and `structured`, successful detail mirrors the `{ "issues": [...] }` wrapper in the first JSON text block and structured object. In `text`, it remains a serialized array. `structured` adds a second user-facing summary block. Normalized messages remain bounded—not the original unabridged engine messages. ## Snapshots, delta and live ingestion - [Snapshots & edit verification](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/): retention, semantic matching, scope and coverage limits. - [Live & external diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/): LSP input shape, accumulated buffer behavior and the full live array. - `get_live_diagnostics` can enumerate latest internal/buffered issue IDs even when the check summary omitted flat records. It is not pagination of every historical snapshot. ## Source-writing tools Read [preview & apply transactions](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/) before enabling apply. Preview supplies lengths rather than a unified diff. Apply performs path revalidation and SHA-256/content drift preflight, then **sequential adjacent temp-file replacements with in-process best-effort rollback**. It is not an all-files atomic commit or persistent crash journal. An optional supplied baseline is validated before writes; unknown/expired baselines refuse without consuming an active preview. Without a baseline, no delta is requested. Failed post-verification does not undo successful writes. `apply_diagnostic_fix` advertises `readOnlyHint: false`, `destructiveHint: true`, and `idempotentHint: false`. Preview is non-idempotent in-memory preparation; discard mutates in-memory state. Other read-only annotations do not eliminate check artifacts or ingestion's buffer mutation. ## Payload modes - `both` (default): minified JSON text plus structured data, with the literal-pong exception; successful detail is wrapped consistently in both channels. - `text`: text only; advertised output schemas are omitted. - `structured`: the same JSON text fallback and structured object, plus a second user-facing summary. It is larger than `both` on the audited fixtures. See [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) for measured wire costs and compact-key limitations. Schema 1.4 describes native check summaries; it is not a version field present on every tool result. ## Errors and cancellation Unknown tool names and non-object argument containers throw JSON-RPC **`-32602`**. Missing fields, unknown keys and path refusals are handled as tool-result refusals (`isError: true`) where caught by the server. This distinction is narrower than “all invalid arguments are protocol errors.” Normal engine fan-out can preserve successful peers and return `status: error`; inspect `engines` as well as `isError`. Thrown check-provider failures are classified as `engine_timeout` (retryable), `output_limit_exceeded`, or `engine_execution_failed`, with actionable next steps. The engine-failure helper sanitizes wire messages with path/env/stack patterns; stderr still logs the original detail, and input/transaction errors are not all processed by that helper. No universal secret-redaction guarantee is implied. Check-request cancellation propagates to native providers/processes; it does not imply every tool, transaction write or rollback has equivalent cancellation semantics. Source: [`tools.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/tools.ts), [`toolArguments.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/toolArguments.ts), and [`createServer.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/createServer.ts). ## Schema-validation scope Inputs are parsed by strict Zod objects; output schemas are advertised in both/structured modes and exercised by upstream Ajv tests and validating clients. The server's low-level handlers do not add a per-tool Ajv validation gate for every return. Compact aliases and thrown post-apply verification remain known schema gaps; test the specific client/path rather than treating the release prose as a universal runtime guarantee. --- # Response schema Source: https://tranqui004.github.io/signalint-site/docs/reference/response-schema/ Signalint **1.2.1** normally emits **`schemaVersion: "1.4"`** for project and file checks. Its validator also accepts historical 1.3 data; that does not mean current results keep the old shape. Early refusals and other tools have separate contracts. [MCP payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) determine how the data is transported, independently of compact keys. ## Normal check response ```ts type BuiltinEngine = "oxlint" | "tsc" | "biome" | "eslint"; type IssueEngine = BuiltinEngine | "external-lsp" | "vscode"; interface CheckResponse { schemaVersion: "1.4"; status: "clean" | "issues_found" | "error"; projectRoot: string; engines: Partial>; totalIssues: number; clusters: Cluster[]; remainingIssues: RemainingIssue[]; omittedIssueCount: number; filteredOutIssueCount?: number; filteredOutCount?: number; nextStep?: string; truncated: boolean; loopWarning?: LoopWarning; fileRuleChurnWarning?: FileRuleChurnWarning; checkId?: string; code?: string; message?: string; } interface EngineStatus { status: "ok" | "error" | "disabled"; message?: string; } ``` This describes normal non-compact runtime output, not every shape accepted by the broader validator or output schema. | Field | Meaning | | --- | --- | | `status` | Engine failures take precedence; no successful task yields `error` / `nothing_checked` | | `projectRoot` | Canonical absolute root in normal full checks; omitted by compact check output | | `engines` | Present outcomes; disabled entries without messages can be omitted | | `totalIssues` | Collected diagnostic count after provider filtering, before response caps | | `clusters` | Up to 10 ranked groups with at least 2 issues each | | `remainingIssues` | Up to 100 flat records: single-rule occurrences and demoted overflow-group issues | | `omittedIssueCount` | Number of flat records omitted by the 100-entry cap; normally `0` when none | | `filteredOutIssueCount`, `filteredOutCount` | Equal aliases emitted when a file check excludes diagnostics outside requested paths | | `nextStep` | Guidance when flat records are omitted or file-scope filtering hides other diagnostics | | `truncated` | Flat records exceeded 100—not a repository coverage certificate or simply an overflow-cluster flag | | `checkId` | An 8-hex checksum of selected diagnostic fields, not a unique run ID | | Warning fields | Objects only when active; inactive warnings are omitted, not emitted as `null` | An `error` check can still include useful diagnostics from completed engines. `ok` can mean cached results or no relevant files; disabled/missing entries do not imply successful coverage. ## Clusters and flat records ```ts interface Cluster { clusterId: string; rootCauseSummary: string; ruleIds: string[]; issueCount: number; fileCount: number; priority: number; suggestedAction?: string; sampleIssueIds?: string[]; } interface RemainingIssue { issueId: string; file: string; line: number; col: number; rule: string; severity: "error" | "warning"; fixable: boolean; } ``` Groups of **2 or more** same-rule issues become clusters. The systemic threshold still requires **more than 3 issues across more than 2 files**. Non-systemic groups of 2–3 issues can therefore be clusters too. Single issues and overflow groups go flat; see [clustering](https://tranqui004.github.io/signalint-site/docs/reference/priority-ladder/). Clusters with **more than 2 issues** include up to 2 distinct sample IDs. Two-issue clusters omit `sampleIssueIds`; use `clusterId` for detail. `suggestedAction` appears only when all group issues are marked fixable. Its wording is not a returned patch or proof of a safe edit. Flat records deliberately omit `engine` and `message`. Use their emitted issue IDs to retrieve matching normalized detail. ## Compact keys are a separate option `signalint check --compact` or `SIGNALINT_COMPACT=1` changes check keys: | Normal | Compact | | --- | --- | | `schemaVersion` | `v` | | `totalIssues` | `total` | | `remainingIssues` | `remaining` | | `omittedIssueCount` | `omitted` | | Both filtered-count aliases | `filteredOut` | Compact checks **omit `projectRoot`**. `clusters`, `status`, `engines`, `checkId`, and active warning names stay the same. An environment-enabled compact mode also affects MCP checks, but not every early error or other tool. Prefer the default full shape unless your consumer supports both. ```json { "v": "1.4", "status": "issues_found", "engines": { "tsc": { "status": "ok" } }, "total": 1, "clusters": [], "remaining": [ { "issueId": "b2dbbc348dd5", "file": "src/file01.ts", "line": 3, "col": 14, "rule": "TS2322", "severity": "error", "fixable": false } ], "omitted": 0, "truncated": false, "checkId": "7e3c2bd2" } ``` This is an illustrative shape, not a captured result. `mcpPayload: "text"` does **not** enable compact keys. ## Normalized issue detail ```ts interface NormalizedIssue { issueId: string; file: string; line: number; col: number; engine: IssueEngine; rule: string; severity: "error" | "warning"; message: string; fixable: boolean; clusterId?: string; serverName?: string; sourceKind?: "compiler" | "linter" | "lsp" | "editor"; provenance?: { kind: "compiler" | "linter" | "lsp" | "editor"; engine: IssueEngine; serverName?: string; timestamp: number; }; } ``` Messages are collapsed to one line and capped at approximately **120 characters**. Detail retains full issue hashes but does not restore original unnormalized messages or edit patches. Successful detail uses `{ "issues": [...] }` in both/structured channels with matching first JSON text. In text-only mode it is an array. `get_issue_detail` requires a check ID. ## Reference identity limits - Full issue IDs hash file, rule, line, and quoted-value-normalized message. They omit engine and column; **duplicate issue IDs** remain possible for distinct diagnostics. - Summary IDs use **12-character prefixes**, extended when distinct full hashes share that prefix in the same check. Equal full hashes are not made distinct by prefix extension. - Detail lookup first tries exact IDs, then returns all prefix matches. Use emitted prefixes rather than guessing shorter ones; a manually shortened prefix can match multiple diagnostics. - Same-rule multi-issue groups now share one cluster. The old 1.0 behavior that could make a local cluster stale after a same-ID assignment collision is not carried forward as an unresolved guarantee-breaking case. - Cluster IDs hash rule/severity/systemic scope and can be reused across changed results; they are not permanent global identities. - `checkId` hashes engine, file, line, column, rule, severity, and message. It omits `fixable`, project root, and invocation time. It is not a unique invocation ID, security token, or complete freshness check. In 1.2.1, **required** `checkId` selects a retained snapshot (default 50 / 30 minutes). Missing it is an argument refusal; the latest-result fallback has been removed. A later check no longer inherently invalidates an older retained snapshot. Unknown/expired IDs or unmatched references can produce: ```json { "status": "stale", "message": "This cluster/issue no longer exists; run check_project again." } ``` ## Caps do not provide pagination All diagnostics passed into a check snapshot are retained in its normalized issue array, but omitted flat records do not expose their IDs or paths in the summary. Demoted overflow groups do not receive returned cluster IDs. `get_issue_detail` needs a known reference: it is **not an enumeration or pagination API** for every omitted item. `get_live_diagnostics` can enumerate the full newest non-LSP-snapshot plus buffered normalized array, including currently omitted IDs. It is not pagination of every retained historical snapshot. `get_diagnostic_snapshot` returns bounded summaries and metadata, not its full issue array. If you cannot identify an older omitted issue, narrow `check_files` to known paths, make a suitably scoped project check, or run native engines. A `nextStep` hint does not by itself make every hidden diagnostic discoverable. The caps bound record counts, not a fixed serialized-byte ceiling; long rules, paths, messages, and detail arrays can change payload size. ## Tool-specific results in 1.2.1 Snapshot metadata, delta, live ingestion and transaction results have separate shapes, not a new check-schema number. Optional provenance fields are accepted by the normalized issue schema; they are not populated identically by every handler. Read [MCP tools](https://tranqui004.github.io/signalint-site/docs/guides/tools/), [snapshots](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/) and [live diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/). ## Early refusals Check the shape before parsing a tool result as a normal check: ```json { "status": "error", "code": "project_not_initialized", "message": "Run 'npx signalint-mcp init' in /path/to/project before checking it.", "projectRoot": "/path/to/project" } ``` Source: [`schema.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/schema.ts), [`clusterEngine.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/cluster/clusterEngine.ts), and [`toolSchemas.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/toolSchemas.ts). ## Classified check-provider failures A thrown provider/engine failure can return a different error object, with `isError: true` on the MCP result: ```json { "status": "error", "code": "engine_timeout", "engine": "tsc", "message": "TypeScript timed out", "retryable": true, "nextStep": "Increase the engine timeout in signalint.config.json or check for hanging processes." } ``` This is illustrative, not captured output. `output_limit_exceeded` and `engine_execution_failed` are non-retryable classifications. Normal settled engine outcomes can still produce a full diagnostic check with `status: error`, rather than this thrown-provider shape. Inspect both shape and engine coverage. Strict advertised schemas do not make every raw return valid: compact check aliases and post-apply `delta.error` need the documented combination caveats. The server parses inputs with Zod; upstream/client Ajv validation is not proof of a universal server-side output validation gate. --- # Clustering & priorities Source: https://tranqui004.github.io/signalint-site/docs/reference/priority-ladder/ Signalint groups diagnostics by normalized rule string, including across engines and severities. This is a deterministic heuristic—not semantic root-cause analysis or proof that one edit fixes every grouped diagnostic. ## Cluster membership in schema 1.4 - **2 or more issues with the same rule:** one cluster, even when the group is local. - **More than 3 issues across more than 2 files:** a systemic cluster. - **One occurrence:** a flat `remainingIssues` record, not a one-issue cluster. A summary such as `21 TS2322 issues across 11 files` reports repetition and scope. It does not identify the dependency or edit responsible for it. ## Priority ladder **1 is most urgent; 5 is least urgent.** For each group, any error makes it an error group; fixability requires every issue to be marked fixable. | Priority | Condition | Suggested reading order | | --- | --- | --- | | **1** | Systemic group containing an error | Investigate repeated errors across files | | **2** | Non-systemic error, not all issues fixable | Inspect the error details | | **3** | Non-systemic error, all issues fixable | Review the diagnostics; no patch is supplied | | **4** | Non-systemic warning, not all issues fixable | Review local warnings | | **5** | Systemic warning or all-fixable warning | Review lower-priority warnings | Systemic errors stay priority 1 even when every issue is marked fixable. Systemic warnings map to priority 5. ## Ranking and overflow Clusters are sorted by priority ascending, issue count descending, rule name, then the first issue's deterministic ordering. The default response keeps the top **10 clusters**. Single issues and issues from demoted overflow groups are merged into a flat list. That list is sorted by each issue's **individual** priority, then file, line, column, and ID, and capped at **100 records**. A demoted systemic group's records are no longer represented as a priority-1 cluster in that list. `omittedIssueCount` counts flat records beyond 100. `truncated: true` means this flat cap was exceeded. More than 10 groups alone need not set `truncated` if their overflow records fit the flat list. ## Request details deliberately Clusters with more than 2 issues include up to **2 distinct short sample IDs**; two-issue clusters omit samples. Call `get_issue_detail` with a returned cluster ID or an emitted flat/sample issue ID. `fixable` and `suggestedAction` are hints, not edit patches or safety guarantees. Prefixes distinguish distinct hashes within a check, not all real-world diagnostics. See [reference identity limits](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/#reference-identity-limits). Omitted flat IDs are not automatically exposed. `get_live_diagnostics` returns the full latest internal/buffered array, but there is no historical snapshot pagination. Use that filtered live view, narrower known paths or native engines when the summary cannot identify what you need. ## CLI failure thresholds ```sh npx --no-install signalint check . --fail-on-priority 3 ``` In 1.2.1, this examines **returned clusters and returned flat records**. Flat records have their priority calculated from severity/fixability if no priority is present. Omitted records and engine errors do not independently force a thresholded failure. Inspect `status` and engines as well; see [CLI thresholds](https://tranqui004.github.io/signalint-site/docs/guides/cli/#failure-threshold). Source: [`clusterEngine.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/cluster/clusterEngine.ts) and [`cli.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/cli.ts). --- # Caching Source: https://tranqui004.github.io/signalint-site/docs/reference/caching/ Signalint stores diagnostic cache state in **`.signalint/cache.sqlite`** using Node's built-in `node:sqlite`. Both check tools consult this cache. Neither is an unconditional force-refresh command. ## Requested-file engines Oxlint, Biome, and ESLint reuse matching requested-file snapshots. Misses are submitted as a batch and split back into per-file results. The implemented key contains: ```text SHA-256(file content) : engine : hash of recognized native config files : Signalint version : engine version resolved for the cache lookup ``` It is based on contents, not Git status or mtime. Importantly, **the file path is not part of this key**. ### Same-content, different-path limitation Two files with identical contents can share a cache entry. Cached diagnostics are relocated to the requested filename, but path-dependent rules or overrides are not re-evaluated on a hit. This can reuse diagnostics from the wrong file context. If you depend on path-specific rules, use a direct native-engine run to verify results. Clearing the cache forces misses for the next run but does not eliminate the key collision from subsequent reuse. ### Formatter-mode and version limits - `engines.biome.includeFormatter` is not hashed. Changing it can reuse results from the old mode; stop the server and clear the diagnostic cache after a change. - Engine resolution and version lookup are process-memoized. Restart after upgrading packages. - Cache-version lookup defaults to `process.cwd()`, while an engine can run against another root selected with `SIGNALINT_PROJECT_ROOT` or the server API. These roots can resolve different engine versions. Prefer launching in the intended root and clear cache after engine changes in an overridden-root setup. Do not infer that every setting, plugin, or dependency change invalidates an entry. ## TypeScript stays whole-program The latest TypeScript result is stored separately. In 1.2.1 its state key combines a per-project cache partition and recognized config hash with a **source-program fingerprint**: a sorted list of JS/TS-family files under the selected config directory (or project root), each file's path, size, and `mtimeMs`, plus explicit workspace dependency directories when supplied. File content itself is not hashed by this fingerprint. Requested relevant snapshots still use content keys as well. This improves 1.0's omitted-dependency behavior: an unrequested source edit that changes size/mtime inside the fingerprint walk can force a rerun. It is **not TypeScript's dependency graph**. The walk skips `node_modules`, `.git`, `.signalint`, and `dist`, does not follow directory symlinks or interpret compiler include/exclude, and can miss sources outside its selected directory. A same-size edit with restored mtime can leave its fingerprint unchanged. The TypeScript runner remains whole-program, but the `check_files` provider now filters displayed diagnostics to requested paths, adding filtered-count aliases when needed. Cached program state is not a promise that all external/imported/configured inputs were tracked. There is no force-refresh tool flag; use native tools when the fingerprint boundary is insufficient. ### TypeScript selection and inspection limits The project-check runner selects a config from its first target; see [selection rules](https://tranqui004.github.io/signalint-site/docs/guides/engines/#typescript-configuration-selection). The default `check_files` runner still compiles the root `.` config, while its cache-hash/fingerprint probe uses the first requested file and can select a nearer config. Those can differ: a root config edit may not invalidate a result hashed against a nested config. New workspace dependency-directory fingerprinting does not remove this mismatch. The topological project-check path separately supplies a package-specific TypeScript runner. The process-lifetime `tsc --showConfig` cache keys only the selected root file's contents, not the full inheritance chain, path, or engine version. Restart and clear cached diagnostics after configuration changes whose coverage is uncertain. ## Recognized configuration hashing | Engine | Sources hashed by Signalint | | --- | --- | | Oxlint | Root `.oxlintrc`, `.oxlintrc.json`, `oxlint.json` | | Biome | Root `biome.json`, `biome.jsonc` | | ESLint | Root `eslint.config.js`, `.mjs`, `.cjs`, `.ts`, `.mts`, `.cts` | | TypeScript | Config selected by the hash probe; bounded `extends` / `references` traversal; first recognized root lockfile | These are **hashing inputs**, not a guarantee that every native engine accepts all of those names or that all configurations it loads are tracked. For parseable JSON TypeScript configs, the walker follows its supported `extends` / `references` paths up to **50 unique paths**. It uses `JSON.parse`, not the TypeScript configuration resolver. JSONC inheritance, package-based extends, nested linter configuration, dynamic imports, and plugin/dependency changes can be missed. The selected file's own contents are hashed even if chain discovery stops. TypeScript hashing also incorporates the first readable root lockfile among `pnpm-lock.yaml`, `package-lock.json`, and `yarn.lock`; it is not an exhaustive dependency resolver. ## SQLite contention The cache sets **`PRAGMA busy_timeout = 5000`** so a competing connection can wait for a lock instead of immediately failing. This does not guarantee every concurrent run succeeds: a lock can outlast the wait, and cache waiting is not a cross-process transaction for engine execution or session JSONL writes. ## Storage and observed reuse - File-result rows: **10,000** by default, with LRU eviction. Identical-content paths may share a row; this is not a count of unique checked filenames. - Normal TypeScript project-mode state: `.signalint/cache/tsc//tsc.tsbuildinfo`. - TypeScript build-mode state: native/configured `.tsbuildinfo` locations, not necessarily `.signalint/`. - Session history and metrics: `.signalint/session.jsonl`, separate from diagnostic caching. After MCP checks have accumulated: ```sh npx --no-install signalint stats ``` Its hit rate describes engine-file lookups in completed tasks. It does not report database disk usage, all project files, or total retained cache rows. See [CLI statistics](https://tranqui004.github.io/signalint-site/docs/guides/cli/#stats). ### Redirect supported state `SIGNALINT_STATE_DIR` redirects the SQLite cache, session log/statistics and normal project-mode TypeScript build info. Prefer an absolute per-project directory; sharing one destination can mix state. Snapshots, buffered live diagnostics and prepared transactions are separate in-memory stores. This override does not redirect every native build-mode artifact. ## Clear cached results deliberately Stop the server before removing `.signalint/cache.sqlite` and `.signalint/cache/` (or corresponding state-override paths), then reconnect. This does not remove build-mode `.tsbuildinfo` files elsewhere; locate those from the selected TypeScript configs if a complete compiler-state reset is needed. Preserve `session.jsonl` and its backup to keep retained history. Deleting all of `.signalint/` removes that history too. No cache reset substitutes for verification with native tools when a key limitation applies. Source: [`src/checkFiles.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/checkFiles.ts), [`src/cache/sqliteCache.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/cache/sqliteCache.ts), and [`src/engines/tsc.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/engines/tsc.ts). --- # Loop & churn detection Source: https://tranqui004.github.io/signalint-site/docs/reference/loop-detection/ Signalint records diagnostic history across MCP checks. It can reveal a recurring issue pattern, but it does not detect arbitrary conversation loops or know whether an agent's strategy is correct. ## Diagnostic oscillation A signature combines the **rule** and a normalized **message template**. Quoted text, certain identifier patterns, numeric details, whitespace, and case are normalized; the signature is not tied to a specific file or line. Signalint counts appearances separated by absence. A loop warning is emitted after a signature has disappeared and reappeared **at least twice**: ```text Check 1 Check 2 Check 3 Check 4 Check 5 present → absent → present → absent → present warning ``` The warning is structured: ```json { "signature": "TS2322:type is not assignable to type .", "occurrences": 2, "hint": "This issue was fixed and reappeared 2 times — consider a different approach" } ``` `occurrences` counts reappearances after the initial appearance. Once a signature has crossed the threshold, it remains in the retained historical loop state; a clean check does not erase that historical count. ## File-rule churn A separate counter tracks the **same `(file, rule)` pair** across `check_files` calls, even if the message or line changes. It warns at **3 or more consecutive matching file-check results**: ```json { "file": "src/auth.ts", "rule": "TS2345", "checkCount": 3, "hint": "src/auth.ts has re-triggered TS2345 across 3 separate checks — the agent may be stuck on this file, not just this exact issue" } ``` | Event | Churn behavior | | --- | --- | | Pair appears in a `check_files` result | Increment once for that call | | Pair is absent from a `check_files` result | Reset its counter to zero | | `check_project` is called | Does not increment or reset the counter in the live process | Absence from a targeted result can reflect a narrower request, not necessarily a verified project-wide fix. Treat these signals as prompts to investigate, not proofs of causality. ## Reading warnings Normal check responses include `loopWarning` or `fileRuleChurnWarning` only when active. In schema 1.4 inactive warnings are omitted, not emitted as `null`. For the complete flagged lists, call `get_loop_status` with `{}`: ```json { "looping": false, "signatures": [], "fileChurning": false, "fileRuleChurns": [] } ``` `looping` and `fileChurning` are independent. A file can churn without an exact signature disappearing and returning. ## Persistence and reset Session history is written under `.signalint/session.jsonl`. On restart, Signalint replays a bounded tail of valid entries and skips malformed or crash-truncated lines. The tail reader retains at most 5,000 non-empty log lines (also bounded by bytes), then skips invalid entries; fewer valid checks may be restored. The `.1` backup is used by `stats`, not replayed by `SessionMemory`. The active log rotates before an append would exceed the 10 MiB budget; this is a rotation trigger, not a guarantee that one oversized entry fits. Replay is not identical to live churn handling in v1: a project check logs an empty pair array and can reset previously active churn counters when that history is replayed. Historical warnings also depend on the retained tail, not unlimited lifetime history. Stop the server before deliberately removing session history. Clearing only the SQLite cache does not clear recorded loop history. Implementation: [`src/memory/sessionMemory.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/memory/sessionMemory.ts). --- # Architecture Source: https://tranqui004.github.io/signalint-site/docs/reference/architecture/ Signalint is a **local stdio MCP server**. Engine adapters produce normalized diagnostics; the cache, clustering, and session layers transform or retain that data. There is no hosted API between your project and the MCP client. ## The pipeline ### Read the diagram 1. The MCP client supplies tool arguments over stdio. 2. Strict input validation resolves canonical paths and refuses project escapes. 3. File snapshots, optional workspace planning and configured engine selection determine the work. 4. Matching cached results are reused; engine adapters check file-local misses or the configured TypeScript program. These paths converge into normalized diagnostics. 5. File checks filter displayed diagnostics to the requested paths; repeated rules are grouped and ranked. 6. The server stores deep-cloned/frozen check snapshots for required-ID detail and delta; persistent session state records events and detects recurrence/churn. 7. Check results use schema 1.4 with clusters and flat remaining records, then return to the client through `both`, `text`, or `structured` mode. ## Responsibilities | Layer | Source modules | Responsibility | | --- | --- | --- | | Entry points | `src/index.ts`, `src/cli.ts` | Start the server or dispatch CLI commands | | MCP server | `src/server/` | Register tools, validate arguments, route handlers, retain diagnostic snapshots, live buffers and prepared transactions | | Project boundary | `src/projectPaths.ts` | Canonicalize requested paths and refuse escapes | | Engine execution | `src/engines/`, `src/subprocess.ts`, `src/engineFanout.ts` | Resolve tools, normalize output, enforce process limits, preserve independent outcomes | | Incremental cache | `src/checkFiles.ts`, `src/cache/sqliteCache.ts` | Snapshot inputs and reuse matching diagnostics | | Clustering | `src/cluster/clusterEngine.ts` | Group repeated rules, rank clusters, produce bounded summaries | | Retained diagnostics | `src/diagnostics/` | In-memory snapshots/delta, LSP buffer and supplied external report normalizers | | Workspace planning | `src/workspace/` | Opt-in pnpm discovery and dependency/reference planning | | Hook adapters | `src/hooks/` | Host event parsing, verification policy and emitted configuration | | Explicit transactions | `src/transactions/` | Caller-provided preview, path/hash preflight, optional explicit baseline, adjacent replacements and in-memory rollback journal | | Session state | `src/memory/` | Record history, detect recurrence, rotate logs, aggregate metrics | Adapters are the modules that invoke diagnostic engines. Higher layers work with `NormalizedIssue` objects instead of shell output. ## File-local and whole-program work Oxlint, Biome, and ESLint are treated as file-local engines. Matching snapshots are reused; misses are submitted in batches. TypeScript stays whole-program. Requested content snapshots and a source-file metadata fingerprint control invalidation, but the compiler receives its configured program. File-check results are then filtered to requested paths. The first project target selects a TypeScript config; file checks use a root runner with a separate hash probe. Normal configs use project mode; configs reporting references use build mode. See [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/) for the fingerprint and configuration-selection boundaries. ## Concurrency and failure isolation Enabled engine tasks run concurrently and settle independently. Normal engine-task failures preserve fulfilled tasks; config-loading or snapshot I/O failures can occur before that fan-out. The response marks each engine `ok`, `disabled`, or `error`; any error makes the top-level status `error`. Subprocesses have deadlines, output budgets, and cancellation handling. Shutdown attempts process-tree termination before closing cache handles and the transport; termination failures are logged. SQLite cache lock contention waits up to 5 seconds; this is not a guarantee that longer locks succeed. Session-memory updates are serialized through a promise queue within each process. This ordering is distinct from engine execution and does not make every project operation an operating-system transaction. ## State on disk ```text .signalint/ ├── cache.sqlite # Diagnostic cache ├── cache/tsc// # Normal project-mode TypeScript build info ├── session.jsonl # Check history and metrics └── session.jsonl.1 # Rotated history backup, when present ``` `SIGNALINT_STATE_DIR` redirects supported cache/session/project-mode state. TypeScript build mode can write `.tsbuildinfo` files elsewhere at native/configured locations. Snapshots (50 / 30 minutes), the live buffer (10,000 entries), and prepared transactions (20 / 15 minutes) are in-memory stores, not this disk tree. The cache defaults to 10,000 file-result rows with LRU eviction; identical-content paths can share a key. Session replay and log rotation are bounded. These are local artifacts, not source files; keep them out of Git. ## Additional paths and 1.2.1 hardening `ingest_diagnostics` accepts supplied LSP-shaped batches; `get_live_diagnostics` merges them with the newest retained non-LSP snapshot and returns a full normalized array, not a 100-issue-bounded check. External-language adapter exports normalize reports but do not launch additional engines. `after_edit_check` can compare a new scoped check to a retained baseline. Delta is heuristic and coverage-sensitive. Hook event adapters use a separate policy path with documented cluster/configuration gaps. `preview_diagnostic_fix` stages full-file patches; only explicit `apply_diagnostic_fix` writes them. Apply preflight revalidates paths/content hashes and an optional supplied baseline before sequential adjacent replacements. The rollback journal is in memory, not an all-files atomic transaction or persistent crash recovery. Without an explicit baseline no delta is requested; post-verification failure still does not undo writes. See [transactions](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/). ## Trust boundaries Tool arguments are untrusted even when supplied by an authorized agent. Native check paths are checked lexically and after symlink/junction resolution. Apply also performs containment preflight in 1.2.1; neither path is a lock against adversarial external filesystem changes. That boundary does not sandbox engine plugins, imports, or trusted configurations. Child engines run with your account's permissions. See [security](https://tranqui004.github.io/signalint-site/docs/about/security/). ## Read the implementation - [Source tree](https://github.com/TranQui004/signalint/tree/v1.2.1/src) - [Upstream architecture document](https://github.com/TranQui004/signalint/blob/v1.2.1/ARCHITECTURE.md) - [MCP tool reference](https://tranqui004.github.io/signalint-site/docs/guides/tools/) - [Response schema](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/) ### Stateless detail and mirrored JSON Handlers no longer retain mutable `latestIssues`/`latestCheckId` state. `get_issue_detail` requires a check ID; the live view explicitly selects a retained non-LSP snapshot. Retrieved snapshot copies do not mutate stored data, but content-derived IDs can still repeat. Both/structured modes mirror the structured object in the first JSON text block (except literal-pong). Structured mode adds a user-facing summary rather than removing diagnostic JSON. Thrown check-provider failures use classified tool errors; ordinary settled engine failures preserve per-engine diagnostic outcomes. --- # Known limitations Source: https://tranqui004.github.io/signalint-site/docs/reference/limitations/ These are verified boundaries of **Signalint 1.2.1**, not a roadmap or a certificate that every native configuration or editor version works. ## Native JS/TS versus supplied external diagnostics The native MCP check safety gate requires recognized JS/TS ecosystem markers. The four registered subprocess engines are Oxlint, TypeScript, Biome and ESLint. Oxlint, TypeScript and Biome are optional peers, not guaranteed bundled engines. Biome can additionally contribute JSON/JSONC, CSS and GraphQL diagnostics in that ecosystem. Caller-supplied LSP reports and Python/Rust/Go report normalizers are new, but **do not launch additional language toolchains**. There is no built-in editor subscription/extension or watcher. See [live diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/). ## Explicit patches, not automatic repairs or a sandbox Native checks do not request automatic fix/write modes. `fixable` is inferred metadata, not a patch or proof a change is safe. The new preview tool accepts caller-provided full-file content; apply explicitly writes source with `confirm: true`. Apply now revalidates paths and original-content SHA-256, then uses **sequential adjacent temp-file replacements** with an in-memory journal. It is not filesystem-atomic across files or persistent crash recovery. Rollback remains best-effort and post-check failure does not revert successful writes. A supplied unknown/expired baseline is refused before writes; omitting the baseline requests no delta. The boolean does not prove human approval. Review [transaction limits](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/) and [security](https://tranqui004.github.io/signalint-site/docs/about/security/) before enabling it. Plugins, native config and compiler artifacts operate with the user's permissions. The tool-argument boundary is not an OS sandbox. ## Project scan coverage and path limits - Input path arrays are limited to **512 paths**. Oversized MCP arrays fail validation. - Expanded snapshots use the same 512-path resolver: `["."]` can fail with `too_many_paths` after discovering more than 512 candidates. Non-JS candidates count before relevance filtering. - The walker skips `node_modules`, `.git`, `.signalint` and `dist` independently of ignore settings. Unreadable directories can be skipped. - Its 2,000-candidate directory-entry guard is not a complete-walk guarantee or a strict universal file cap. `truncated` describes summary overflow, not repository coverage. Use narrower explicit scopes and native tools when the walk cannot provide required coverage. An all-disabled native check reports `nothing_checked`; a check with failed engines is not clean just because it has no issue records. ## Workspace planning is opt-in and narrow `monorepoMode` defaults to `off`. Auto/strict discovery now uses the `yaml` parser and common `*`, `?`, `**`/negative-exclusion patterns. It is not a complete package-manager glob or semver implementation: brace/classes, wildcard prefixes before recursive `**`, ordered re-inclusion and full dependency resolution have limits. Strict rejects missing/malformed YAML and graph cycles; other root/empty-scope fallbacks remain. Project planning can include transitive dependents. `check_files` uses its **first file** for the TypeScript hash/fingerprint probe and workspace dependency directories, while the default compiler runner still uses the root config; it does not independently typecheck every package represented in a multi-package list. See [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/). Normal TypeScript project mode defaults `--skipLibCheck` when omitted and partitions build info under `.signalint/cache/tsc//`. Native build mode can write elsewhere. Do not assume equivalence with an arbitrary bare compiler command. ## Cache reuse can miss context Requested-file keys omit filename and `includeFormatter`. Identical-content files under different path-based rules can share results; formatter-mode changes can preserve old behavior. The TypeScript metadata fingerprint does not hash source contents or implement the full compiler dependency graph. Same-size/restored-mtime edits, unsupported config inheritance, skipped/symlinked sources and external inputs can leave stale state. Project-ID partitioning, bounded config/reference hashing, root lockfile hashing and explicit dependency-directory fingerprints improve tracking; they do not prove there are no false hits. There is no force-refresh tool flag. See [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/). ## Bounded checks versus full live arrays Native check summaries emit at most **10 multi-issue clusters and 100 flat records**. Only clusters with more than 2 issues include up to 2 distinct sample IDs. Overflow groups are demoted; omitted flat IDs are not listed by that summary. Record caps do not enforce a constant byte size. `get_live_diagnostics` returns the full newest retained non-LSP-snapshot plus buffered normalized array, so it can enumerate currently omitted IDs. **It is not historical snapshot pagination and is not bounded to 100 issues.** `get_diagnostic_snapshot` returns metadata and bounded summaries, not its full normalized array. Detail requires a known issue/cluster reference. Clustering groups rule strings, including across engines. Repetition is not semantic proof of a common cause or one-edit resolution. ## Snapshot identity and delta are not absolute guarantees Snapshots default to **50 / 30 minutes**, in memory only. Content-derived `checkId` can repeat and overwrite an entry. Stored snapshots are deep-cloned/frozen; lookups return detached copies, not necessarily frozen objects. **Detail requires `checkId`**; latest-result lookup has been removed. Issue IDs omit engine and column. Exact/prefix lookup can return multiple matches; prefix extension distinguishes different hashes, not equal full IDs. Detail returns bounded **normalized** messages, not original compiler output. Delta matches exact IDs, then file/rule/normalized-message/severity with nearest-line pairing. Error counters exclude warnings and do not establish engine coverage. A narrow/failed current check can make unobserved baseline errors look resolved; “Clean check” advice is not a safety certificate. See [snapshot verification](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/). ## Live-buffer lifecycle Batches accumulate; **empty ingestion does not clear old file diagnostics**. Buffer dedup ignores engine and message, and the merged internal/external view has no cross-source dedup. Batch snapshots and accumulated live state are different views. MCP has no clear-buffer tool; richer timestamped provenance is not wired into every ingestion result. ## Hooks are not a clean-project gate The threshold policy inspects **flat remaining records only**, not clusters. Grouped priority-1 errors can yield an approve action. All-disabled stop checks and no-relevant-file post-edit calls can report clean without useful engine coverage. Post-edit bypasses the configured file-check wrapper. Host emitters/decision formats vary; install may replace targeted existing hooks, and non-TTY install does not enforce `--confirm`. The launcher communicates decisions in JSON and normally exits 0. See [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/). ## Transport, state and concurrency Text mode omits structured data/schemas. In both/structured, the first JSON text mirrors the structured object; structured adds a summary block and is not the lower-byte mode. Compact mode's second summary can incorrectly say zero because it reads `totalIssues`, not `total`. Compact snapshot construction can also omit normal-key totals/remaining records, and advertised structured check schemas do not declare compact aliases. Prefer normal keys for those integrations. See [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/). SQLite waits up to 5 seconds for locks, not indefinitely. Session updates are serialized per process, not across every agent. `SIGNALINT_STATE_DIR` redirects supported state; use separate destinations where project isolation matters. Check cancellation and coarse token-based progress are not equivalent guarantees for apply or every new tool. MCP initialize version now follows the package; the old `0.4.2` metadata mismatch is fixed. ## Setup, historical warnings and CLI thresholds - `init` automatically updates `.gitignore` when needed. Its JSON merger does not parse arbitrary JSONC and replaces the selected Signalint entry. Back up client settings. - Doctor can swallow config parse errors; `PASSED` does not prove all config, engines or an MCP handshake work. - Registry and hook paths verify emitted configuration, not every external client version/platform. - Oscillation/churn history is bounded and scope-sensitive. Narrow absence is not proof of repair; replay can reset churn differently from live project checks. - Thresholded CLI exits inspect returned clusters and flat records, not omitted records. An engine error without a qualifying diagnostic can exit 0. Inspect JSON status or retain native validation commands. See [CLI](https://tranqui004.github.io/signalint-site/docs/guides/cli/) and [loop detection](https://tranqui004.github.io/signalint-site/docs/reference/loop-detection/). ## Hardened is not universally validated Thrown check-provider failures now carry classified `code`, `engine`, `message`, `retryable` and `nextStep` with `isError: true`; normal settled engine failures still use diagnostic status data. The wire-message sanitizer is pattern-based, stderr logs original failures, and not every input/transaction error uses it. Do not treat it as universal secret redaction. Inputs are Zod-validated and outputs advertise schemas. Upstream Ajv tests/validating clients do not imply every server return is Ajv-validated; compact aliases and thrown post-apply `delta.error` remain shape gaps. Per-file replacement also does not guarantee preservation of inode/permission/ownership metadata or every rename-fallback failure path. Runtime evidence outranks the upstream risk register's broad “closed” labels. --- # Benchmarks & payload costs Source: https://tranqui004.github.io/signalint-site/docs/reference/benchmark/ A repeated-rule fixture is useful for measuring compression, not for promising an average result. This page separates **observed source-audit results**, historical upstream numbers, serialized MCP results, and actual response-line framing. ## Repeated-rule fixture: source-audit rerun On **October 13, 2026**, Signalint **1.2.1**, Linux / Node **22.22.3**, Oxlint **1.86.0**, and TypeScript **7.0.2** returned **52 issues across 11 files as 2 clusters**: | Representation | Observed bytes | Serialization | | --- | ---: | --- | | Native engine output | **7,370** | Combined Oxlint agent output and plain tsc output | | Normalized issue array | **19,103** | `JSON.stringify(issues, null, 2)` | | Clustered check response | **965** | `JSON.stringify(response, null, 2)` | The pretty JSON response was **86.9% smaller than native output** and **94.9% smaller than normalized JSON**. These use different denominators. Absolute `projectRoot`, versions, fixture contents, and serialization change byte counts. This is the source script's **standalone representation comparison**, not an MCP result or a complete JSON-RPC message, and not a token, speed, or repair-outcome measurement. ### Historical numbers are not the current rerun The earlier 1.0.0 README comparison recorded **1,477 B** (80.0% / 92.3%); the October 7 audit observed **1,440 B** (80.5% / 92.5%). The latest README still contains the 1,477-byte legacy table alongside a differently serialized hero example. Those figures are not relabeled as measurements of the current 1.2.1 runtime. ## Three MCP modes on four fixtures The source-bound website harness was rerun against the exact 1.2.1 source. It asserted the server-version handshake, actual issue totals, successful Oxlint/tsc outcomes, zero omitted flat records, JSON text/structured parity and equality with captured stdout response lines. It did not modify the runtime. | Fixture | Issues | Minified check JSON | `both` result JSON | `text` result JSON | `structured` result JSON | | --- | ---: | ---: | ---: | ---: | ---: | | `sparse` | 1 | 386 B | 894 B | 487 B | 970 B | | `systemic-ts` | 12 | 470 B | 1,064 B | 573 B | 1,142 B | | `mixed-app` | 11 | 1,181 B | 2,594 B | 1,392 B | 2,672 B | | `scale-app` | 60 | 3,086 B | 6,718 B | 3,611 B | 6,796 B | Here **result JSON means `JSON.stringify(CallToolResult)`**, not the entire JSON-RPC response. `text` reduced those serialized results by **45.5%, 46.1%, 46.3%, and 46.2%** relative to `both` on these runs. See [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/) before changing a client's transport. **1.2.1 serialization change:** `structured` now sends the same JSON text fallback and structured object as `both`, plus a user-facing summary block. Its measured result/line sizes are therefore larger than `both` on these fixtures. The old 1.2.0 summary-only figures are historical, not the current transport. ### Captured response lines | Fixture | `both` JSON-RPC line | `text` JSON-RPC line | `structured` JSON-RPC line | | --- | ---: | ---: | ---: | | `sparse` | 929 B | 522 B | 1,005 B | | `systemic-ts` | 1,099 B | 608 B | 1,177 B | | `mixed-app` | 2,629 B | 1,427 B | 2,707 B | | `scale-app` | 6,753 B | 3,646 B | 6,831 B | These count the captured **single tool-response JSON-RPC line including its newline**. They exclude the request, initialization, tools/list exchange, and other session traffic. The 35-byte framing difference observed here is not a constant for every request ID or transport. ### Why upstream recorded figures differ The pinned upstream `docs/benchmarks.md` still labels its measurements as 1.1.2 and records `both` result sizes of **984 / 1,154 / 2,684 / 6,808 B** for the same named fixtures. This audit observed the smaller values above. Absolute root text and JSON escaping are environment-dependent; no single cause is asserted for the whole difference. The source script labels `JSON.stringify(result)` as a full envelope, but does not include JSON-RPC framing in that number. It also prints hardcoded fixture labels and native-output comparison percentages rather than measuring fresh raw CLI baselines in that script. This site does not adopt those labels, break-even estimates, or percentages as independently verified facts. Its clean-checkout token estimates divide bytes by roughly 3.8 unless an optional tokenizer is available. Such estimates are not token counts for your model; this site reports bytes instead. ## Count bounds are not a byte ceiling The runtime returns at most **10 multi-issue clusters and 100 flat remaining records**. It retains check snapshots in server memory; `get_live_diagnostics` can enumerate latest internal/buffered IDs, but there is no historical snapshot pagination. Detail retrieval needs a known reference and can return a larger array. Long paths, rules, and detail messages also vary in size. Do not infer a fixed ~21.8 KB maximum, guaranteed reachability of every hidden issue, or a universal 10–15-issue break-even point from the upstream prose. Use scoped checks or native engines when response limits prevent discovery. See [schema limits](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/#caps-do-not-provide-pagination). ## Reproduce From the audited source checkout: ```sh pnpm install --frozen-lockfile pnpm build pnpm bench node scripts/measure-mcp-payload-mode.mjs ``` The measurement fixtures use engines resolvable beside the source checkout; they do not demonstrate that a fresh package installation bundles engines. Current Signalint requires you to provide the selected toolchain; see [installation](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/). Source: [`bench.mjs`](https://github.com/TranQui004/signalint/blob/v1.2.1/scripts/bench.mjs), the [audited measurement script](https://github.com/TranQui004/signalint/blob/v1.2.1/scripts/measure-mcp-payload-mode.mjs), and [latest recorded measurements](https://github.com/TranQui004/signalint/blob/v1.2.1/docs/benchmarks.md). ## Your own session metrics ```sh npx --no-install signalint stats --json ``` Session stats compare minified normalized issues with the check response before transport formatting. They do not meter `both` duplication, captured wire traffic, CLI-check history, or model tokens. See [CLI statistics](https://tranqui004.github.io/signalint-site/docs/guides/cli/#stats). --- # FAQ Source: https://tranqui004.github.io/signalint-site/docs/reference/faq/ ## Is Signalint another linter? No. It invokes registered diagnostic engines and normalizes, groups, ranks, caches, and tracks their output. Native engine rules and compiler configuration still determine diagnostics. ## Does it fix files? Checks request no automatic source-fix mode. **Explicit `apply_diagnostic_fix` can write caller-provided full-file replacements after preview and `confirm: true`.** It does not generate patches automatically, guarantee filesystem-atomic writes, or undo successful writes after failed verification; see [transaction limits](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/). `fixable` is metadata, not an edit patch or a safe-edit guarantee. Cache/session writes and TypeScript build-info artifacts still occur; plugins/configuration run with your permissions. ## Does local mean all my data stays private? Signalint has local stdio diagnostic transport and no hosted Signalint API or API key. Your MCP client, model, and engine plugins have their own data/network behavior. Local execution is not a blanket privacy promise or sandbox. See [security](https://tranqui004.github.io/signalint-site/docs/about/security/). ## Which languages can it check? The project safety gate and fixed engine set target JS/TS projects. Biome can additionally contribute JSON/JSONC, CSS, and GraphQL diagnostics within that ecosystem. New library adapters normalize supplied Ruff/Mypy, Cargo/Clippy and golangci-lint reports, and MCP accepts LSP-shaped batches. It does not launch Python/Rust/Go toolchains, arbitrary engines, a watcher or a vulnerability scanner. See [external diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/). ## Are Oxlint, TypeScript, or Biome bundled? Since **1.1.0**, all three are optional peers, not required runtime dependencies. The 1.2.1 declarations are Oxlint **1.86.0**, TypeScript **7.0.2**, and Biome **2.5.15**. Install the chosen toolchain in your project. ESLint is also project-local and is not shipped as a runtime dependency. The resolver can still fall back to an engine actually available beside Signalint, as in the source checkout; that is not a fresh-install guarantee. If an enabled engine cannot be resolved when needed, its outcome is disabled. No successful engine means `nothing_checked`, not `clean`. ## Which executable should I use? Use `signalint` for explicit CLI work. Since 1.1.1 `signalint-mcp` also recognizes CLI verbs such as doctor/check/stats, while a normal non-TTY client launch starts stdio. A bare interactive server launch prints guidance and exits `2` rather than hanging. ## Does it always save 80% of tokens? No. Current audit measurements compare bytes, with separate standalone, MCP result, and captured response-line baselines. Repeated rules can compress well; one-off unrelated issues can have higher overhead. Neither an upstream historical percentage nor a byte/token estimate proves your model's token cost or repair success. See [benchmarks](https://tranqui004.github.io/signalint-site/docs/reference/benchmark/). ## Is text mode the same as compact mode? No. MCP `text` mode omits structured content and output-schema advertisement. Compact mode changes check keys and omits their root. `structured` now mirrors diagnostic JSON in its first text block and adds a second summary; it is not the lower-byte mode. That second summary can still be misleading with compact keys. Validating host behavior needs testing. Read [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/). ## Does check_files type-check only those files? No. TypeScript still runs its configured program, then the file provider filters displayed diagnostics to requested paths. Filtered-count aliases warn about other diagnostics. The metadata fingerprint improves invalidation for some unrequested edits, not every dependency/configuration context. See [caching](https://tranqui004.github.io/signalint-site/docs/reference/caching/). ## Can I retrieve every omitted issue with get_issue_detail? Detail needs a known reference. Omitted flat IDs are not listed by the check summary, but `get_live_diagnostics` can enumerate latest internal/buffered records. It is not historical pagination. Older retained checks remain addressable by explicit `checkId` until eviction/expiry; the snapshot metadata tool does not return its complete issue array. Use narrower checks/native diagnostics when needed. ## Can I use one global MCP entry for every repository? Be deliberate: a fixed project root can point diagnostics at the wrong project. Registry targets are generated configuration, not proof of every editor version's active path/schema. Use [client setup](https://tranqui004.github.io/signalint-site/docs/getting-started/clients/) and [multiple-project safety](https://tranqui004.github.io/signalint-site/docs/getting-started/multiple-projects/) to verify the actual root. ## Does doctor PASSED prove the setup is correct? No. It now inspects all four engine package records and payload mode, but can swallow config parse errors and issue non-blocking missing-engine warnings. It does not prove native engines ran, a client handshake succeeded, or every editor setting is valid. ## Does SQLite waiting guarantee concurrent agents always succeed? No. The cache waits up to **5 seconds** for locks. Longer contention can still fail, and this does not globally serialize session logging or every project operation. ## Where should I start when something is wrong? Read [troubleshooting](https://tranqui004.github.io/signalint-site/docs/guides/troubleshooting/), run doctor from the intended repository, and inspect a real check's status/outcomes. Include sanitized configuration, package/Node versions, and engine messages in an upstream issue only when appropriate; do not post secrets or private source. ## Do hooks or a green delta prove the project is clean? No. Hook thresholds currently miss clustered errors and can approve all-disabled checks. Delta advice ignores engine coverage and can treat unobserved out-of-scope issues as resolved. Inspect status, engines and scope, then run explicit required validation. See [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/) and [snapshots & delta](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/). --- # MCP payload modes Source: https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/ Signalint 1.2.1 supports three **MCP tool-result transport modes**; **`both`** is the default. The patch release changes serialization: `structured` is no longer a summary-only text channel. ## Modes at a glance | Mode | `content[0].text` for checks/detail | Second content block | `structuredContent` | Advertised tool `outputSchema` | | --- | --- | --- | --- | --- | | **`both`** — default | Minified JSON equal to `JSON.stringify(structuredContent)` | None | Complete object; detail arrays wrapped in `{ issues: [...] }` | Present | | **`text`** | Minified original payload; successful detail is a JSON array | None | Omitted | Omitted for all 13 tools | | **`structured`** | Same minified JSON fallback as `both` | Human summary with `annotations.audience: ["user"]` | Complete object | Present | **Ping remains an exception:** its first text is literal `pong` in every mode. `text` ping omits the structured project root; the other modes also return `{ pong: true, projectRoot }`. Do not try to JSON-parse ping's first block. Checks/detail in `both` and `structured` duplicate the data across text and structured channels. The latter adds a summary block as well. On the audited 1.2.1 fixtures, **`structured` costs more wire bytes than `both`**, not less. Select a mode for the host's behavior, not an assumed universal size ranking. ## Configure the mode In the checked project's `signalint.config.json`: ```json { "mcpPayload": "both" } ``` Or set the server environment: ```sh SIGNALINT_MCP_PAYLOAD=text npx --no-install signalint-mcp ``` This launches a **stdio server**, not a terminal diagnostic command. Configure the environment through your client and reconnect; Windows uses its own environment syntax. Precedence remains **non-empty environment → server option/config → default `both`**. Environment whitespace is trimmed, names are case-sensitive, and invalid nonempty values/config fields fail parsing. The active server mode is captured at creation. `signalint doctor` inspects the resolvable configuration, not an existing server handshake or full editor integration. ## Choose based on your client - Start with **`both`** while verifying the root and client result handling. - Use **`text`** after confirming the host forwards the JSON diagnostics to the model. It has no structured result or advertised output schema; recheck the root through stderr or an appropriate full result. - Use **`structured`** when its structured object and additional user-facing summary are useful. Text-consuming clients now receive diagnostic JSON in the first block, but host-version compatibility is not independently certified by this site. Closed advertised output schemas and upstream Ajv tests are not a promise that every raw server response is runtime-Ajv-validated. The server parses arguments with Zod; validating clients can reject nonconforming result shapes. In particular, review the compact and post-apply exception paths before assuming universal output-schema coverage. ## Compact keys are a separate setting `SIGNALINT_COMPACT=1` / CLI `--compact` abbreviate check keys (`v`, `total`, `remaining`, `omitted`, `filteredOut`) and omit `projectRoot`. They are not payload modes. The 1.2.1 human-summary helper still reads `totalIssues`, not compact `total`. A compact positive check can therefore produce **`0 issues found.` in the second structured-mode summary block**. The first JSON block and structured object still contain the compact fields. Do not use that human sentence as the count authority. Retained-check construction still reads normal-key totals/remaining records; compact snapshot metadata can lose those fields while normalized issues remain retained. Closed structured check schemas do not declare compact aliases. Prefer normal keys for snapshot/validating structured integrations; compact CLI/text support does not certify every handler combination. ## Measure the actual object The [benchmark reference](https://tranqui004.github.io/signalint-site/docs/reference/benchmark/) separates standalone representations, serialized `CallToolResult` values, and captured JSON-RPC response lines. | Fixture | `both` result | `text` result | `structured` result | | --- | ---: | ---: | ---: | | `sparse` | 894 B | 487 B | 970 B | | `systemic-ts` | 1,064 B | 573 B | 1,142 B | | `mixed-app` | 2,594 B | 1,392 B | 2,672 B | | `scale-app` | 6,718 B | 3,611 B | 6,796 B | These are observed 1.2.1 `JSON.stringify(CallToolResult)` bytes, not complete session traffic, model tokens or latency. Absolute project-root text and fixture/serialization choices affect them. Only `text` reduced these fixture results relative to `both`; a small/weakly grouped result can still exceed native output. Source: [`config.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/config.ts), [`errors.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/errors.ts), [`tools.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/tools.ts), and [`ping.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/server/handlers/ping.ts). --- # Changelog Source: https://tranqui004.github.io/signalint-site/docs/about/changelog/ These docs describe published **1.2.1 / schema 1.4**. The [upstream changelog](https://github.com/TranQui004/signalint/blob/v1.2.1/CHANGELOG.md) has the full release narrative; this summary is scoped to verified runtime behavior, not every broad promise in those notes. ## 1.2.1 — October 11, 2026 The tool count remains **13**, native check schema **1.4**, and Node floor **22.12.0**. This patch changes contracts that clients should review: - **Explicit detail lookup:** `get_issue_detail` requires `checkId`; no mutable latest-issue/check fields in handler context. Stored snapshots are deep-cloned/frozen and retrieved copies are detached. Check IDs are still content-derived, not unique invocations. - **JSON fallback:** both/structured first text mirrors the structured object, including the issue-detail wrapper. Structured mode adds a second user-facing summary and is larger than both on the audited fixtures. Literal-pong remains an exception; text-only detail stays an array. - **Classified thrown failures:** engine/provider errors carry actionable classification and sanitized wire messages. Ordinary settled engine failures, other refusal paths and local stderr retain their separate behavior. Do not infer universal secret redaction or per-return server-side Ajv validation. - **Workspace hardening:** real YAML parsing, common recursive/include/negative patterns, canonical directories/references and better local dependency edges. Strict rejects graph cycles; the remaining glob/semver/root-compiler limits are documented. - **Safer explicit apply:** path/hash preflight, adjacent temp-file replacement and in-memory reverse rollback journal. Optional explicit baseline is validated before writes; unknown/expired baselines refuse without consuming active preparation. Omitted baseline requests no delta. - **Release checks:** upstream action SHAs are pinned, metadata/tarball checks precede publishing, and the development lockfile upgrades Vitest for advisories. The independent production/development audits reported zero known advisories on October 13. Multi-file atomicity, persistent crash recovery, complete compiler/glob semantics and every schema-failure path are not certified. Read the current guides/limits rather than copying broad upstream closure labels. ## 1.2.0 — October 10, 2026 The following is historical release context; 1.2.1's detail/serialization/storage/apply semantics above supersede the original behavior. The native check schema remains **1.4**; the MCP catalog expands from **5 to 13 tools**. - Package-derived MCP initialize version fixes the old hardcoded metadata. Unknown tools/non-object argument containers now throw protocol `-32602`; other caught input/path refusals remain tool results. - `SIGNALINT_STATE_DIR` redirects supported cache/session/normal TypeScript build-info paths. - Adds retained snapshots, `get_diagnostic_snapshot`, `compare_diagnostics` and `after_edit_check`, with coarse check progress and native cancellation propagation. - Adds hook preview/install/run and four runtime event emitters; host compatibility and policy gaps remain explicit. - Adds opt-in pnpm workspace discovery, dependency/reference planning, per-project TypeScript partitions and supplied dependency-directory fingerprints. - Adds caller-supplied LSP ingestion/live queries and library report normalizers for Ruff, Mypy, Cargo/Clippy and golangci-lint. No new external-language subprocess engines or editor subscription is launched. - Adds caller-provided full-file preview, explicit apply and discard. **Apply writes source**, uses sequential writes/best-effort rollback, and does not undo successful writes after failed verification. These capabilities are not blanket guarantees of immutable snapshots, complete workspace discovery, clean-project hooks or filesystem-atomic apply. Independent probes found coverage/identity/buffer/transaction limits. Read [snapshots](https://tranqui004.github.io/signalint-site/docs/guides/snapshots-and-delta/), [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/), [monorepos](https://tranqui004.github.io/signalint-site/docs/guides/monorepos/), [live diagnostics](https://tranqui004.github.io/signalint-site/docs/guides/live-diagnostics/), [transactions](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/) and [known limitations](https://tranqui004.github.io/signalint-site/docs/reference/limitations/). ## 1.1.2 — October 8, 2026 - Sets SQLite **`busy_timeout = 5000`** to wait for lock contention. This reduces immediate lock failures; it does not guarantee success when a lock lasts longer than the wait. - Isolates cache fixtures and adds cleanup retries/test timeout changes for Windows testing. ## 1.1.1 — October 8, 2026 - Dispatches recognized CLI verbs from `signalint-mcp`, including doctor, check, stats, help, and version. - A bare interactive TTY launch prints actionable guidance and exits `2` instead of starting a waiting stdio server. - Normal non-TTY MCP startup remains supported. Use [CLI reference](https://tranqui004.github.io/signalint-site/docs/guides/cli/) for exact command behavior. ## 1.1.0 — October 8, 2026 ### Schema 1.4 and bounded output - Groups of 2+ same-rule issues become clusters; single occurrences and overflow groups use flat `remainingIssues`. - Returns up to **10 clusters and 100 flat records**, with `omittedIssueCount`, `truncated`, and guidance when needed. This is not pagination or a fixed-byte guarantee. - Uses **12-character summary issue prefixes**, extended for different full hashes sharing a prefix. Full detail hashes remain supported; equal full hashes and prefix ambiguity are still documented. - Minifies normal MCP JSON text, omits inactive warning fields, omits message-less disabled engine entries where possible, and trims unnecessary cluster fields. - Adds optional compact check keys (`--compact`, `SIGNALINT_COMPACT=1`), which also omit the check root. ### Scoped file results and TypeScript cache - `check_files` now filters returned diagnostics to requested paths, with filtered-count aliases and project-check guidance. - Adds a sorted source-file size/mtime fingerprint to TypeScript cache state. This catches some unrequested source changes, not every compiler dependency/configuration or same-size/restored-mtime edit. ### MCP modes and package setup - Adds **`both`**, **`text`**, and **`structured`** payload modes, selected by environment/config. Host compatibility must be verified; structured-mode summaries and compact keys have a current combination caveat. - Reclassifies **Oxlint and TypeScript as optional peers**, so a default install no longer supplies those toolchains. Biome remains optional; ESLint is project-local. - Adds missing-tool guidance to init/doctor. Doctor now inspects ESLint and reports payload mode, but still does not fully validate configuration or a client connection. - Adds `stats --json`, benchmark-mode tooling. Read [installation](https://tranqui004.github.io/signalint-site/docs/getting-started/installation/), [schema](https://tranqui004.github.io/signalint-site/docs/reference/response-schema/), [payload modes](https://tranqui004.github.io/signalint-site/docs/reference/payload-modes/), and [limitations](https://tranqui004.github.io/signalint-site/docs/reference/limitations/) before upgrading a 1.0 consumer. ## 1.0.0 — October 6, 2026 First stable release. Requires Node **>=22.12**, uses built-in SQLite, adds flat-config ESLint, introduces schema **1.3** with project-root/status/checksum context, and adds the seven-client setup registry. Structured loop/churn warnings were retained from the 0.4 line, not invented in this release. The 1.0 edition had required Oxlint/TypeScript runtime dependencies and different cluster-cap behavior. Its historical audit is not presented as current 1.2.1 semantics. ## Earlier 0.4 releases - **0.4.2:** bounded TypeScript config-chain hashing and process-lifetime `--showConfig` inspection cache. - **0.4.1:** shared MCP tool annotations. - **0.4.0:** independent file-rule churn detection and schema 1.2 warning fields. ## Source and releases - [Published v1.2.1 source](https://github.com/TranQui004/signalint/tree/v1.2.1) - [GitHub releases](https://github.com/TranQui004/signalint/releases) - [npm package](https://www.npmjs.com/package/signalint-mcp) The audited main and dereferenced annotated `v1.2.1` tag both identify `6327008dbfdfa7112254dd89e330c7df97090575`. Registry SHA512/SHA1 were verified and all **75 published runtime JavaScript files** byte-match this source build. Upstream tests: **340 passed / 47 files**; those counts do not replace independent runtime probes or establish universal guarantees. --- # Contributing Source: https://tranqui004.github.io/signalint-site/docs/about/contributing/ Signalint and this documentation site are maintained in separate repositories. Open a focused contribution in the repository that owns the change: - [Signalint](https://github.com/TranQui004/signalint) — MCP server, CLI, engines, tests. - [signalint-site](https://github.com/TranQui004/signalint-site) — website, docs, search, and design. ## Develop the MCP project Use **Node.js ≥ 22.12.0** and **pnpm 11.9.0**. The current runtime depends on built-in SQLite; older Node 20 setup advice does not apply to v1. ```sh git clone https://github.com/TranQui004/signalint.git cd signalint pnpm install --frozen-lockfile ``` Run the available verification commands: ```sh pnpm lint pnpm typecheck pnpm test pnpm build ``` Read [`AGENTS.md`](https://github.com/TranQui004/signalint/blob/v1.2.1/AGENTS.md) for coding and test expectations. The project's CI checks the supported Windows, Ubuntu, and macOS environments; inspect the current workflow for authoritative requirements. ## Improve this website ```sh git clone https://github.com/TranQui004/signalint-site.git cd signalint-site pnpm install --frozen-lockfile pnpm dev ``` Before opening a website PR: ```sh pnpm check pnpm test:content pnpm llms pnpm build ``` The build validates authored documentation links and generates the Pagefind search index. Preview the **built** site to test search; search is not available in Astro development mode. For interactive and accessibility checks, install the Playwright Chromium browser and run `pnpm test:e2e` after building. See the website README for details. ## Keep technical docs accurate When runtime behavior and a narrative document disagree, inspect the current implementation and tests before changing a public claim. In particular: - Distinguish MCP tool payloads from CLI output. - Treat clustering as a rule-based heuristic, not proven semantic causality. - Label fixture metrics and their baselines explicitly. - Document the limits of cache invalidation and local trust boundaries. - Regenerate `llms.txt` and `llms-full.txt` after content changes. ## Pull requests Keep a PR scoped to one coherent change and explain how it was verified. Use conventional subjects such as `fix(cache): ...`, `docs(clients): ...`, or `feat(site): ...`. Do not bypass protected-branch checks. Release and deployment actions require maintainer approval; a local preview is not a release. For MCP publication, follow the [upstream release checklist](https://github.com/TranQui004/signalint/blob/main/RELEASE.md) rather than publishing from a developer machine. ## Patch-release verification The 1.2.1 source pins upstream CI/action dependencies to immutable SHAs and verifies package/server/action metadata and the packed artifact before publication. The reviewed development lockfile also updates Vitest to address advisory findings. Follow the living upstream release checklist; these website docs do not authorize publishing a package from this checkout or bypassing the gates. --- # Security Source: https://tranqui004.github.io/signalint-site/docs/about/security/ ## Report a vulnerability privately Do not post exploit details in a public issue. Use [GitHub Security Advisories](https://github.com/TranQui004/signalint/security/advisories/new) for private reporting to the Signalint project. ## Native check arguments are untrusted An authorized coding agent can be prompt-injected by repository content or another tool. The native check path validates strict argument objects and resolves selected paths before starting engines: - Unknown keys, wrong types and invalid detail-reference combinations are refused. - Path arrays and expanded project snapshots are limited to **512 paths**. - Absolute, NUL-containing, empty, leading-dash, missing and escaping check paths are refused. - Canonicalization checks containment after symlink/junction resolution. - Engine processes have bounded output, deadlines, cancellation and best-effort process-tree termination. These controls do not establish identical permissions or side effects for all 13 tools. ## Source-writing permission and 1.2.1 hardening `preview_diagnostic_fix` stages caller-supplied full-file replacements in memory. **`apply_diagnostic_fix` writes source** and advertises a destructive tool hint. Hosts should require appropriate user permission and show the exact replacement content. `confirm: true` is a request field, not a human-consent enforcement mechanism. Preview and apply preflight now both validate containment, including symlink/deepest-existing-ancestor checks. Apply also validates original text/hash. A supplied unknown/expired baseline is refused before writes. These checks improve the boundary but do not lock paths/content against an adversarial external filesystem race or create an OS sandbox. Writes are sequential adjacent temp-file replacements with an in-memory journal; rollback is best-effort and there is no persistent crash-recovery/fsync contract. Replacement metadata and rename-fallback failure behavior need review. A failed post-check does not revert successful writes. Keep a reviewed version-control diff/backup and verify the full project after applying. Read [transaction behavior](https://tranqui004.github.io/signalint-site/docs/guides/fix-transactions/). ## Local execution is not an OS sandbox Signalint and its engines run with the launching user's permissions. Engine-native config can resolve imports, TypeScript extends, plugins and packages using normal filesystem behavior, including outside the project root. Plugins can have their own side effects/network access. **Only execute projects and configurations you trust with your account's permissions.** Signalint is not a SAST scanner, an OS permission boundary, or proof a repository is secure. Tool annotations and a green-looking hook/delta do not substitute for those controls. ## State and setup writes Cache and session data default beneath `.signalint/`; `SIGNALINT_STATE_DIR` redirects supported cache/session/project-mode TypeScript paths and can point outside the project. Use absolute, deliberately chosen per-project destinations and protect diagnostic data appropriately. Normal TypeScript project mode writes partitioned build info; native build mode can use configured destinations elsewhere. `init` creates missing Signalint configuration, updates `.gitignore`, and can update selected client settings. Hook installation also writes host config, can replace targeted hook entries, and does not enforce `--confirm` in non-TTY mode. Snapshots, LSP buffers and prepared transactions live in server memory. Their retention is bounded; stored snapshots are deep-cloned/frozen and retrieved copies are detached. Content-derived check IDs still are not unique invocation identities. See [architecture](https://tranqui004.github.io/signalint-site/docs/reference/architecture/) and [hooks](https://tranqui004.github.io/signalint-site/docs/guides/hooks/). ## Stdio, not a hosted service The server uses `StdioServerTransport`, not an HTTP listener or static file service. It needs no Signalint cloud API or API key. Your MCP client's forwarding of diagnostics to a model and your plugins' data handling remain separate trust decisions. ## Dependency audit context The current source lockfile resolves **`@hono/node-server@2.1.3`** on the SDK dependency path. This is outside the affected `<2.0.5` range for **GHSA-frvp-7c67-39w9**, a Windows static-file middleware advisory. The reviewed stdio server does not instantiate that vulnerable HTTP/static-serving path. The October 13, 2026 independent production **and development** lockfile audit runs reported **zero known advisories** against the pinned upstream lockfile. That result is time- and dependency-scoped; it does not prove the transaction or hook logic is free of correctness/security issues. Source: [pinned security policy](https://github.com/TranQui004/signalint/blob/v1.2.1/SECURITY.md), [`projectPaths.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/projectPaths.ts), and [`transactions/manager.ts`](https://github.com/TranQui004/signalint/blob/v1.2.1/src/transactions/manager.ts). Runtime behavior takes precedence when older policy prose omits the new source-writing tools or state override. ## Error and diagnostic data are not anonymized The new engine-failure wire helper strips recognizable path/env/stack patterns, but local stderr logs original details. Other refusal/transaction paths and normal diagnostic/project-root fields can expose paths/content by design. This is not a universal redaction boundary. Your client and engine plugins still control what data reaches a model or remote service. ---