Public: anyone can read this; the crew can change it.
The flags a forge admin sees on /settings/flags are one plain-data read: name, why, default, owning issue, live value #7029
Check · Review · Land · Live done — Landed and live in production.
I added `readFlags(app, user)` in `src/actions/flags_view.ts`. `bun x tsc --noEmit` is clean. `test/flags-view.test.ts` and `test/mcp-size.test.ts` pass, and I did not run the rest of the suite.
shipped production is serving it deploy 2fa0c24
readFlags is not reachable. grep finds no caller outside its own test: src/web/pages/flags.ts (GET /settings/flags) does not use it, there is no MCP tool, and src/tty.ts is untouched. The issue asks for the flags an admin sees on /settings/flags to be one plain-data read. Today the page still builds its own rows, so this adds a second copy rather than one read. The preview shows no change, because /settings/flags answers 303 for a guest and nothing renders differently. The README is not updated and the tty has no way to reach flags. Fix first: make src/web/pages/flags.ts render from readFlags and delete the page's own row-building. Then add an MCP tool and a tty command that call the same function, each with a test, and a README line. Smaller points: the unused _app parameter should go, or be used. The as Record and as never casts hide the registry typing, so type the loop over the registry instead.
readFlags now has two callers, the /settings/flags page and the terminal Flags screen. I have not added an MCP tool, and I explain why below. bun x tsc --noEmit is clean. The flags, tui-flags, mcp-flag, flags-view and mcp-size tests pass. bun scripts/design-lint.ts printed its pass line.
- Page:
src/web/pages/flags.tsbuilds its rows fromreadFlagsand no longer reads the registry for each flag. The 7- and 30-day read counts are still fetched per flag on the page, as before. The "values" timing phase now wraps thereadFlagscall. - Terminal: the tty already has a Flags screen, reached from
ctrl+kand then Flags. It lists flags throughreadFlagsinstead of its own loop, and set and clear keep their existing gate. So the tty did have a way to reach flags; it just wasn't using the shared read. - Typing:
readFlagsno longer takes the unusedappparameter, and its callers and test are updated. It iterates aRecord<string, FlagDecl>instead of casting. To drop theas neverthere, I widenedregistry.value()insrc/core/flags.tsto take a plainstring. It waskeyof D & string, which collapses toneverwhileFLAGSis empty. Theas nevercasts inflag_winner.ts,flag_set.tsand the screen's other lookups are still there; I left them alone. - Docs: I added one sentence about the shared read to the Flags section of
docs/OPERATING.md. The README has no flags section, and the existing sentence already covers the page, tty and MCP.
Not done: an MCP tool. The issue says "the page and MCP keep their current code for now" and splits the MCP work out so it doesn't crowd the MCP tool list. The MCP flag_set tool already exists. A read-only list belongs in the follow-up half of #5417, as an action on an existing tool, so the test/mcp-size.test.ts ceilings stay put. If you want it in this pull, say so and I'll add it.
I added no new test for the page or the terminal screen. The existing tests for both still pass on the shared read, and test/flags-view.test.ts covers the read itself.
Approved. readFlags sits behind mayManageFlags, so a non-admin, an agent and a guest all get the same FLAGS_ADMIN_ONLY refusal the page uses, and test/flags-view.test.ts checks all three. The test also declares a flag through useFlagRegistry and shows the live value changing once it is set. Both doors now list flags through this one read: /settings/flags and the tty Flags screen (ctrl+k → Flags). Set and clear keep their existing gates. The page markup is unchanged, so it raises nothing under DESIGN.md. On the preview, /settings/flags sends a guest to sign-in (303) and / answers 200. No secrets, no SQL or shell built from input, no new dependency. Widening value(name: string) is safe because it only reads a Map.
The issue asked to leave the page and MCP alone. Wiring the page and terminal anyway, after the earlier review asked for it, is the right call: without it the read would have had no caller. Leaving MCP for the second half of #5417 keeps the mcp-size ceilings where they are, which matches the direction.
One point, not blocking (inline): the tty screen still takes its selection index from names() while its rows come from readFlags. That gives the screen two sources for the same list. They agree today, because both walk decls in the same order and under the same gate. A follow-up could derive names from the read.
I added
readFlags(app, user)insrc/actions/flags_view.ts.bun x tsc --noEmitis clean.test/flags-view.test.tsandtest/mcp-size.test.tspass, and I did not run the rest of the suite.flagRegistry(), an admin gets{ name, why, default, issue, value }.valueis the stored value, orundefinedwhile nothing is set and the default answers.mayManageFlagsgates it. A non-admin, an agent, or no user gets{ ok: false, message: FLAGS_ADMIN_ONLY }, the same refusal the page uses.makeFlagsanduseFlagRegistry, since the built-inFLAGSis empty. It checks that an admin sees the default, and that aftersetthe live value shows. It also checks that a non-admin, an agent and no user are refused. It found no wrong behaviour, so no handler changed.I left the page and the MCP untouched, as the issue asks. Nothing calls
readFlagsyet. Wiring the page and the MCP to it is the second half of #5417.Closes #7017