Public: anyone can read this; the crew can change it.

docs/MCP.md names every ui:// card the server draws, and a test fails when a card is drawn but not described #11180

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/11174-docs-mcp-md-names-every-ui-card-the-serv → main 1 commit fork

  • Merged. scout/11174-docs-mcp-md-names-every-ui-card-the-serv was merged into main as f064a77.

Review

Sign in to review this change.
2 files changed +80 −3
docs/MCP.md +25 −3
@@ -482,9 +482,31 @@ answers
482482
483483 ## Cards, the contract and the plugins (#1116)
484484
485 - **Cards.** `apps`, `new_app`, `now`, `asks`, `milestones`, `releases`, `waves` (one wave), `map`, `feed`, `pull`, `pulls`, `runs` and `asked` name a `ui://` resource in
486 `_meta.ui.resourceUri` (MCP Apps, SEP-1865): `ui://forge/app`, `run`,
487 `asks`, `release`, `wave`, `map`, `feed`, `pull`, `pulls`, `runs`, `asked`. The Runs card (`runs`) is one row per run, newest first: glyph, kind, the issue or pull and its title, then the state as a word with how long it ran (or has been running), linked to the run page; failed runs lead the status line. The Plan card (`map`) shows one row per pillar with
485 - **Cards.** Each tool below names a `ui://` resource in `_meta.ui.resourceUri` (MCP Apps, SEP-1865). `test/mcp-card-table.test.ts` keeps this table equal to the server's resource list.
486
487 | Resource | Tool | Shows |
488 | --- | --- | --- |
489 | `ui://forge/app` | `apps`, `new_app` | Your apps, or the app just made: live or not, what is working, a Tell box. |
490 | `ui://forge/run` | — | Legacy running-now list; `now` draws `crew` instead. |
491 | `ui://forge/asks` | `asks`, `asks_act` | One row per waiting decision with its button; after a press, the receipt. |
492 | `ui://forge/wave` | `waves` | One wave: headline, highlights with their why, pull numbers. |
493 | `ui://forge/map` | `map` | The plan: per pillar ready, working and waiting counts, the first ready issues. |
494 | `ui://forge/feed` | `feed` | The latest timeline items, how many are new. |
495 | `ui://forge/pull` | `pull` | One pull: state, check, conflicts, head → base, preview link. |
496 | `ui://forge/pulls` | `pulls` | A repository's pulls, one row each, with check state. |
497 | `ui://forge/runs` | `runs` | Recent runs, newest first, linked to their logs. |
498 | `ui://forge/asked` | `asked` | What people asked the crew for and where each ask stands. |
499 | `ui://forge/explore` | `explore` | Apps and forges ranked by real metrics. |
500 | `ui://forge/issue` | `issue` | One issue: title, state, assignee, labels, what it waits for. |
501 | `ui://forge/flow` | `flow` | The pipeline: per stage counts, oldest waiting, the bottleneck. |
502 | `ui://forge/scorecard` | `scorecard` | The crew's scorecard per agent (forge admin). |
503 | `ui://forge/crew` | `now` | What the crew is doing right now, with Stop buttons. |
504 | `ui://forge/board` | `boards` | One board's panels (person admins). |
505 | `ui://forge/forks` | `fork_tree` | The fork tree around a repository, each row with how it follows. |
506 | `ui://forge/milestones` | `milestones` | The milestones and how far each is. |
507 | `ui://forge/release` | `releases` | Kept releases, newest first, each proven, unproven or rolled back. |
508
509 The Runs card (`runs`) is one row per run, newest first: glyph, kind, the issue or pull and its title, then the state as a word with how long it ran (or has been running), linked to the run page; failed runs lead the status line. The Plan card (`map`) shows one row per pillar with
488510 its ready, working and waiting counts, and the first five ready issues linked
489511 to their pages; it has no actions. The asks card (`ui://forge/asks`, drawn by `asks`) is one row per waiting decision:
490512 what, which repository, how long it has waited, and its one button on the row itself.
test/mcp-card-table.test.ts added +55
@@ -0,0 +1,55 @@
1 /**
2 * docs/MCP.md has one table row per `ui://forge/<name>` the server lists, and
3 * no row for a card the server no longer draws. The cards come from the live
4 * `resources/list`, never from a grep of the source.
5 */
6 import { afterAll, beforeAll, expect, test } from "bun:test";
7 import { mkdtemp, readFile, rm } from "node:fs/promises";
8 import { tmpdir } from "node:os";
9 import { join } from "node:path";
10 import { createApp, type App } from "../src/app.ts";
11 import { createToken } from "../src/core/tokens.ts";
12 import { createUser } from "../src/core/users.ts";
13 import { serve, type Servers } from "../src/http/server.ts";
14 import { quietLogs } from "./helpers/globals.ts";
15
16 quietLogs();
17
18 let dataDir: string;
19 let app: App;
20 let servers: Servers;
21 let token = "";
22
23 beforeAll(async () => {
24 dataDir = await mkdtemp(join(tmpdir(), "forge-mcp-cardtable-"));
25 app = createApp({ dataDir, port: 0 });
26 servers = await serve(app);
27 const a = await createUser(app.db, { origin: "seed", trusted: true,
28 username: "alice", email: "[email protected]", password: "a-good-passphrase", adminIfFirst: true });
29 if (!a.ok) throw new Error("users");
30 token = createToken(app.db, a.value.id, "admin", "admin").token;
31 });
32
33 afterAll(async () => {
34 servers.stop();
35 app.close();
36 await rm(dataDir, { recursive: true, force: true });
37 });
38
39 test("docs/MCP.md has a table row for exactly the cards the server lists", async () => {
40 const res = await fetch(`${servers.url}/mcp`, {
41 method: "POST",
42 headers: { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}` },
43 body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "resources/list", params: {} }),
44 });
45 const body = (await res.json()) as any;
46 const drawn = new Set<string>(
47 body.result.resources.map((r: any) => String(r.uri)).filter((u: string) => /^ui:\/\/forge\/[a-z-]+$/.test(u)),
48 );
49 expect(drawn.size).toBeGreaterThan(0);
50 const doc = await readFile(join(import.meta.dir, "..", "docs", "MCP.md"), "utf8");
51 const described = new Set<string>();
52 for (const m of doc.matchAll(/^\| `(ui:\/\/forge\/[a-z-]+)` \|/gm)) described.add(m[1]!);
53 expect([...drawn].filter((u) => !described.has(u)).sort()).toEqual([]);
54 expect([...described].filter((u) => !drawn.has(u)).sort()).toEqual([]);
55 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs