Architecture
The local diagnostic pipeline, its stateful layers, and the boundaries between them.
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
Section titled “The pipeline”Strict tool inputs. Canonical paths. Refuse project escapes.
Requested files, workspace plan, configuration and TypeScript source metadata.
Reuse stored normalized results when the engine's cache key matches.
content-hash stateFile-local misses.
TypeScript's configured program.
Engine-independent issues. File checks filter displayed paths.
Caller-supplied LSP diagnostics and report normalizers. No built-in editor subscription.
Repeated-rule clusters plus bounded, flat remaining records.
Clone/freeze snapshots; require check IDs for detail/delta. Log events for recurrence and churn.
Caller content. Path/hash preflight, optional baseline and adjacent replacements; in-memory best-effort rollback. Review transaction limits before enabling apply.
Read the diagram
Section titled “Read the diagram”- The MCP client supplies tool arguments over stdio.
- Strict input validation resolves canonical paths and refuses project escapes.
- File snapshots, optional workspace planning and configured engine selection determine the work.
- Matching cached results are reused; engine adapters check file-local misses or the configured TypeScript program. These paths converge into normalized diagnostics.
- File checks filter displayed diagnostics to the requested paths; repeated rules are grouped and ranked.
- The server stores deep-cloned/frozen check snapshots for required-ID detail and delta; persistent session state records events and detects recurrence/churn.
- Check results use schema 1.4 with clusters and flat remaining records, then return to the client through
both,text, orstructuredmode.
Responsibilities
Section titled “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
Section titled “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 for the fingerprint and configuration-selection boundaries.
Concurrency and failure isolation
Section titled “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
Section titled “State on disk”.signalint/├── cache.sqlite # Diagnostic cache├── cache/tsc/<project-id>/ # Normal project-mode TypeScript build info├── session.jsonl # Check history and metrics└── session.jsonl.1 # Rotated history backup, when presentSIGNALINT_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
Section titled “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.
Trust boundaries
Section titled “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.
Read the implementation
Section titled “Read the implementation”Stateless detail and mirrored JSON
Section titled “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.