loopback-mcp-server
Advanced tools
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 @@ |
+67
-1
@@ -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 @@ } |
+11
-4
@@ -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 |
+1
-1
@@ -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 { |
+26
-0
@@ -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 @@ ``` |
+6
-2
@@ -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
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
856797
2.14%6873
1.46%506
5.2%