Repository navigation
Give loom the surfaces it was grading other libraries on - #4
Merged
Merged
Conversation
lib/sources.ts ranks five component libraries by how much of each one a machine can reach unattended. Three publish an llms.txt, one runs an MCP server, three install from a CLI. Everything loom knew about its own blocks, themes, tokens and exports was reachable only by a person clicking buttons in a browser, which put loom last on the list it wrote. This adds the three surfaces it was grading others on. No dependency was added for any of it. An MCP server, `npm run mcp`, over hand-written JSON-RPC on stdio. Five read-only tools: the component-source table, the sources for a given block, the 13 blocks with their variants, the six themes, and a theme's 17 tokens. Every answer is read from lib/plan.ts, lib/themes.ts and lib/sources.ts at call time rather than copied, so a tool cannot go on answering confidently after the table changes. A test calls every tool the server advertises, so a name added without a handler fails there instead of in front of an agent. The two answers that are not lookups are the ones worth care. An unrecognised block returns an empty list with an explanation rather than a fabricated row, and `theme_tokens` on an unknown name says which theme it actually returned — `themeVars()` falls back to forest, and an agent handed those values unlabelled pastes a green button in believing it asked for terminal. `/llms.txt`, generated per request from the same modules that serve the app. llmstxt.org allows links only inside H2 sections, so the blocks, themes, tokens, exports and sources are prose — inventing a /docs/blocks link to satisfy the shape would send an agent to a 404. Other libraries' endpoints appear as code spans, so every followable url points at the deployment the file came from. The export list is the one thing in that file written by hand rather than derived, and it went stale on its first opportunity: bundle.zip shipped in this same change and the list still said five formats. Rather than reach from a pure builder into a React component to recover six strings, the test now reads the `download` attributes out of app/page.tsx and fails when the two disagree. Written twice, checked once. A sixth export, `bundle.zip`, carrying all five artifacts plus a `CLAUDE.md` holding `@AGENTS.md` — closing a gap the generated brief admits in its own first paragraph, since Claude Code reads CLAUDE.md and not AGENTS.md. The archive is a hand-written STORE zip: table-driven CRC-32, sizes measured on UTF-8 bytes rather than string length so a page of CJK copy does not produce an archive that opens locally and fails elsewhere, the UTF-8 filename flag set, and a fixed DOS timestamp so two exports of the same page are byte-identical. Verified with the system unzip, not only against its own reader. docs/CONTRIBUTING.zh.md was missing the component-source section added last change as well as the new MCP one, and carried a test count three changes out of date. Both fixed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
lib/sources.tsranks five component libraries by how much of each one a machine can reach unattended. Three publish anllms.txt, one runs an MCP server, three install from a CLI. Everything loom knew about its own blocks, themes, tokens and exports was reachable only by a person clicking buttons in a browser, which put loom last on the list it wrote.This adds the three surfaces it was grading others on. No dependency was added for any of it — the JSON-RPC framing, the llms.txt builder and the ZIP writer are all in-repo.
MCP server
npm run mcp, hand-written JSON-RPC 2.0 over stdio. Driven for real:Four messages in, three responses out — the notification is correctly unanswered.
Every answer is read from
lib/plan.ts,lib/themes.tsandlib/sources.tsat call time rather than copied, so a tool cannot go on answering confidently after a table changes. A test calls every tool the server advertises, so a name added without a handler fails there rather than in front of an agent.The two answers that are not lookups got the most care:
theme_tokenson an unknown name reports which theme it actually returned.themeVars()falls back toforest, and an agent handed those values unlabelled pastes a green button in believing it asked forterminal.Protocol revision
2025-11-25, chosen deliberately:/specificationpoints at2026-07-28, but that revision removes theinitializehandshake, and the handshake is what Claude Code and Codex open with.initializeechoes back any of2025-11-25 / 2025-06-18 / 2025-03-26 / 2024-11-05a client asks for.To use it:
claude mcp add loom -- npm run --silent mcpfrom a checkout./llms.txtGenerated per request from the modules that serve the app, so it cannot drift.
llmstxt.org allows links only inside H2 sections, so blocks, themes, tokens, exports and sources are prose — inventing a
/docs/blockslink to satisfy the shape would send an agent to a 404. Other libraries' endpoints appear as code spans, so every followable url points at the deployment the file came from.## Optionalis last, since the spec defines that name as the section to skip under context pressure.The one hand-written list in that file went stale on its first opportunity.
bundle.zipshipped in this same change and the export list still said five formats — exactly the failure the rest of the file is built to avoid. Rather than reach from a pure builder into a React component to recover six strings, the test now parses thedownloadattributes out ofapp/page.tsxand fails when the two disagree. Written twice, checked once.bundle.zipA sixth export carrying all five artifacts plus a
CLAUDE.mdholding@AGENTS.md— closing a gap the generated brief admits in its own first paragraph, since Claude Code reads CLAUDE.md and not AGENTS.md.Hand-written STORE zip. Verified with the system
unzip, not only against its own reader, on a page generated from a Chinese prompt:Sizes are measured on UTF-8 bytes rather than string length, so a page of CJK copy does not produce an archive that opens on the author's machine and fails on someone else's — there is a test that states that bug directly. The DOS timestamp is fixed, so two exports of the same page are byte-identical, and the test file parses the central directory with its own reader and recomputes CRCs with an independent implementation rather than importing the writer's table.
Verification
End to end through the real editor with Playwright: six export buttons render,
bundle.zipdownloads, and the archive above is what came out of it.51 new tests, every one mutation-verified — the implementing agents report the specific mutations tried, including several that stayed green on the first pass and led to a stronger test rather than being quietly dropped.
Also fixed
docs/CONTRIBUTING.zh.mdwas missing the "Adding a component source" section added in #3, had no "Adding an MCP tool" section, and carried a test count three changes out of date. All three fixed.Still not in scope
npm run devunder Next 16 writes an untrackedAGENTS.mdandCLAUDE.mdat the repo root. Next reports it is disabled withagentRules: falseinnext.config. Not changed here — it is a config decision, and this branch is already carrying three features.