🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

loopback-mcp-server

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

loopback-mcp-server - npm Package Compare versions

Comparing version
0.9.2
to
0.9.3
public/dashboard/assets/index-r3d60Fjf.js

Sorry, the diff of this file is too big to display

+176
-1

@@ -8,2 +8,177 @@ # Changelog

## [0.9.3] — accuracy pass: a red CI nobody was watching, and docs nothing checked
### Fixed — CI had been red for six days, and three releases shipped on top of it
`ci.yml` failed on **sixteen consecutive runs**, from 2026-07-27 (`f7dfa27`)
through 2026-08-02. Last green was `623aa05`. 0.9.0, 0.9.1 and 0.9.2 were all
published inside that window, so all three shipped two real WCAG 1.4.4/1.4.10
failures as product defects, not just a red badge.
The a11y gate had **never passed on CI at all**: it was added in `d2ca476` and
pushed in a batch with 27 other commits, so its first run was already red.
The bug was real and one line. `Updated` rendered `item.updated_at` as a bare
ISO timestamp with no `break-all`, while every other value in that card
(project, source/reporter, assignee, route) had one. An ISO string is a single
24-char token with no break opportunity, so at 200% text zoom it set a 236px
min-content floor that the section's `min-w-0` could not shrink. On macOS that
lands at 301px and passes a 320px viewport; on CI's Linux font metrics it
measures ~325px — exactly the `{"s":325,"c":320}` in every failing run, and why
it never reproduced locally. Now clears 320px with 40px of headroom.
### Added — `release-preflight`: publishing requires a green, pushed, tagged commit
The gates verify the source. `verify:release` deliberately runs *after* a
publish, against published artifacts, where npm's immutability means it can only
report damage. Nothing ever asked the question in between, which is how a red
`main` stayed publishable three times.
`release-preflight` runs as `prepublishOnly`: clean tree, commit pushed, a
completed successful `ci.yml` run for that exact sha, and a `v<version>` tag
pointing at it. It **fails closed** — an unreachable GitHub is a refusal, not a
pass, since "unknown" is the state that let the last three releases through.
`LOOPBACK_ALLOW_RED_CI=1` is the explicit, auditable override.
Two traps found by running it rather than reading it: the GitHub API matches
`head_sha` on the full 40-char sha only, so an abbreviated sha returns an empty
run list — indistinguishable from "never tested"; and a fail-closed gate sails
through a canary that only checks for a non-zero exit, so the sweep asserts
**both** directions against two immutable commits (`10a1c6d` red must fail,
`623aa05` green must pass).
Tags `v0.9.0`, `v0.9.1` and `v0.9.2` were created retroactively. Tag discipline
had stopped exactly when public publishing began: everything from v0.3.0 to
v0.8.0 is tagged, and the only three versions that ever reached npm were not.
### Added — `docs-facts-gate`: the numbers in the docs are re-derived from the code
Every structural gate was green while the docs drifted, because none of them
ever read a *claim*. Everything that had rotted was a hand-typed figure:
- the widget was quoted **four** ways — 46KB/15KB, 57KB/19KB, 57,183 B, ~29KB —
against a real 59,443 B, and two separate commits each claimed to have
"corrected the widget size" while fixing one occurrence out of two;
- "The MCP bus — 10 tools" sat above a 9-row table, missing
`loopback_update_feedback`, which the 0.8.0 entry below announced;
- the HTTP surface table omitted all three attachment endpoints;
- the shadcn section documented two registry items and shipped three;
- the repo map claimed `init` writes `.claude-plugin/` (it does not) and that
`init-gate` re-renders it (it asserts a version), and omitted `.github/` and
`.impeccable/`.
Measuring it exposed a smaller error inside the bigger one: "19,751 B gzipped on
the wire" was measured with the `gzip(1)` CLI, but the server serves `gzipSync`
at zlib's default level — 19,770 B. Three plausible numbers for one file, so the
gate measures it the way `src/http.ts` actually produces it.
The gate also caught a security claim CI never could: the README said "Three
endpoints stay open on a LAN bind" and listed three, while `requireAuth` lets
four through — `GET /health` was open in the code and absent from the table. It
now derives the open set from `requireAuth` itself.
And then the canary caught **this gate** being decorative, which is the whole
argument for having one. The first version substring-searched the table for each
open path, so the canary's mutation — renaming the row to `/health-REMOVED` —
still *contained* `/health` and the check passed with its subject broken. It now
parses the path out of each row's `METHOD /path` cell and compares sets in both
directions, so a renamed, missing or invented row all fail.
### Fixed — the README's shadcn section was mangled, on GitHub and on npm
A sentence cut off mid-clause, a contradictory replacement pasted over it, and
an orphan `free).` fragment. Live on `main` and in the published 0.9.2 readme,
which is why this needed a release rather than a docs commit.
### Fixed — `link-gate`: the link check had never checked a link
The CI step had been green since the day it was added while scanning **zero**
links, for two independent reasons: `linkinator README.md docs integrations`
silently ignores every path after the first, and `--skip "127.0.0.1|…"` matched
the root linkinator serves the files from, so the crawl never started. That one
pattern took the scan from 24 links to 0, and "Successfully scanned 0 links"
exits 0. The gate now asserts a **minimum link count per target**, because the
failure was never a broken link — it was an empty crawl.
### Added — `init-gate` covers the seam between the two canonical sources
Every parity check ran from one canonical source down to its renderings.
Nothing compared the two canonical sources to *each other*, and
`integrations/instructions-src.md` says outright that the skill body "mirrors
this text and must be updated with it" — a manual sync, and the last drift class
with no check. They are deliberately not byte-identical, so the invariant is the
loop: both must drive the same tools in the same order.
### Fixed — two bugs found by driving the built CLI, not by reading it
`--version` printed nothing and **hung**. It was not a known flag, so it fell
through to the default branch, started a stdio server and waited on a stdin that
a human terminal never closes — after opening the user's real
`~/.loopback/loopback.db` on the way. Any typo did the same, silently. argv is
now validated *before* the store is constructed: `--version`/`-v` print the
version and exit 0, anything unrecognised names itself on stderr and exits 1,
and neither touches a database. The e2e check asserts that last part by pointing
the run at a directory that has to stay empty — an exit code alone would not
have caught the database being opened.
A whitespace-only title was accepted while an empty string was correctly
rejected: `" "` is three characters, so it cleared `min(3)`, and the queue
could hold an item nothing could act on. Length checks now run on the trimmed
value across the fields where a blank is meaningless — title, project, claiming
agent, comment author and body, on both submit and update — which also
normalises the padding people paste into slugs instead of storing it.
Both are canaried; the sweep is 30.
### Fixed — smaller corrections
- `docs/05-surface-compatibility.md` stated three different LAN-auth realities
in one document; the bearer token shipped, so the "next security milestone"
text is gone.
- `docs/ROADMAP.md` was stale on its own date, the npm version and the canary
count, and claimed "Open: **Nothing**" while CI was red.
- CHANGELOG dated 0.9.1 to the version-bump commit (2026-07-27) rather than its
actual npm publish (2026-08-01) and registry listing (2026-08-02).
- `integrations/claude.md` labelled an npm install "zero-install from GitHub".
- `server.json` had `websiteUrl: null`, so the registry entry carried no link.
- README overstated capture: buffers hold 30 console lines and 30 network calls,
but a filed report carries the most recent **15 of each**.
- `dashboard/tsconfig.tsbuildinfo`, tsc's incremental cache, was tracked.
- Added `CONTRIBUTING.md`, `SECURITY.md` and issue/PR templates — community
health was 42%.
### Fixed — the hub exited 0 on a taken port
A second `--http --port <taken>` printed its success banner and exited **0**,
having served nothing: express's listen callback fires even when the bind failed
(with `address() === null`), and an unhandled `error` on the underlying server
is not a throw. For a tool people habitually start in a second terminal, silence
is the worst possible outcome. Now exits 1, names the port, suggests the next
free one and `lsof`, and suppresses the banner it was about to contradict.
Found in passing by an agent designing an unrelated check.
### Added — release verification covers what it always claimed to
`npm run verify:release` went from 24 checks to **63**, closing the audit
remainder:
- **The hub actually starts.** The section has been titled "npm → init → hub →
MCP" since it was written and never made an HTTP request. It now serves
`/widget.js`, `/queue`, the hashed dashboard assets (read out of the served
shell, so they cannot go stale), and `POST /ingest`, asserting on CONTENT —
a stub `widget.js` answers 200 with perfectly valid empty JavaScript, so only
content can catch a `files`-whitelist miss. Canaried by stubbing the widget
inside the installed tree: `200, 7 B`, exactly one check red.
- **The plugin channel is fetched from GitHub**, the ref adopters actually
clone — previously the only channel read from the developer's own tree.
- **The registry listing is launched, not just compared.** `Package.transport`
is `anyOf(stdio|streamable-http|sse)`, so a wrong transport is schema-VALID
and `mcp-publisher validate` cannot reject it.
- **The documented npx onboarding path** is exercised against the published
package, not just the local-bin variant.
- **All nine files `init` writes** are checked, not the five the loop carried
under a comment claiming it covered every one.
- **Diagnostics:** the child's stderr is captured and the line that *names* the
failure is preferred over a blind tail (which was returning `code: '` from a
serialised Error). The cold handshake is guarded, so a dead install is one red
check instead of a lost run that skipped two whole channels.
Two of these were caught being decorative by their own canaries before landing —
a port-collision mutation that fell through to a branch whose message still
matched, and a collision probe pointed at a `0.0.0.0` hub from a `127.0.0.1`
client, which never collided at all.
The canary sweep ends this release at **30** checks (20 before the gates above),
and every gate added here is canaried in both directions where it can refuse.
## [0.9.2] — 2026-08-02

@@ -84,3 +259,3 @@

## [0.9.1] — 2026-07-27 — published to npm and the MCP Registry
## [0.9.1] — 2026-08-01 — published to npm; listed on the MCP Registry 2026-08-02

@@ -87,0 +262,0 @@ ### Added — MCP Registry identity (`mcpName` + `server.json`)

+1
-1

@@ -25,3 +25,3 @@ # Loopback Design System v0

dependencies and never will be. The widget is injected into arbitrary host
pages and must stay a single ~29KB dependency-free file; the server must stay
pages and must stay a single ~58KB dependency-free file; the server must stay
`npx`-installable with `tsc` as its only build.

@@ -28,0 +28,0 @@

@@ -12,2 +12,3 @@ #!/usr/bin/env node

* --port <n> HTTP port (default: $LOOPBACK_HTTP_PORT or 7077)
* --version print the version and exit
* --help usage

@@ -32,2 +33,32 @@ */

}
/**
* Flags that take a value, so the token after them is data and not an argument.
* Kept next to the boolean set because getting these two out of sync is how an
* argument checker starts rejecting valid input.
*/
const VALUE_FLAGS = new Set(["--db", "--port", "--host"]);
const BOOL_FLAGS = new Set(["--http", "--help", "-h", "--version", "-v"]);
/**
* Anything in argv that is not a known flag or the value of one.
*
* Without this, an unrecognised argument fell through to the default branch and
* silently started a stdio server: `loopback-mcp-server --version` printed
* nothing, hung forever waiting on a stdin that a human terminal never closes,
* and opened the user's real ~/.loopback/loopback.db on the way. A typo did the
* same. Validation therefore has to run BEFORE the store is constructed.
*/
function unknownArgs(argv) {
const unknown = [];
for (let i = 0; i < argv.length; i++) {
const arg = argv[i];
if (VALUE_FLAGS.has(arg)) {
i++; // skip its value
continue;
}
if (BOOL_FLAGS.has(arg))
continue;
unknown.push(arg);
}
return unknown;
}
const USAGE = `loopback-mcp-server v${SERVER_VERSION} — feedback bus for coding agents (MCP)

@@ -47,2 +78,3 @@

# onboard the current repo (AGENTS.md, skills, MCP configs ×3)
loopback-mcp-server --version # print the version and exit

@@ -67,2 +99,6 @@ Environment:

}
if (hasFlag("--version") || hasFlag("-v")) {
console.log(SERVER_VERSION);
return;
}
if (hasFlag("--help") || hasFlag("-h")) {

@@ -72,2 +108,10 @@ console.log(USAGE);

}
// Before the store: an unknown argument must not open the user's database.
const unknown = unknownArgs(process.argv.slice(2));
if (unknown.length) {
console.error(`loopback-mcp-server: unknown argument${unknown.length > 1 ? "s" : ""} ${unknown
.map((a) => `'${a}'`)
.join(", ")}\nRun 'loopback-mcp-server --help' for usage.`);
process.exit(1);
}
const dbPath = argValue("--db") ??

@@ -88,3 +132,8 @@ process.env.LOOPBACK_DB ??

const app = createHttpApp(() => buildServer(store), store, { host, port, token });
app.listen(port, host, () => {
const httpServer = app.listen(port, host, () => {
// The callback fires even when the bind FAILED, with address() === null,
// so an unguarded banner announced a healthy server moments before the
// error handler below exited 1 — the two lines contradicted each other.
if (httpServer.address() === null)
return;
console.error(`loopback-mcp-server v${SERVER_VERSION} on http://${host}:${port}/mcp (db: ${dbPath})`);

@@ -103,2 +152,19 @@ if (token) {

});
// Without this the process exits 0 on a taken port, having printed its
// success banner and served nothing: express's listen callback still fires
// (with address() === null), and an unhandled 'error' on the underlying
// server is not a throw. Measured — a second `--http --port 7077` looked
// exactly like a clean start. Silence on a port collision is the worst
// outcome for a tool people run in a second terminal.
httpServer.on("error", (err) => {
if (err.code === "EADDRINUSE") {
console.error(`Port ${port} is already in use — another Loopback (or something else) is on it.\n` +
` Use a different port: loopback-mcp-server --http --port ${port + 1}\n` +
` Or find the process : lsof -i :${port}`);
}
else {
console.error(`Could not listen on ${host}:${port} — ${err.message}`);
}
process.exit(1);
});
return;

@@ -105,0 +171,0 @@ }

@@ -129,4 +129,9 @@ /** Zod schemas (raw shapes) for Loopback tool inputs and the HTTP /ingest endpoint. */

export const submitShape = {
// .trim() runs BEFORE the length check, so " " is rejected rather than
// counted as three characters. Without it a blank title cleared min(3) and
// filed an item nothing in the queue could act on. It also normalises the
// padding people paste into slugs and agent names.
project: z
.string()
.trim()
.min(1)

@@ -138,2 +143,3 @@ .max(100)

.string()
.trim()
.min(3)

@@ -244,2 +250,3 @@ .max(200)

.string()
.trim()
.min(1)

@@ -269,4 +276,4 @@ .max(100)

...idShape,
author: z.string().min(1).max(100).describe("Comment author, e.g. 'claude-code' or 'dj'"),
body: z.string().min(1).max(10000).describe("Comment text (markdown ok)"),
author: z.string().trim().min(1).max(100).describe("Comment author, e.g. 'claude-code' or 'dj'"),
body: z.string().trim().min(1).max(10000).describe("Comment text (markdown ok)"),
};

@@ -308,7 +315,7 @@ export const linkChangeShape = {

export const updateShape = {
title: z.string().min(3).max(200).optional().describe("Corrected summary"),
title: z.string().trim().min(3).max(200).optional().describe("Corrected summary"),
body: z.string().max(10000).optional().describe("Corrected description"),
severity: severityEnum.optional().describe("Re-ranked severity"),
type: typeEnum.optional().describe("Corrected dimension"),
project: z.string().min(1).max(100).optional().describe("Move to another project slug"),
project: z.string().trim().min(1).max(100).optional().describe("Move to another project slug"),
route: z.string().max(500).optional().describe("Corrected route"),

@@ -315,0 +322,0 @@ author: z

@@ -6,3 +6,3 @@ /** MCP server definition: nine loopback_* tools over the feedback store. */

import { itemMarkdown, listMarkdown } from "./format.js";
export const SERVER_VERSION = "0.9.2";
export const SERVER_VERSION = "0.9.3";
function ok(text, structured) {

@@ -9,0 +9,0 @@ return {

@@ -225,2 +225,28 @@ /**

console.log("✅ strict tool inputs: a typo'd argument errors instead of filing the wrong thing");
// 7b. A blank title cleared min(3) because three spaces are three characters,
// so an item nothing could act on filed successfully. Length checks now run
// on the trimmed value.
const blankTitle = await callExpectError("loopback_submit_feedback", {
project: "demo-web",
type: "ui",
title: " ",
});
assert(/too_small|at least|validation/i.test(blankTitle), `a whitespace-only title is rejected (got: ${blankTitle.slice(0, 80)})`);
const blankProject = await callExpectError("loopback_submit_feedback", {
project: " ",
type: "ui",
title: "Real enough title",
});
assert(/too_small|at least|validation/i.test(blankProject), `a whitespace-only project is rejected (got: ${blankProject.slice(0, 80)})`);
// The same trim normalises padding rather than storing it.
const padded = await call("loopback_submit_feedback", {
project: " demo-web ",
type: "ui",
title: " Padded title needs trimming ",
});
const paddedId = padded.structured.id;
const paddedBack = await call("loopback_get_feedback", { id: paddedId, response_format: "json" });
const paddedItem = paddedBack.structured;
assert(paddedItem.title === "Padded title needs trimming" && paddedItem.project === "demo-web", `padding is trimmed, not stored (got title=${JSON.stringify(paddedItem.title)} project=${JSON.stringify(paddedItem.project)})`);
console.log("✅ blank-after-trim titles and slugs are rejected; padding is normalised");
await mcp.close();

@@ -227,0 +253,0 @@ console.log("\nALL SMOKE TESTS PASSED 🎉");

@@ -15,3 +15,3 @@ # Claude Code × Loopback

claude mcp add --scope user loopback -- node /ABS/PATH/loopback/dist/index.js
# or zero-install from GitHub:
# or zero-install from npm:
claude mcp add --scope user loopback -- npx -y loopback-mcp-server

@@ -18,0 +18,0 @@ ```

@@ -5,3 +5,3 @@ {

"author": "Dikshant Joshi",
"version": "0.9.2",
"version": "0.9.3",
"files": [

@@ -17,3 +17,3 @@ "dist",

],
"description": "Loopback \u2014 a project-tagged feedback bus for coding agents (Claude Code, Codex, Gemini CLI) over MCP. Capture UI/backend/usage/UX feedback once; let any agent claim, fix, and write the change back.",
"description": "Loopback — a project-tagged feedback bus for coding agents (Claude Code, Codex, Gemini CLI) over MCP. Capture UI/backend/usage/UX feedback once; let any agent claim, fix, and write the change back.",
"type": "module",

@@ -28,2 +28,4 @@ "main": "dist/index.js",

"prepare": "npm run build",
"prepublishOnly": "node scripts/release-preflight.mjs",
"preflight": "node scripts/release-preflight.mjs",
"start": "node dist/index.js",

@@ -43,2 +45,4 @@ "dev": "tsx src/index.ts",

"a11y-gate": "node scripts/a11y-gate.mjs",
"docs-facts-gate": "node scripts/docs-facts-gate.mjs",
"link-gate": "node scripts/link-gate.mjs",
"canary": "node scripts/canary-all.mjs",

@@ -45,0 +49,0 @@ "verify:release": "node scripts/release-verify.mjs"

@@ -18,3 +18,3 @@ <!doctype html>

</script>
<script type="module" crossorigin src="/dashboard/assets/index-s4C-2lo9.js"></script>
<script type="module" crossorigin src="/dashboard/assets/index-r3d60Fjf.js"></script>
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-BKg3DtAq.css">

@@ -21,0 +21,0 @@ </head>

+46
-21
# Loopback
**Pin feedback on your live app. Any coding agent fixes it. The pin turns green.**
**Pin feedback on the app you're building. Any coding agent claims it. The pin turns green when the fix is verified.**
Loopback is the interactive feedback layer between real product usage and your
coding agents: one script tag makes any web app commentable
(Vercel-toolbar-style toolbar, element-anchored pins), every pin auto-captures
coding agents: one script tag makes the app you're running locally commentable
(Vercel-toolbar-style toolbar, element-anchored pins; deployed public sites go
via the `/ingest` rails instead — see [surface compatibility](docs/05-surface-compatibility.md)), every pin auto-captures
the *functional* context — failing requests **with response bodies**, console

@@ -203,14 +204,21 @@ trail, the route journey that led there, LLM run metadata, typed repro steps —

npx shadcn@latest add https://raw.githubusercontent.com/joshidikshant/loopback/main/public/r/loopback-theme.json
npx shadcn@latest add https://raw.githubusercontent.com/joshidikshant/loopback/main/public/r/loopback-components.json
npx shadcn@latest add https://raw.githubusercontent.com/joshidikshant/loopback/main/public/r/loopback-widget.json
```
`loopback-theme` adds the `--lb-*` feedback status/severity tokens to your
`loopback-theme` ships the FULL shadcn theme contract (background, foreground,
primary, muted, ring, radius…) alongside the `--lb-*` domain tokens, so
installing it REPLACES your palette. If you only want the Loopback-specific
tokens, copy the `--lb-*` block out of `design/tokens.css` instead.
free). `loopback-widget` drops the capture widget into `public/`. Register
`"@loopback"` in your `components.json` to install by name and let an agent
with the shadcn MCP browse the registry.
Three items:
- **`loopback-theme`** ships the FULL shadcn theme contract (background,
foreground, primary, muted, ring, radius…) alongside the `--lb-*` feedback
status/severity tokens, so **installing it replaces your palette**. If you
only want the Loopback-specific tokens, copy the `--lb-*` block out of
[`design/tokens.css`](design/tokens.css) instead.
- **`loopback-components`** — vanilla CSS recipes for the shadcn component
vocabulary (`lb-btn`, `lb-badge`, `lb-card`…), for surfaces that want the
look without React.
- **`loopback-widget`** drops the capture widget into `public/`.
Register `"@loopback"` in your `components.json` to install by name and let an
agent with the shadcn MCP browse the registry.
## Where it works (surfaces)

@@ -233,3 +241,3 @@

~46KB (15KB gzipped) of dependency-free vanilla JS in a shadow-DOM host — it never fights
~58KB (19KB gzipped) of dependency-free vanilla JS in a shadow-DOM host — it never fights
your app's CSS or framework.

@@ -241,7 +249,8 @@

`usage` when AI context is present.
- **Functional context, always on**: ring buffers from page load — last 30
console lines (log/warn/error + window errors + unhandled rejections), all
fetch/XHR calls (url/method/status/ms), and for failures (status ≥ 400 or
network error) up to **2KB of response body** into
`extra.failed_responses`. Calls to Loopback itself are never recorded.
- **Functional context, always on**: ring buffers from page load keep the last
30 console lines (log/warn/error + window errors + unhandled rejections) and
the last 30 fetch/XHR calls (url/method/status/ms); a filed report carries
the most recent **15 of each**, plus — for failures (status ≥ 400 or network
error) — up to **2KB of response body** into `extra.failed_responses`. Calls
to Loopback itself are never recorded.
- **AI/automation context**: the nearest ancestor with

@@ -275,2 +284,3 @@ `data-loopback-context='{"run_id":...}'` is parsed into `extra.context`.

| `loopback_get_feedback` | Full item: all context + linked change + comment trail |
| `loopback_update_feedback` | Correct an item after filing: title, body, severity, type, project, or route — re-rank a severity, fix a mis-guessed type, move it to the right project |
| `loopback_claim_feedback` | **Atomic** claim; a conflict names the holder; `force` to take over; `open/triaged → in_progress` |

@@ -296,2 +306,5 @@ | `loopback_update_status` | `open → triaged → in_progress → fixed → verified \| wontfix`; note becomes an audit comment |

| `GET /feedback/:id` | One item with its full trail, as JSON |
| `POST /feedback/:id/attachments` | Attach a screenshot or asset to an item (`name`/`intent`/`target` as query params, body is the bytes) |
| `GET /blob/:id/:attachmentId` | Fetch one attachment's bytes |
| `DELETE /feedback/:id/attachments/:attachmentId` | Remove an attachment |
| `GET /widget.js` | The embeddable widget |

@@ -324,3 +337,3 @@ | `GET /health` | Liveness |

Three endpoints stay open on a LAN bind, deliberately:
Four endpoints stay open on a LAN bind, deliberately:

@@ -332,2 +345,3 @@ | Open | Why |

| `GET /feedback?view=pins` | The minimum needed to draw pins and show one turn green. A strict projection — no body, console, network, repro steps, comments or attachments — of what is already visible on the page. |
| `GET /health` | Liveness only — `{ok, name, version}`. A keep-alive probe cannot be made to carry a token. |

@@ -445,3 +459,3 @@ Everything else — the dashboard, full reads, every write, and `/mcp`, which

the ingestion boundary.
7. **Widget is 57KB / 19KB gzipped, not the ~10KB sketch** — ring buffers, failure-body
7. **Widget is 58KB / 19KB gzipped, not the ~10KB sketch** — ring buffers, failure-body
capture, live pins, repro steps, the route journey and the walkthrough earn

@@ -466,5 +480,12 @@ their bytes; still zero deps, one file, served pre-compressed with an ETag.

plugin/ Claude Code plugin (skill + MCP registration); repo doubles as its marketplace
scripts/ e2e.mjs · the six gates · canary-all.mjs · screenshot.mjs
scripts/ e2e.mjs · the seven gates · canary-all.mjs · screenshot.mjs
release-preflight.mjs (pre-publish) · release-verify.mjs (post-publish) · bump-version.mjs
docs/ the decision history (spec → memo → paths → technical path) + ROADMAP
assets/ the README screenshot (generated by scripts/screenshot.mjs)
.github/ ci.yml · canary.yml · readme-checks.yml · issue and PR templates
.impeccable/ design-detector config (the skill itself is not vendored — see .gitignore)
CONTRIBUTING.md how to run the gates, and why a new gate needs a canary case
SECURITY.md the trust boundary, what is deliberately open, and what is out of scope
CHANGELOG.md every release, with the reasoning that produced it
```

@@ -486,5 +507,9 @@

.mcp.json MCP server registration for this repo
.claude-plugin/ marketplace manifest (repo hosts its own plugin)
```
`.claude-plugin/` is the one root dot-directory `init` does **not** write: it is
the marketplace manifest for the plugin this repo hosts, maintained by hand and
bumped by `npm run bump`. `init-gate` asserts its version matches rather than
re-rendering it.
MIT © Dikshant Joshi

Sorry, the diff of this file is too big to display