sparda-mcp 0.68.0 → 0.69.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,97 +6,53 @@
6
6
 
7
7
  <br/>
8
8
 
9
- > 🇫🇷 **Français** _L'IA écrit. SPARDA prouve._ Un gate déterministe et hors-ligne qui détecte quand une modif d'IA retire une garde, expose une route ou casse un invariant — sans clé API, directement dans la boucle d'édition de l'agent. Pour tout comprendre en 10 minutes (douleur, architecture, vision) : [SPARDA-EXPLIQUE.md](docs/SPARDA-EXPLIQUE.md).
9
+ > 🇫🇷 **Français** : Pour comprendre SPARDA en 10 minutes (douleur, architecture, vision), lisez le document du fondateur : [SPARDA-EXPLIQUE.md](docs/SPARDA-EXPLIQUE.md).
10
10
 
11
11
  ---
12
12
 
13
- <h1 align="center">AI writes. SPARDA proves.</h1>
14
- <p align="center"><em>L'IA écrit. SPARDA prouve.</em></p>
13
+ **The trust layer for AI-written code. AI writes. SPARDA proves.**
15
14
 
16
- **The trust layer for AI-written backends.** SPARDA compiles your backend routes, database queries, state mutations, guards, side-effects into one deterministic behavior graph, then **statically proves what can and can't break before you ship**: no unguarded mutation, no broken invariant, no non-atomic aggregate write.
15
+ Under the hood: a compiler for backend behaviorthe LLVM of web applications.
17
16
 
18
- [![npm](https://img.shields.io/npm/v/sparda-mcp)](https://www.npmjs.com/package/sparda-mcp)
19
- [![CI](https://github.com/zyx77550/sparda/actions/workflows/ci.yml/badge.svg)](https://github.com/zyx77550/sparda/actions/workflows/ci.yml)
20
- ![node](https://img.shields.io/badge/node-%3E%3D18-brightgreen)
21
- ![runtime deps](https://img.shields.io/badge/runtime%20deps-4%20pinned-4c1)
22
- [![license](https://img.shields.io/badge/license-BUSL--1.1-blue)](./LICENSE)
17
+ For twenty years software communicated through APIs. Then AI agents arrived, and the industry's answer was to expose more endpoints (MCP). But an agent doesn't *understand* an application from a list of disconnected tools — and neither can a linter, a debugger, or a deploy gate reason about a codebase they only read line by line.
23
18
 
24
- 100% local · deterministic · zero API key · no cloud account. It fails loudly on a real risk, and when it can only see part of your app it says **PROVEN (PARTIAL)** never a false green.
19
+ SPARDA compiles your backend routes, database queries, state mutations, permissions, side-effects into one language-agnostic, mathematical graph: the **Unified Behavior Graph (UBG)**, serialized as `.sparda/ubg.json` under the **SBIR** specification ([SPARDA Behavior IR](docs/SBIR_SPEC_V1.1.md)). Compile once; then every tool is a simple pass over that graph.
25
20
 
26
- ## 60-second proof
27
-
28
- From your Express, FastAPI, Flask, Next.js, NestJS or Medusa app — nothing to configure:
29
-
30
- ```bash
31
- npx sparda-mcp apocalypse # prove the tree is safe to deploy — exit 1 on any real risk
32
- npx sparda-mcp prove # the whole verdict: proof + coverage + shareable seal
33
- npx sparda-mcp badge # a README badge: proven · coverage% · routes
34
- ```
35
-
36
- Under the hood it compiles your backend into one language-agnostic graph — the **Unified Behavior Graph (UBG)**, serialized as `.sparda/ubg.json` under the **SBIR** specification ([SPARDA Behavior IR](docs/SBIR_SPEC_V1.1.md)) — and every command is a pass over that graph.
21
+ > [!IMPORTANT]
22
+ > **The 3700-Route Proof:** In our latest stress-test (v0.26.0), SPARDA successfully compiled and proved **3700+ routes** from elite open-source monsters (Next.js *Dub*, NestJS *Immich*, *MedusaJS*, and GitHub's OpenAPI) in **< 2.2 seconds** per repo, with zero crashes. It natively resolves deep Dependency Injection, external controllers, and Next.js handlers.
37
23
 
38
- ## The wedgecatch an AI edit that removes a guard, in the loop
24
+ **What the graph unlocks 100% local, deterministic, zero runtime dependencies, zero API key:**
39
25
 
40
- The one thing a text-diff review and a pattern scanner structurally can't do: prove that **this specific edit** dropped a protection the previous version had. `sparda gate` diffs the behavior graph before/after an edit and blocks a regression — deterministic, offline, sub-second, exit 2 (the Claude Code `PostToolUse` contract that stops the agent's edit loop). See it end-to-end in one command, zero setup:
26
+ | Command | What it does |
27
+ |---|---|
28
+ | **`ubg`** | Compile the codebase to its behavior graph (Express · FastAPI · Next.js natively; **any** stack via OpenAPI) |
29
+ | **`apocalypse`** | *Prove the deploy* — no guard, invariant, transaction or aggregate boundary can be broken (SARIF + CI gate) |
30
+ | **`timeless`** | *Time-travel* — record a production request, replay it byte-identically, export the bug as a test |
31
+ | **`heal`** | *Self-heal, proven* — bug → fix → the machine proves the fix is correct and breaks nothing |
32
+ | **`mirror`** | *Execute the graph* — serve the compiled behavior over HTTP with no framework and no source |
33
+ | **`openapi`** | *Emit the standard* — produce an OpenAPI 3.1 spec from the graph |
34
+ | **`verify`** | *Prove the compiler's own laws* (determinism, soundness, round-trip) on your app |
35
+ | **`init` / `dev`** | Expose the graph to AI clients as a live MCP server (+ Twin, Immune, Evolution runtime layer) |
41
36
 
42
37
  ```bash
43
- npm run wedge # (from a clone) — or drive it on your own app with `sparda gate --arm` then `sparda gate --hook`
38
+ npx sparda-mcp ubg # compile your backend .sparda/ubg.json
39
+ npx sparda-mcp apocalypse # prove the current tree is safe to deploy
44
40
  ```
45
41
 
46
- ```
47
- 1. baseline armed on the guarded code (POST /admin/delete-user · requireAdmin)
48
- 2. an AI edit "simplifies" requireAdmin → a pass-through (still compiles, still 200s)
49
- 3. sparda gate on the edit:
50
- ✗ [critical] GUARD_REMOVED — POST /admin/delete-user was guarded in the baseline
51
- and is now reachable without any guard (src/app.js:11)
52
- ⏱ ~40 ms · deterministic · offline · no API key
53
- ⛔ exit 2 on --hook — Claude Code PostToolUse blocks the edit
54
- ```
55
-
56
- **Wire it into Claude Code in one line** — the [plugin](integrations/claude-code-plugin) registers a `PostToolUse` hook that runs `npx -y sparda-mcp gate --hook` after every `Edit`/`Write`, so a guard-removing edit is caught before it lands.
57
-
58
- > [!IMPORTANT]
59
- > **The Route-Compilation Proof — reproduce it yourself.** SPARDA compiles real open-source monsters to their behavior graph with **zero crashes**, each in **≈1–2 seconds**: Next.js _Dub_ (579 routes), NestJS _Immich_ (281), _MedusaJS_ (477). It natively resolves deep Dependency Injection, external controllers, and Next.js handlers. One command clones them and re-measures on your machine:
60
- >
61
- > ```bash
62
- > node bench/repro.mjs # → bench/route-proof.json
63
- > ```
64
- >
65
- > Honesty first: _compiling_ a route is a parser result (the number above); _proving_ it safe is a separate per-repo verdict — and most real apps come back **NOT_PROVEN**, which is the true state, not a failure. (Our full 25-repo corpus stress compiles **3,565 routes** at ~150 routes/s; that one needs the corpus checked out.)
66
-
67
- **What the graph unlocks — 100% local, deterministic, 4 exact-pinned dependencies, zero API key:**
42
+ No cloud account. No server to host. Exposing raw APIs to AI is the old way — SPARDA compiles the whole system's behavior, then proves, replays, heals, and serves it.
68
43
 
69
- | Command | What it does |
70
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
71
- | **`prove`** | _The whole trust verdict in one gesture_ — proof + coverage + a shareable seal (`--json` / `--markdown`) |
72
- | **`apocalypse`** | _Prove the deploy_ — no guard, invariant, transaction or aggregate boundary can be broken (SARIF + CI gate) |
73
- | **`heal`** | _Self-heal, **proven**_ — the gate Copilot Autofix doesn't have: a fix ships **only if** replay matches, `verify` still passes, and `apocalypse` finds no new risk / no dropped guard. Whoever wrote the fix, the machine judges it. |
74
- | **`badge`** | _The shareable artifact_ — a self-contained SVG badge + README snippet (verdict · coverage · routes) |
75
- | **`dossier`** | _The public report_ — one self-contained HTML page: verdict, risks, and SPARDA's own blind spots |
76
- | **`ubg`** | Compile the codebase to its behavior graph (Express · FastAPI · Flask · Next.js · NestJS · Medusa natively; **any** stack via OpenAPI) |
77
- | **`timeless`** | _Time-travel_ — record a production request, replay it byte-identically, export the bug as a test |
78
- | **`mirror`** | _Execute the graph_ — serve the compiled behavior over HTTP with no framework and no source |
79
- | **`init` / `dev`** | _Runtime, optional_ — expose the graph to AI clients as a live MCP server (+ Twin, Immune, Evolution) |
44
+ **Nomenclature:** **SBIR** is the specification (the format, like "JSON"); **UBG** is the compiled graph itself (the artifact, `ubg.json`). The MCP server is one *output* of the graph, not the product.
80
45
 
81
- The prover is the product. The MCP server is one _output_ of the graph, not the point — SPARDA compiles the whole system's behavior, then proves, replays, heals, and (optionally) serves it.
82
-
83
- **Nomenclature:** **SBIR** is the specification (the format, like "JSON"); **UBG** is the compiled graph itself (the artifact, `ubg.json`). The MCP server is one _output_ of the graph, not the product.
84
-
85
- ## Optional: expose the graph to AI clients (MCP runtime)
86
-
87
- Beyond proving, SPARDA can turn your running app into a live MCP server — the graph, executable, with write-safety and an immune layer. This is optional and separate from the prover above.
46
+ ## Quickstart
88
47
 
89
48
  1. **Scan + inject** — run once, from your app's directory:
90
-
91
49
  ```bash
92
50
  npx sparda-mcp init
93
51
  ```
94
-
95
52
  SPARDA parses your routes (AST), generates a marked `/mcp` router, injects it into
96
53
  your app (with a backup), and writes `sparda.json`. Every step is reversible.
97
54
 
98
55
  2. **Start your app, then start the bridge:**
99
-
100
56
  ```bash
101
57
  npx sparda-mcp dev
102
58
  ```
@@ -120,31 +76,25 @@ Beyond proving, SPARDA can turn your running app into a live MCP server — the
120
76
  ## Try the Standalone Demo
121
77
 
122
78
  To see SPARDA in action instantly without modifying your codebase:
123
-
124
79
  ```bash
125
80
  npx sparda-mcp demo
126
81
  ```
127
-
128
82
  This runs the entire MCP lifecycle (detect → parse → generate → inject → remove) on a bundled demo app in a temporary folder, in about 10 seconds. For the compiler itself, run `npx sparda-mcp ubg` then `apocalypse` on any Express/FastAPI app.
129
83
 
130
84
  ## Black Box Report
131
85
 
132
86
  SPARDA is designed as a local organism. To see what it remembers and how much compute it has recycled:
133
-
134
87
  ```bash
135
88
  npx sparda-mcp report
136
89
  ```
137
-
138
90
  This prints a terminal dashboard aggregating your exposed tools, write opt-ins, proof journal decisions, and crystallized composite tools.
139
91
 
140
92
  To write a self-contained, offline HTML dashboard at `.sparda/report.html`, append the `--html` flag:
141
-
142
93
  ```bash
143
94
  npx sparda-mcp report --html
144
95
  ```
145
96
 
146
97
  To output raw JSON for integration:
147
-
148
98
  ```bash
149
99
  npx sparda-mcp report --json
150
100
  ```
@@ -152,35 +102,28 @@ npx sparda-mcp report --json
152
102
  ## Deployment Proof: Apocalypse
153
103
 
154
104
  SPARDA's Behavior Graph is a formal model of your system. Instead of waiting for runtime failures or relying on static analysis vibes, you can statically prove the safety of your backend before any deployment:
155
-
156
105
  ```bash
157
106
  npx sparda-mcp apocalypse
158
107
  ```
159
-
160
108
  This command reads the compiled `.sparda/ubg.json` (with zero source code parsing at runtime) and discharges five static correctness obligations:
161
-
162
- - **Unguarded Mutation (Critical)**: Flags any mutation path that does not cross a security `guard`.
163
- - **Non-Atomic Aggregate Write (High)**: Flags when an API writes to multiple tables of the same Consistency Domain (Aggregate) outside a single transaction scope.
164
- - **Unvalidated Constrained Write (Medium)**: Flags writes into columns with declared invariants (CHECK, NOT NULL, UNIQUE parsed from your `.sql` DDL **or `schema.prisma`**, Prisma enums included) without prior validation (Zod/Pydantic).
165
- - **Irreversible Observable Effect (High)**: Flags out-of-process actions (like Stripe charges) that happen alongside state writes without a structural compensation path (like a catch-refund).
166
- - **Aggregate Member Bypass (Info)**: Flags mutating a member table directly without routing through the aggregate root.
109
+ * **Unguarded Mutation (Critical)**: Flags any mutation path that does not cross a security `guard`.
110
+ * **Non-Atomic Aggregate Write (High)**: Flags when an API writes to multiple tables of the same Consistency Domain (Aggregate) outside a single transaction scope.
111
+ * **Unvalidated Constrained Write (Medium)**: Flags writes into columns with declared invariants (CHECK, NOT NULL, UNIQUE parsed from your `.sql` DDL **or `schema.prisma`**, Prisma enums included) without prior validation (Zod/Pydantic).
112
+ * **Irreversible Observable Effect (High)**: Flags out-of-process actions (like Stripe charges) that happen alongside state writes without a structural compensation path (like a catch-refund).
113
+ * **Aggregate Member Bypass (Info)**: Flags mutating a member table directly without routing through the aggregate root.
167
114
 
168
115
  To save your current graph as a safe baseline:
169
-
170
116
  ```bash
171
117
  npx sparda-mcp apocalypse --save-baseline
172
118
  ```
173
-
174
119
  Subsequent runs will diff the candidate graph against this baseline to detect regression vectors:
175
-
176
- - Deletion of any security `guard` (Critical).
177
- - Deletion of a database SQL invariant (High).
178
- - API blast radius expansion (Medium).
120
+ * Deletion of any security `guard` (Critical).
121
+ * Deletion of a database SQL invariant (High).
122
+ * API blast radius expansion (Medium).
179
123
 
180
124
  If any Critical or High finding is found, `apocalypse` exits with a non-zero code to block your CI pipeline.
181
125
 
182
126
  **One step in your workflow — findings land in the GitHub Security tab (SARIF):**
183
-
184
127
  ```yaml
185
128
  - uses: zyx77550/sparda@main
186
129
  with:
@@ -198,13 +141,11 @@ npx sparda-mcp timeless export <id> # the production bug is now a vitest test
198
141
  ```
199
142
 
200
143
  Recording is two lines in your app (ESM), with deterministic sampling and GDPR redaction built in:
201
-
202
144
  ```js
203
145
  import { getFlightBox } from 'sparda-mcp/src/flight/box.js';
204
- const box = getFlightBox();
205
- box.arm();
206
- app.use(box.middleware({ sample: 100 })); // 1 request in 100; passwords/tokens redacted by default
207
- const db = box.wrapClient(pgPool); // your query client, tapped
146
+ const box = getFlightBox(); box.arm();
147
+ app.use(box.middleware({ sample: 100 })); // 1 request in 100; passwords/tokens redacted by default
148
+ const db = box.wrapClient(pgPool); // your query client, tapped
208
149
  ```
209
150
 
210
151
  The closed loop nobody else has: **production bug → recorded flight → failing test → AI writes the fix → `apocalypse` proves the fix breaks no guard, invariant or transaction → deploy.** Replay is per-request (concurrent-race capture is out of scope for v1 — stated, not hidden).
@@ -221,7 +162,7 @@ npx sparda-mcp heal <flightId> --check --expect '{"status":404}'
221
162
 
222
163
  The brief is built from the graph itself — it hands the fixer the handler's `file:line`, the capabilities the fix must not grow, and the guards it must not remove. Then the **gate** — the actual product — proves the fix on three axes at once:
223
164
 
224
- 1. **Behavior** — lenient replay of the recorded flight (same deterministic inputs) now produces the _expected_ response, not the recorded bug. The fix may reformulate a query (the tap is relabeled, allowed); it may **not** change the effect order or kinds.
165
+ 1. **Behavior** — lenient replay of the recorded flight (same deterministic inputs) now produces the *expected* response, not the recorded bug. The fix may reformulate a query (the tap is relabeled, allowed); it may **not** change the effect order or kinds.
225
166
  2. **Compiler laws** — `verify` still passes: the graph is still sound and deterministic.
226
167
  3. **No regression** — `apocalypse` diff against the frozen pre-fix graph: zero new critical/high findings, no guard removed, no blast radius grown.
227
168
 
@@ -229,11 +170,11 @@ The brief is built from the graph itself — it hands the fixer the handler's `f
229
170
  ✓ HEALED & PROVEN — same recorded inputs, correct output, zero law broken, zero protection lost. Ship it.
230
171
  ```
231
172
 
232
- The gate is honest in both directions: an unfixed bug, or a "fix" that silently drops a guard, keeps it **closed** (exit 1). This is the difference between an AI that writes plausible code and a system that _proves_ the code is correct — the trust layer the agent era is missing.
173
+ The gate is honest in both directions: an unfixed bug, or a "fix" that silently drops a guard, keeps it **closed** (exit 1). This is the difference between an AI that writes plausible code and a system that *proves* the code is correct — the trust layer the agent era is missing.
233
174
 
234
175
  ## Any Backend On Earth: OpenAPI Lowering
235
176
 
236
- SPARDA parses Express, FastAPI, Flask and Next.js natively — and **every other stack through the format the industry already agreed on**. Go, Java, Rails, Laravel, .NET: if it has an OpenAPI spec, it compiles.
177
+ SPARDA parses Express, FastAPI and Next.js natively — and **every other stack through the format the industry already agreed on**. Go, Java, Rails, Laravel, .NET: if it has an OpenAPI spec, it compiles.
237
178
 
238
179
  ```bash
239
180
  npx sparda-mcp ubg --openapi openapi.json
@@ -274,7 +215,7 @@ To undo everything: **`npx sparda-mcp remove`** restores your code byte-for-byte
274
215
  5. **Nothing leaves your machine.** No telemetry to us, no cloud, local key auth, 4 exact-pinned dependencies.
275
216
  6. **What it learns is never lost.** Diagnoses, descriptions, settings — versioned with your git, surviving every re-init.
276
217
 
277
- What we _don't_ promise: the honest limits in [docs/SECURITY.md](./docs/SECURITY.md).
218
+ What we *don't* promise: the honest limits in [docs/SECURITY.md](./docs/SECURITY.md).
278
219
 
279
220
  ## How it works
280
221
 
@@ -291,52 +232,34 @@ What we _don't_ promise: the honest limits in [docs/SECURITY.md](./docs/SECURITY
291
232
  ## What SPARDA gives your AI
292
233
 
293
234
  ### Operate, not just read
294
-
295
235
  Every route becomes a tool that runs against your live process — real auth, real data,
296
236
  warm connections. One call to **`sparda_get_context`** hands the AI the whole living
297
237
  picture: enabled tools, suggested workflows, runtime telemetry, quarantine state, and
298
238
  immune memory — so every session resumes where the last one stopped.
299
239
 
300
- ### Prove the edit before you commit — the one check an LLM can't do to itself
301
-
302
- The AI just edited a route. Did it quietly drop a guard? It calls **`sparda_prove`** and
303
- finds out **now**, not in a CI run later. The tool recompiles the app to its behavior graph,
304
- discharges the same static obligations as `sparda apocalypse`, and returns a deterministic
305
- verdict — the exact word the CLI and badge emit, so it can never over-claim (a low-coverage
306
- clean app reads `SURFACE`, never a bare `PROVEN`). Save a baseline once
307
- (`sparda apocalypse --save-baseline`) and every later `sparda_prove` flags any finding with
308
- `regression: true` — the guard your edit removed, the route it dropped, the blast radius it
309
- grew. That's _"AI writes. SPARDA proves."_ inside the edit loop. Clients that list MCP prompts
310
- also get the **`prove-my-edit`** workflow.
311
-
312
240
  ### Write-safety: the AI can't write until you say so
313
-
314
241
  - Writes (POST/PUT/DELETE) ship **disabled**. Enable them per tool in `sparda.json`; your choice survives every re-init.
315
242
  - An enabled write is **never executed on the first call**. SPARDA returns an `awaiting_confirmation` envelope — a single-use token plus a preview of the action — and commits only after an explicit confirm step.
316
243
  - When your client supports MCP elicitation, that confirmation prompt appears **in the AI's own UI**.
317
244
  - **Proof-after-write**: every successful write is followed by a read-back of the same resource, so the AI — and you — see the real effect, not a hopeful guess.
318
245
 
319
246
  ### Your app defends itself — zero LLM on the hot path
320
-
321
247
  - **Quarantine.** A tool that returns 3 consecutive 5xx is quarantined: further calls get a `503` with a reason and a retry delay instead of hammering your broken route. After a cooldown it half-opens for a single probe.
322
248
  - **Latency & anomaly flags.** The router learns each route's baseline and flags deviations locally, in a few lines of math.
323
249
  - **Adaptive diagnosis, only on surprise.** A genuinely new failure wakes your AI client's own model to diagnose it once; the diagnosis is cached as an "antibody" in `sparda.json`, so the same failure later costs zero tokens. Cloning your code doesn't clone its immune memory.
324
250
 
325
251
  ### A free intelligence layer, zero API key
326
-
327
252
  On first connection your AI client's own model (via MCP sampling) rewrites raw routes
328
253
  into business-language tool descriptions and proposes multi-step workflows — cached in
329
254
  `sparda.json` and exposed as MCP prompts. Nothing to configure, nothing to pay.
330
255
 
331
256
  ### It gets cheaper the more you use it
332
-
333
257
  - **Response recycling.** When a read keeps returning the same answer, SPARDA serves the next identical call straight from memory — without touching your host app. Reads only; writes always hit the host.
334
258
  - **A recycling gauge.** `GET /mcp/stats` counts how many calls were answered from SPARDA's own knowledge vs. how many paid the host route. It reads 0% on day one and fills with usage — a measure, never a promise.
335
259
 
336
260
  ### Tools nobody wrote — Labs, opt-in, default OFF
337
-
338
261
  Turn it on with `"labs": { "recordSequences": true }` in `sparda.json`. SPARDA then
339
- notices when one tool's output feeds the next tool's input and records the _circuit_
262
+ notices when one tool's output feeds the next tool's input and records the *circuit*
340
263
  structure only (tool names, argument names, counts), never your data. A read-only
341
264
  circuit seen enough times **crystallizes into a composite tool**, announced
342
265
  mid-session: one call runs the whole chain, auto-feeding each step from the previous
@@ -344,13 +267,11 @@ step's real response. Write routes are never absorbed — their per-call confirm
344
267
  always stands.
345
268
 
346
269
  ### Living context & telemetry
347
-
348
270
  `GET /mcp/stats` (per-tool calls/errors, tool "purity", quarantine state) and
349
271
  `GET /mcp/events` (errors, latency anomalies, cached diagnoses) expose exactly what
350
272
  your app is doing — surfaced to the AI as live notifications.
351
273
 
352
274
  ## Built for AI clients: the bundled Skill
353
-
354
275
  SPARDA ships with an Agent Skill ([`SKILL.md`](./SKILL.md)) that teaches any compatible
355
276
  AI client how to drive a SPARDA server to its **full potential** — call
356
277
  `sparda_get_context` first, exploit response recycling, honor quarantine, prefer
@@ -361,19 +282,13 @@ runtime, so the guidance never goes stale.
361
282
  ## Supported frameworks
362
283
 
363
284
  - **Next.js App Router (13/14/15)** — file-based injection. SPARDA creates a catch-all route handler. It natively resolves wrapped handlers (`export const POST = withAuth(h)`) and deep effect chains.
364
- - **NestJS** — AST-based router injection. Deeply resolves Multi-hop Dependency Injection (Controller → Service → Repository), inherited DI, and `baseUrl`/`paths` imports. Resolves ORM writes: Prisma, Kysely, and TypeORM injected repositories (`@InjectRepository(Entity)` → `this.repo.save()`).
365
- - **Express 4/5** (JS/TS, ESM/CJS) — AST-based router injection. Deeply resolves external controllers, Mongoose schemas, barrel re-exports, and inline handlers. Uses dynamic tree-scanning to find non-standard entry points (`bootstrap.ts`, etc).
285
+ - **NestJS** — AST-based router injection. Deeply resolves Multi-hop Dependency Injection (Controller → Service → Repository), inherited DI, and `baseUrl`/`paths` imports. Supports Prisma, TypeORM, and Kysely.
286
+ - **Express 4/5** (JS/TS, ESM/CJS) — AST-based router injection. Deeply resolves external controllers, Mongoose schemas, and barrel re-exports. Uses dynamic tree-scanning to find non-standard entry points (`bootstrap.ts`, etc).
366
287
  - **MedusaJS** — Native AST ingestion of complex e-commerce routing.
367
288
  - **Any Backend On Earth (Go, Java, Rails, Laravel)** — Compiles flawlessly from OpenAPI 3.x specs.
368
289
  - **FastAPI** (Python >= 3.9) — AST-based router injection.
369
290
 
370
- ### Effects it resolves (what makes the irreversibility & atomicity proofs bite)
371
-
372
- - **Databases** — Prisma (incl. named/multiline relations and interactive `$transaction(tx ⇒ …)`), TypeORM, Kysely, Drizzle, Knex, Sequelize, Mongoose, and raw SQL. Foreign keys become aggregate/consistency domains, so a multi-table write outside a transaction is caught.
373
- - **External side-effects** — recognized by call shape and by import origin, so an irreversible outbound effect next to a DB write is proven compensable-or-not: `fetch`/axios/got, Stripe, Twilio, SendGrid/Resend/nodemailer, AWS SDK v3 (`send(new PutObjectCommand())`), and other payment/mail/cloud/queue clients. A read on such a client stays a non-observable GET — no false alarms.
374
-
375
291
  ## Security posture (honest)
376
-
377
292
  - 4 runtime dependencies, exact-pinned.
378
293
  - **Dynamic Local Key Resolution.** The generated router contains no baked secrets. It resolves authorization keys at runtime from the `SPARDA_LOCAL_KEY` environment variable or the local gitignored `.sparda/key` file, and fails closed (503) when neither is found. For custom production or staging setups, you can override this behavior by exposing `SPARDA_LOCAL_KEY` in your environment.
379
294
  - Local key on every router call; self-reference loop protection; 30s timeouts; 8 KB output truncation.
@@ -383,21 +298,18 @@ runtime, so the guidance never goes stale.
383
298
  Full threat model and known gaps: [docs/SECURITY.md](./docs/SECURITY.md).
384
299
 
385
300
  ## Documentation
386
-
387
301
  - [docs/ARCHITECTURE.md](./docs/ARCHITECTURE.md) — how `init`, the injected router, and the bridge fit together, plus the `sparda.json` schema.
388
302
  - [docs/SECURITY.md](./docs/SECURITY.md) — threat model, defenses, and honest known gaps.
389
303
  - [docs/TESTING.md](./docs/TESTING.md) — how the promises above are kept honest in CI.
390
304
  - [docs/ERRORS.md](./docs/ERRORS.md) — the error knowledge base.
391
305
 
392
306
  ## Beyond the open core
393
-
394
307
  SPARDA is free, including in production (see License). Team-scale capabilities —
395
308
  fine-grained per-person access policies and a signed, tamper-evident audit log — are
396
309
  planned for a future paid tier. The open core stands on its own; nothing here is
397
310
  crippled to upsell you.
398
311
 
399
312
  ## License
400
-
401
313
  [Business Source License 1.1](./LICENSE) — free to use, including in production.
402
314
  You may not resell SPARDA or offer it as a competing commercial service.
403
315
  Each version converts to Apache 2.0 four years after its release.
package/SKILL.md CHANGED
@@ -13,11 +13,11 @@ description: >-
13
13
 
14
14
  A SPARDA server is driven by a compiled **Unified Behavior Graph (UBG)** — the graph SPARDA's compiler produces from the host application's states, transitions, permissions, and side-effects, serialized under the **SBIR** specification. Instead of exposing raw, disconnected endpoints, SPARDA compiles the app into a deterministic behavioral model. The local **SPARDA Runtime** dynamically executes this graph inside the live host process, powering the MCP interface, the Twin simulation clone, and the Immune system offline.
15
15
 
16
- > This skill covers the **runtime** (driving a live MCP server) — the live half of SPARDA's trust layer: _"AI writes. SPARDA proves."_ The same graph also powers dev-time proof commands you run in the app's repo — `sparda review` (the behavior diff of a PR), `apocalypse` (prove the deploy), `timeless` (record/replay a request), `heal` (prove a fix), `mirror` (serve the graph), `ubg` (compile), `verify` (prove the compiler's laws). Those are CLI, not MCP tools; see the project README.
16
+ > This skill covers the **runtime** (driving a live MCP server) — the live half of SPARDA's trust layer: *"AI writes. SPARDA proves."* The same graph also powers dev-time proof commands you run in the app's repo — `sparda review` (the behavior diff of a PR), `apocalypse` (prove the deploy), `timeless` (record/replay a request), `heal` (prove a fix), `mirror` (serve the graph), `ubg` (compile), `verify` (prove the compiler's laws). Those are CLI, not MCP tools; see the project README.
17
17
 
18
18
  ## Rule 0 — call `sparda_get_context` first, every session
19
19
 
20
- Before anything else, call **`sparda_get_context`** (no params). It returns the _live_ state of the SPARDA Behavior Graph:
20
+ Before anything else, call **`sparda_get_context`** (no params). It returns the *live* state of the SPARDA Behavior Graph:
21
21
 
22
22
  - the active routes/tools, workflows, and type-propagated schemas;
23
23
  - `runtime` — current stats (calls, errors, quarantine states, Twin mode active);
@@ -35,45 +35,19 @@ Read it to orient yourself inside the graph. `sparda_info` gives a lighter summa
35
35
  parameter schema was only partially inferred — pass arguments carefully.
36
36
  - **Meta-tools** — `sparda_get_context`, `sparda_info`,
37
37
  `sparda_list_disabled_tools`, `sparda_confirm`.
38
- - **The proof tool** — `sparda_prove`. Call it **after you edit a route, before you
39
- commit** — see _Prove your own edit_ below. It's the one check you can't do to
40
- yourself by re-reading your code.
41
38
  - **Composite tools** — labelled `[Labs circuit ×N]`, `readOnly`. One call runs a
42
- whole proven multi-step chain (see _Crystallized circuits_ below).
39
+ whole proven multi-step chain (see *Crystallized circuits* below).
43
40
 
44
41
  Only **enabled** tools appear. Write tools are hidden until the user opts in, so a
45
- missing write is a config state, not an error — see _Writing safely_.
46
-
47
- ## Prove your own edit — call `sparda_prove` before you commit
48
-
49
- This is the tool an LLM needs most and can least fake. After you edit a route,
50
- **call `sparda_prove`** — it recompiles the app to its behavior graph and discharges
51
- the same static obligations as `sparda apocalypse` (unguarded mutation, non-atomic
52
- aggregate write, unvalidated constrained write), then returns a deterministic verdict.
53
-
54
- - **Focus it.** Pass `route` with the method+path you just touched (e.g.
55
- `{ "route": "DELETE /orders" }`) to narrow the finding list. The **verdict still
56
- reflects the whole app** — a filter never buys you a greener light.
57
- - **Read the verdict honestly.** `PROVEN` / `PARTIAL` are safe to commit. `SURFACE`
58
- and `NO_PROOF` mean SPARDA _couldn't resolve enough to prove it_ — that is
59
- "unknown", **never** a pass. The word is the exact one the CLI and badge emit; it
60
- physically cannot over-claim.
61
- - **The regression check is the point.** If a baseline was saved
62
- (`sparda apocalypse --save-baseline` on a known-good state), any finding with
63
- `regression: true` means _your edit_ removed a guard, dropped a route, or grew the
64
- blast radius vs the last proven state. Fix those before committing — this is the
65
- check you cannot perform by re-reading your own diff.
66
- - Clients that list MCP prompts also see **`prove-my-edit`**, the built-in workflow
67
- that walks these steps.
42
+ missing write is a config state, not an error — see *Writing safely*.
68
43
 
69
44
  ## Exploit the intelligence layer (this is the "full potential")
70
45
 
71
46
  **1. Response-recycling flywheel — make repeated reads free.**
72
- When the _same_ read tool returns a byte-identical result for the _same_ arguments
47
+ When the *same* read tool returns a byte-identical result for the *same* arguments
73
48
  **3 times within 30 seconds**, SPARDA serves the next identical call straight from
74
49
  RAM (`servedByFlywheel: true`) **without touching the host app**. So:
75
-
76
- - Don't fear repeating stable GETs — repetition is what _activates_ the cache.
50
+ - Don't fear repeating stable GETs — repetition is what *activates* the cache.
77
51
  - Don't bolt your own client-side cache on top; you'd hide the signal that lets
78
52
  SPARDA recycle, and you'd lose freshness control.
79
53
  - Watch `recycling.flywheel.servedFromMemory` climb in context — that's free work.
@@ -81,7 +55,7 @@ RAM (`servedByFlywheel: true`) **without touching the host app**. So:
81
55
 
82
56
  **2. Circuit-breaker / quarantine — stop hammering a sick backend.**
83
57
  After **3 consecutive 5xx** on a tool, SPARDA quarantines it: subsequent calls
84
- return **HTTP 503** with `reason` and `retryInMs` _instead of_ hitting the failing
58
+ return **HTTP 503** with `reason` and `retryInMs` *instead of* hitting the failing
85
59
  host. Honor `retryInMs` — do not retry-loop. Check `runtime.quarantine` in context
86
60
  before depending on a tool. After a cooldown (~60s) the tool half-opens for one
87
61
  probe; one more 5xx re-quarantines it.
@@ -94,7 +68,7 @@ single **composite tool** for that chain and announces it mid-session via
94
68
  call and it's marked read-only. (Writes are never absorbed into a circuit.)
95
69
 
96
70
  **4. Adaptive immunity — read the diagnosis before retrying.**
97
- Repeated, unfamiliar failures trigger a _one-shot_ LLM diagnosis that SPARDA caches
71
+ Repeated, unfamiliar failures trigger a *one-shot* LLM diagnosis that SPARDA caches
98
72
  as an "antibody" (keyed by `source|tool|status`). Recurrences reuse the cached
99
73
  diagnosis at zero cost. When an error event carries a diagnosis, **read it** and
100
74
  adapt — don't blindly retry the same call.
@@ -104,13 +78,11 @@ an `immune` event in `/mcp/events`. Treat it as a hint to back off or warn the u
104
78
 
105
79
  **6. Twin Simulation Mode — practice safely on a clone.**
106
80
  When `/mcp/stats` or `sparda_get_context.runtime` contains `"twin": true`, you are connected to a safe, in-memory mock clone of the application.
107
-
108
81
  - All GET reads return learned exemplars (observed response shapes and mock values).
109
82
  - All write tools return simulated `202` echoes but do not write to database or external APIs.
110
83
  - Use this twin mode to practice multi-step workflows, debug tool sequences, and test your plans without touching the live production backend.
111
84
 
112
85
  **7. Grammar & Evolution — discover optimal workflows.**
113
-
114
86
  - You can query or contribute to the app's grammar (`.sparda/grammar.json`). The grammar maps valid sequences of tool calls (edges).
115
87
  - Running `sparda evolve` mutates and runs candidate chains against the twin. The successful evolved sequences are suggested as mid-session workflows.
116
88
 
@@ -179,8 +151,7 @@ Writes are **disabled by default**. The protocol is not optional:
179
151
  - **Learn exemplars** → Start your live app and run `sparda twin --learn` to fetch actual response data and construct `.sparda/twin.json` locally.
180
152
 
181
153
  ---
182
-
183
- _This skill ships with `sparda-mcp` and is regenerated from SPARDA's capability
154
+ *This skill ships with `sparda-mcp` and is regenerated from SPARDA's capability
184
155
  surface each release, so it tracks new tools and behaviors. The **live, per-project**
185
156
  tool list, stats, and workflows always come from `sparda_get_context` at runtime —
186
- trust it over any static list._
157
+ trust it over any static list.*
@@ -1 +1 @@
1
- {"v":"imm1","proven":false,"surfaceOnly":true,"coverage":1,"blindHigh":0,"routes":[{"behaviorHash":"bh1_13969839d8a18d54aa0342618e56db6a","pol":121,"exposed":[]},{"behaviorHash":"bh1_2daf8a2b1b0ed393d443991a2b0700f6","pol":121,"exposed":[]},{"behaviorHash":"bh1_58fbf5ac38b6d71abc7204921de6b957","pol":121,"exposed":[]},{"behaviorHash":"bh1_58fbf5ac38b6d71abc7204921de6b957","pol":121,"exposed":[]},{"behaviorHash":"bh1_cc6788289c612af85b8d215b5bdf9b28","pol":121,"exposed":[]}],"posture":{"auth":{"protected":0,"exposed":0,"na":5},"atomicity":{"protected":0,"exposed":0,"na":5},"reversibility":{"protected":0,"exposed":0,"na":5},"validation":{"protected":0,"exposed":0,"na":5},"aggregate":{"protected":0,"exposed":0,"na":5}},"bytes":5}
1
+ {"v":"imm1","proven":false,"surfaceOnly":true,"coverage":null,"blindHigh":0,"routes":[{"behaviorHash":"bh1_13969839d8a18d54aa0342618e56db6a","pol":121,"exposed":[]},{"behaviorHash":"bh1_2daf8a2b1b0ed393d443991a2b0700f6","pol":121,"exposed":[]},{"behaviorHash":"bh1_58fbf5ac38b6d71abc7204921de6b957","pol":121,"exposed":[]},{"behaviorHash":"bh1_58fbf5ac38b6d71abc7204921de6b957","pol":121,"exposed":[]},{"behaviorHash":"bh1_cc6788289c612af85b8d215b5bdf9b28","pol":121,"exposed":[]}],"posture":{"auth":{"protected":0,"exposed":0,"na":5},"atomicity":{"protected":0,"exposed":0,"na":5},"reversibility":{"protected":0,"exposed":0,"na":5},"validation":{"protected":0,"exposed":0,"na":5},"aggregate":{"protected":0,"exposed":0,"na":5}},"bytes":5}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparda-mcp",
3
- "version": "0.68.0",
3
+ "version": "0.69.1",
4
4
  "mcpName": "io.github.zyx77550/sparda-mcp",
5
5
  "description": "AI writes. SPARDA proves. A deterministic, offline gate that catches when an AI edit removes a guard, exposes a route, or breaks an invariant — no API key, right in the agent edit loop.",
6
6
  "type": "module",
@@ -18,7 +18,8 @@ import {
18
18
  verdictState,
19
19
  buildProofObjects,
20
20
  } from '../ubg/apocalypse.js';
21
- import { surveyBlindspots } from '../ubg/blindspots.js';
21
+ import { surveyBlindspots, coveragePct } from '../ubg/blindspots.js';
22
+ import { premiseFor, withPremiseGaps } from '../ubg/premise.js';
22
23
  import { atomicWriteFileSync as atomicWrite } from '../server/persistence.js';
23
24
 
24
25
  // version travels with the proof so an audit knows which prover produced it
@@ -69,13 +70,22 @@ export async function runApocalypse(opts) {
69
70
  }
70
71
 
71
72
  const findings = [...staticFindings, ...diffFindings];
73
+ // THE PREMISE, before the proof. This is the CI gate — the command whose exit code
74
+ // decides whether a tree ships — so it is the last place that may certify an app whose
75
+ // route table nobody checked. `premiseFor` keeps the opt-in boundary: the runtime
76
+ // oracle needs `--probe`, the boot-free convention oracle always runs.
77
+ const premise = await premiseFor(canonical, report, {
78
+ cwd: opts.cwd,
79
+ probe: opts.probe,
80
+ });
72
81
  // the honesty companion: where does the proof stop? (see `sparda blindspots`)
73
- const blind = surveyBlindspots(canonical, report);
82
+ const blind = surveyBlindspots(canonical, withPremiseGaps(report, premise));
74
83
  // coverage feeds the verdict: a clean app that resolved almost nothing is SURFACE, not PROVEN;
75
84
  // and any high-risk blind spot pulls a bare PROVEN down to PARTIAL (E-047, the giant-test rung)
76
85
  const verdict = verdictOf(findings, canonical, {
77
86
  coverage: blind.coverage.ratio,
78
87
  blindHigh: blind.byRisk.critical + blind.byRisk.high,
88
+ premiseGaps: premise.available ? premise.gaps.length : 0,
79
89
  });
80
90
 
81
91
  if (opts.sarif) {
@@ -136,6 +146,21 @@ export async function runApocalypse(opts) {
136
146
  if (opts.verbose && report.skipped?.length)
137
147
  for (const s of report.skipped)
138
148
  console.log(` skipped: ${s.reason}${s.file ? ` (${s.file})` : ''}`);
149
+ } else if (verdict.premiseUnverified) {
150
+ // The one negative backed by a source outside SPARDA. It is stated BEFORE the
151
+ // clean/risky branches on purpose: findings about a subject we did not have are
152
+ // not the headline, the missing subject is.
153
+ const witness =
154
+ premise.oracle === 'convention'
155
+ ? "the framework's own file conventions"
156
+ : 'the running app';
157
+ console.log(
158
+ `✗ PREMISE NOT VERIFIED — ${premise.gaps.length} route(s) ${witness} serve were NEVER seen by the compiler. Nothing below is a claim about this tree: the proof's subject is incomplete, so no verdict is issued.`,
159
+ );
160
+ for (const g of premise.gaps.slice(0, 8))
161
+ console.log(` ✗ ${g.method} ${g.path}`);
162
+ if (premise.gaps.length > 8)
163
+ console.log(` … and ${premise.gaps.length - 8} more`);
139
164
  } else if (verdict.clean) {
140
165
  console.log(
141
166
  `✓ PROVEN — ${obligations} obligation(s) discharged, zero violations. No declared guard, invariant, transaction or aggregate boundary can be broken by this tree.`,
@@ -148,16 +173,22 @@ export async function runApocalypse(opts) {
148
173
  }
149
174
  // Honesty companion: where the proof stops. A green verdict over a graph riddled
150
175
  // with blind spots is not omniscience — say so, on the same screen as the verdict.
176
+ if (premise.available)
177
+ console.log(
178
+ premise.oracle === 'convention'
179
+ ? ` ⚖ premise checked against the framework's file conventions: ${premise.probed} route(s) implied, ${premise.gaps.length} gap(s)`
180
+ : ` ⚖ premise checked against the running app: ${premise.probed} route(s) probed, ${premise.gaps.length} gap(s)`,
181
+ );
151
182
  if (blind.surface > 0) {
152
183
  const hi = blind.byRisk.critical + blind.byRisk.high;
153
184
  console.log(
154
- ` ◐ blind spots: ${blind.surface} (${hi} high+, ${blind.byRisk.medium} medium, ${blind.byRisk.low} low) · coverage ${(blind.coverage.ratio * 100).toFixed(0)}% — run \`sparda blindspots\` for the map`,
185
+ ` ◐ blind spots: ${blind.surface} (${hi} high+, ${blind.byRisk.medium} medium, ${blind.byRisk.low} low) · coverage ${coveragePct(blind.coverage.ratio)} — run \`sparda blindspots\` for the map`,
155
186
  );
156
187
  }
157
188
  }
158
189
 
159
190
  if (!verdict.safe) process.exitCode = 1; // CI gates on this
160
- return { verdict, findings, obligations, blindspots: blind };
191
+ return { verdict, findings, obligations, blindspots: blind, premise };
161
192
  }
162
193
 
163
194
  // SARIF 2.1.0 — GitHub code scanning eats this directly
@@ -9,18 +9,29 @@ import path from 'node:path';
9
9
  import { compileUBG } from '../ubg/compile.js';
10
10
  import { canonicalizeGraph } from '../ubg/schema.js';
11
11
  import { checkGraph, verdictOf, badgeFor } from '../ubg/apocalypse.js';
12
- import { surveyBlindspots } from '../ubg/blindspots.js';
12
+ import { surveyBlindspots, coveragePct } from '../ubg/blindspots.js';
13
+ import { premiseFor, withPremiseGaps } from '../ubg/premise.js';
13
14
 
14
15
  export async function runBadge(opts) {
15
16
  const { graph, report } = compileUBG(opts.cwd, { write: false });
16
17
  const canonical = canonicalizeGraph(graph);
17
18
  const { findings } = checkGraph(canonical);
18
- const blind = surveyBlindspots(canonical, report);
19
+ // A badge is the artifact that leaves the repo. It may not read green over an app
20
+ // whose route table was never checked — that is the one place a false claim travels
21
+ // furthest and is hardest to retract.
22
+ const premise = await premiseFor(canonical, report, {
23
+ cwd: opts.cwd,
24
+ probe: opts.probe,
25
+ });
26
+ const blind = surveyBlindspots(canonical, withPremiseGaps(report, premise));
19
27
  const verdict = verdictOf(findings, canonical, {
20
28
  coverage: blind.coverage.ratio,
21
29
  blindHigh: blind.byRisk.critical + blind.byRisk.high,
30
+ premiseGaps: premise.available ? premise.gaps.length : 0,
22
31
  });
23
- const cov = Math.round(blind.coverage.ratio * 100);
32
+ // null = measured-but-unknown (0/0): the JSON keeps the null, the console says the word
33
+ const cov =
34
+ blind.coverage.ratio == null ? null : Math.round(blind.coverage.ratio * 100);
24
35
  const { state, message, color } = badgeFor(verdict, { coverage: blind.coverage.ratio });
25
36
 
26
37
  const svg = renderBadge('SPARDA', message, color);
@@ -52,7 +63,7 @@ export async function runBadge(opts) {
52
63
  const shields = `https://img.shields.io/badge/SPARDA-${encodeURIComponent(message)}-${color.slice(1)}`;
53
64
 
54
65
  console.log(
55
- `\n✓ Badge written: ${relSvg} (${state} · ${report.routes} routes · ${cov}% coverage)`,
66
+ `\n✓ Badge written: ${relSvg} (${state} · ${report.routes} routes · ${coveragePct(blind.coverage.ratio)} coverage)`,
56
67
  );
57
68
  console.log(`\nPaste into your README:\n`);
58
69
  console.log(` ![SPARDA](./${relSvg})`);