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

Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:19p8wzxghh is past its budget: trevato/forge: "the gathered tools > docs/MCP.md's tool table is current: run `bun scripts/mcp-tools.ts`" failed on 3 p… #6941

Merged opened this

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

Merged

scout/forge:scout/6885-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 3 commits fork 2 threads · 2 unresolved

  • Merged. scout/6885-sentinel-shared-01m23vygyffx5hg2f5vm8r74 was merged into main as a3e1957.

Review

Sign in to review this change.
4 files changed +19 −3
scripts/mcp-tools.ts +2 −1
@@ -14,6 +14,7 @@
1414 */
1515 import { readdirSync, readFileSync, writeFileSync } from "node:fs";
1616 import { join } from "node:path";
17 import { byName } from "../src/http/mcp/shared.ts";
1718
1819 const ROOT = join(import.meta.dir, "..");
1920 const DIR = join(ROOT, "src/http/mcp/tools");
@@ -44,7 +45,7 @@ type Documented = { readonly name: string; readonly doc?: string; readonly write
4445 export function tableText(tools: readonly Documented[]): string {
4546 const rows = [...tools]
4647 .filter((t) => t.doc !== undefined)
outdated

No test pins this order. Add a tableText case with mixed case (b, B, a → B, a, b) that fails under localeCompare. Share this comparator with src/http/mcp/tools.ts and don't write it twice.

47 .sort((a, b) => a.name.localeCompare(b.name))
48 .sort(byName)
4849 .map((t) => `| \`${t.name}\` | ${t.doc} | ${t.writes ? "yes" : ""} |`);
4950 return [BEGIN, "", "| tool | does | writes |", "| ---- | ---- | ------ |", ...rows, "", END].join("\n");
5051 }
src/http/mcp/shared.ts +4
@@ -227,3 +227,7 @@ export function boardFailed(e: BoardError): Answer {
227227 if (typeof e === "object") return bad("refused", e.refused);
228228 return bad(e, BOARD_NOT_SAVED);
229229 }
230
231 /** Orders by name's code points, never the machine's locale: the tool list and its docs table read the same everywhere. */
232 export const byName = (a: { readonly name: string }, b: { readonly name: string }): number =>
233 a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
src/http/mcp/tools.ts +2 −2
@@ -3,7 +3,7 @@
33 * gathered by the generated `tools/index.ts` (`bun scripts/mcp-tools.ts`).
44 * A new tool is a new file there, so pulls adding tools do not collide (#5924).
55 */
6 import type { Tool } from "./shared.ts";
6 import { byName, type Tool } from "./shared.ts";
77 import * as each from "./tools/index.ts";
88
9 export const TOOLS: readonly Tool[] = Object.values(each).sort((a, b) => a.name.localeCompare(b.name));
9 export const TOOLS: readonly Tool[] = Object.values(each).sort(byName);
outdated

This is the same inline comparator as in scripts/mcp-tools.ts. Use one exported byName in both places, and add a test that TOOLS is sorted by code point.

test/mcp-tools.test.ts +11
@@ -50,6 +50,17 @@ describe("the gathered tools", () => {
5050 expect(md).toBe(withTable(md, tableText([...TOOLS, REPORT_TOOL])));
5151 });
5252
53 test("are ordered by code point, not the machine's locale", () => {
54 const names = TOOLS.map((t) => t.name);
55 expect(names).toEqual([...names].sort());
56 const rows = tableText([
57 { name: "b", doc: "x", writes: false },
58 { name: "B", doc: "x", writes: false },
59 { name: "a", doc: "x", writes: false },
60 ]).split("\n").filter((l) => l.startsWith("| `"));
61 expect(rows.map((l) => l.split("`")[1])).toEqual(["B", "a", "b"]);
62 });
63
5364 test("a new file is one new line in the index, touching no other", () => {
5465 const before = indexText(["a", "c"]).split("\n");
5566 const after = indexText(["a", "b", "c"]).split("\n");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs