devsentinel
by pbs002-s
README.md
# DevSentinel š”ļø
[](https://github.com/pbs002-s/devsentinel/actions/workflows/ci.yml)
[](LICENSE)
[](https://modelcontextprotocol.io)
[](https://nodejs.org)
[](https://www.typescriptlang.org)
> **The flight controller for coding agents.**
> An MCP server that covers the three fundamental blind spots a coding agent cannot see on its own.
š **[Explore the Interactive Landing Page & Visual Docs](https://pbs002-s.github.io/devsentinel/)** *(or open [`docs/index.html`](docs/index.html) locally)*
---
| Engine | Question it answers | Tools |
| :--- | :--- | :--- |
| **š„ BlastRadius** | *"What breaks across the repo if I change this?"* | `analyze_blast_radius`, `detect_breaking_changes` |
| **šļø PixelGuard** | *"Does the webpage actually look right?"* | `capture_ui_state`, `diff_ui_visuals` |
| **ā±ļø TimeMachine** | *"Can I undo this experiment safely?"* | `create_checkpoint`, `list_checkpoints`, `diff_checkpoint`, `rollback_checkpoint` |
Eight tools, three workflow prompts and two resources over one stdio connection. Built with Node.js + TypeScript (ESM) and `zod`-validated inputs; every tool returns deterministic, structured JSON.
## How it fits together
```
Claude Desktop / Claude Code
ā JSON-RPC over stdio
ā¼
devsentinel āāā¶ BlastRadius āāā¶ your source tree (Babel AST + Python scanner)
āā¶ PixelGuard āāā¶ headless Chromium (Playwright + pngjs)
āā¶ TimeMachine āāā¶ .devsentinel/ (sha1 snapshots, git read-only)
```
One process, no daemon, no port, no network calls of its own. Chromium is loaded lazily ā the six
non-visual tools never launch a browser. Nothing is written outside the workspace you point it at,
except the screenshots and checkpoints under `.devsentinel/`.
## Install
```bash
npm install
npm run build
npx playwright install chromium # only needed for capture_ui_state
npm test
```
Add `.devsentinel/` to your `.gitignore` ā that is where checkpoints and screenshots land.
## Register the server
### Claude Desktop
Copy `claude_desktop_config.example.json` into your Claude Desktop config and fix the two absolute paths:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"devsentinel": {
"command": "node",
"args": ["C:/absolute/path/to/devsentinel/dist/src/index.js"],
"env": { "DEVSENTINEL_WORKSPACE": "C:/absolute/path/to/your/project" }
}
}
}
```
Restart Claude Desktop afterwards.
### Claude Code
```bash
claude mcp add devsentinel -e DEVSENTINEL_WORKSPACE=/path/to/your/project -- node /abs/path/to/devsentinel/dist/src/index.js
```
Use `--scope project` to commit the server into `.mcp.json` for the whole team, or `--scope user` to make it available in every project.
### Checking the wiring
```bash
node dist/src/index.js --version # prints the version
node dist/src/index.js --help # prints the workspace it resolved
```
Every tool also accepts an explicit `workspaceDir`, which wins over `DEVSENTINEL_WORKSPACE`, which in turn wins over the server's working directory. Human-readable output goes to stderr; stdout carries only the JSON-RPC stream.
## Tools
### `analyze_blast_radius({ filePath, symbolName?, workspaceDir? })`
Parses every `.ts/.tsx/.js/.jsx/.mjs/.cjs` file with Babel and every `.py` file with an import scanner, resolves each import specifier to a real file on disk, and reports who depends on the target:
```json
{
"target": { "file": "src/math.ts", "symbol": "add", "exists": true },
"scannedFiles": 11,
"consumers": [
{ "file": "src/app.ts", "isTest": false, "importedAs": ["add"], "importKind": "esm", "referenceLines": [2, 3], "reexports": false },
{ "file": "test/math.test.ts", "isTest": true, "importedAs": ["add"], "importKind": "esm", "referenceLines": [2], "reexports": false }
],
"testFiles": ["test/math.test.ts"],
"transitiveConsumers": ["src/via-barrel.ts"],
"riskScore": 34,
"riskLevel": "medium",
"summary": "src/math.ts:add is used by 4 file(s) (1 test file(s), 1 transitive). Risk: medium."
}
```
Handles ESM imports, `require()`, dynamic `import()`, barrel re-exports, and Python `import` / `from ... import`. `testFiles` is the list to run after the edit. Because specifiers are resolved to real files, a change to `add` never drags in the callers of `subtract` ā something a text search cannot distinguish.
**`tsconfig` path aliases.** `tsconfig.json`, or `jsconfig.json` if there is no
`tsconfig.json`, is read for `compilerOptions.baseUrl` and `compilerOptions.paths`, so
`@/components/Button`, `~utils` and `@models/user` resolve to real files instead of
being dropped as package imports. Comments and trailing commas are handled, because
that is what these files actually contain; a malformed config degrades to the built-in
`~/` and `@/` handling rather than failing the scan. Bare specifiers are only tried
against `baseUrl` when `baseUrl` is actually set, so a `node_modules` import costs no
wasted lookups.
**Nested barrels.** Re-export chains are followed up to five hops with cycle detection,
so a symbol reached through `src/index.ts` ā `src/feature/index.ts` ā
`src/feature/impl.ts` still names the components that only ever import the outermost
barrel. Those arrive in `transitiveConsumers`, never duplicated into `consumers`. Pass
`includeTransitive: false` to skip the walk.
### `detect_breaking_changes({ filePath, newCode, workspaceDir? })`
Diffs the exported contract of the file on disk against the code you are about to write. Findings are graded:
- **breaking** ā removed export, new required parameter, dropped parameter, changed parameter or return type, removed or newly-required interface/class member.
- **warning** ā renamed parameter, new required member on an interface, changed type of an exported constant.
- **additive** ā new export, new optional parameter or member.
`isBreaking` is true when the breaking list is non-empty, so an agent can gate on one field.
### `capture_ui_state({ url, selector?, viewport?, outputPath?, waitMs?, workspaceDir? })`
Loads the URL in headless Chromium, writes a PNG, and runs an in-page audit that reports:
- **horizontal-overflow** ā the page scrolls sideways, with the offending elements and their widths.
- **clipped-content** ā text cut off by an `overflow: hidden` or `text-overflow: ellipsis` box.
- **overlapping-text** ā two unrelated text elements whose boxes overlap by more than 25% of the smaller one.
- **console-error**, **page-error**, **failed-request** ā anything the browser complained about while loading.
`file://` URLs work, so a static HTML file can be audited without a dev server.
### `diff_ui_visuals({ beforeImagePath, afterImagePath, diffImagePath?, threshold?, workspaceDir? })`
Compares two PNGs pixel by pixel and writes a red-on-grey overlay of what moved. Returns `changedPixels`, `diffPercentage`, whether the dimensions changed, and a verdict (`identical` / `minor` / `significant` / `major`).
### `create_checkpoint({ label?, description?, workspaceDir? })`
Copies the current working state into `.devsentinel/checkpoints/<id>/`, with a SHA-1 per file. It never touches the git index, working tree, or stash, so it is safe to use mid-rebase or in a directory that is not a repo at all. In a git repo, `git ls-files` is used to pick files, so `.gitignore` is respected; otherwise a walk with a built-in ignore list is used. Files over 5MB are skipped, and a workspace over 250MB is refused.
### `list_checkpoints({ workspaceDir? })`
Checkpoints newest first, each with the files modified, added and deleted since it was taken.
### `diff_checkpoint({ checkpointId, filePath?, workspaceDir? })`
What changed in the workspace since a checkpoint, as unified line diffs ā the question `list_checkpoints` leaves open once it has told you a file drifted.
```json
{
"checkpointId": "20260909T101500000Z-0001",
"label": "before auth refactor",
"files": [
{
"path": "src/session.ts",
"status": "modified",
"additions": 1,
"deletions": 0,
"diff": "@@ -12,6 +12,7 @@\n export function createSession(\n userId: string,\n+ ttlSeconds: number,\n ) {",
"binary": false,
"truncated": false
}
],
"totalAdditions": 1,
"totalDeletions": 0,
"summary": "1 file(s) changed since \"before auth refactor\": +1 / -0 line(s)."
}
```
`status` is `modified`, `added` or `deleted`. Pass `filePath` to narrow the report to one file. Binary files come back flagged with no text diff rather than as mojibake, and a change wider than 2000 lines per side degrades to a whole-block replace with `truncated: true`.
### `rollback_checkpoint({ checkpointId, deleteNewFiles?, workspaceDir? })`
Restores every file in the checkpoint and deletes files created since (pass `deleteNewFiles: false` to keep them). A safety checkpoint of the current state is always taken first and returned as `safetyCheckpointId`, so a rollback is itself undoable.
## Prompts
Tool descriptions say what each tool does but not what order to call them in, and
order is the whole point: a checkpoint taken after the edit is worthless, and a
baseline screenshot taken after the markup changed is not a baseline. Three MCP
prompts encode the sequences, and show up as slash commands in clients that support
them.
| Prompt | Arguments | What it drives |
| :--- | :--- | :--- |
| `safe_refactor_check` | `filePath`, `symbolName?` | Blast radius ā checkpoint ā contract check on the proposed code ā edit ā the tests the scan named |
| `visual_regression_audit` | `url`, `changeDescription?`, `selector?` | Baseline capture before the edit, re-capture after, pixel diff, and only the issues that are new |
| `experiment_sandbox` | `goal`, `rollbackIf?` | Checkpoint, a rollback trigger stated before any code is written, then diff and decide |
## Resources
| URI | Contents |
| :--- | :--- |
| `devsentinel://checkpoints/recent` | The checkpoint log with per-entry drift ā the `list_checkpoints` payload, attachable as context |
| `devsentinel://workspace/config` | Resolved workspace root and where it came from, git branch and HEAD, and the tsconfig `baseUrl` and `paths` aliases imports resolve through |
Read `devsentinel://workspace/config` first when a scan comes back empty. That is
almost always the server resolving a different directory than you assume, and this
resource says which one.
## Agent configuration
[`CLAUDE.md`](CLAUDE.md) and [`.cursorrules`](.cursorrules) ship in this repository.
Copy the one matching your tool into your own project so its agent reaches for these
tools at the right moment without being asked each time.
## Suggested workflow
```
create_checkpoint ā before a risky multi-file edit
analyze_blast_radius ā who calls the thing you are about to change
detect_breaking_changes ā does your new code break that contract
... make the edit, run the tests it named ...
capture_ui_state ā screenshot + layout audit after a UI change
diff_ui_visuals ā against the screenshot taken before
diff_checkpoint ā what the experiment actually changed, line by line
rollback_checkpoint ā if any of the above went badly
```
A prompt that puts it to work:
```
Before you touch src/auth/session.ts, run analyze_blast_radius on the
createSession symbol and tell me which tests I need to run.
```
## Project layout
```
CLAUDE.md agent protocol for Claude Code and Claude Desktop
.cursorrules the same protocol as Cursor IDE rules
src/
index.ts MCP server: tools, prompts, resources, CLI flags, shutdown
blastradius.ts Babel AST scanning, import resolution, signature diffing
pixelguard.ts Playwright capture, in-page audit, PNG diffing
timemachine.ts content-hashed snapshots, drift detection, unified diffs, rollback
workspace.ts file walking, git-aware listing, shared path helpers
test/ one suite per engine, plus an end-to-end MCP client
docs/index.html the landing page (GitHub Pages)
```
## Development
```bash
npm run build # tsc to dist/
npm run typecheck # tsc --noEmit
npm test # build, then node:test across all suites
npm start # run the server on stdio directly
npm run clean # remove dist/
```
**60 tests, 13 suites**, run on Node 18/20/22 in CI. Coverage by design:
| Suite | What it proves |
| :--- | :--- |
| `blastradius.test.ts` | ESM/CJS/Python fixtures, symbol precision, `tsconfig` alias resolution, nested and cyclic barrel chains, reference lines, every breaking-change class |
| `pixelguard.test.ts` | A real headless-Chromium audit of a deliberately broken page; pixel diffs with a known 4.00% changed area |
| `timemachine.test.ts` | Checkpoint ā drift ā rollback round trips, safety checkpoints, self-exclusion of the store, unified diffs across modified/added/deleted/binary files |
| `server.test.ts` | An MCP client speaking JSON-RPC over stdio: all eight tools, prompt rendering and tool ordering, resource reads, and error passthrough |
The Chromium tests skip themselves with a message if the browser is not installed, so `npm test` stays green on a machine without it.
## Production notes
- **Failure mode.** Tool errors come back as MCP tool errors with a usable message; the server stays alive. `SIGINT`/`SIGTERM` close the transport cleanly.
- **Input validation.** Every argument is `zod`-parsed at the boundary. Checkpoint paths are workspace-relative and rejected if they escape the workspace root.
- **Data safety.** `rollback_checkpoint` always snapshots the current state first. Nothing deletes a checkpoint automatically.
- **Privacy.** No telemetry, no network calls except the pages `capture_ui_state` is explicitly asked to open.
- **Cost.** A blast-radius scan is file I/O plus a Babel parse per file; there is no model call anywhere in this server.
### Known limits
- Import resolution covers relative, absolute, `~/` and `@/` specifiers and `tsconfig`/`jsconfig` `paths` aliases. `extends` chains are not followed; only the root config is read.
- Barrel re-export chains are followed five hops.
- Checkpoint diffs use an LCS line diff over the changed region only. A change wider than 2000 lines per side comes back as a block replace with `truncated: true`.
- Python analysis is regex-based, not a full parse: it covers module-level `def`, `class` and import statements.
- Overlap detection compares at most 300 text elements per page.
- Checkpoints copy file contents; they are not deltas, so a very large workspace is refused rather than slowly copied.
## Contributing
Issues and pull requests welcome. Keep `npm test` green, and add a test alongside any new behaviour ā each engine has a suite to extend.
## License
MIT Ā© [Pritom Biswas](https://github.com/pbs002-s)
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues