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

README is 300 bytes from its 60 KB budget, so every README addition turns its pull red: move Develop and What works today into docs/ (target 45 KB) #6736

Merged opened this in answer to

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

Merged

scout/forge:scout/6735-readme-is-300-bytes-from-its-60-kb-budge → main 4 commits fork 2 threads · 2 unresolved

  • Merged. scout/6735-readme-is-300-bytes-from-its-60-kb-budge was merged into main as 8feb4a6.

Review

Sign in to review this change.
2 files changed +128 −123
README.md +3 −123
@@ -358,133 +358,13 @@ work here.
358358
359359 ## What works today
360360
361 - **A repository's runs page filters by state**: a row of links (All, then each
362 state) above the list, or `/:owner/:name/runs?state=failed` by hand. An unknown
363 state is ignored, and the word filter and the pager keep the chosen state. The
364 terminal does the same: `H` in a repository opens its runs, and `s` cycles
365 All, Failed, Running, Succeeded.
366 - **A run page's live log** rides the page's one `/events` connection (its
367 `run.log` frames); it opens no stream of its own. Known gaps, both silent: a
368 frame the server cuts for size (64 lines or 16 KB) loses its omitted lines
369 until the next reconnect's catch-up read, and a run whose log was already cut
370 to its last 512 KB at render gets no catch-up read. The page says
371 "Reconnecting" while the connection is down.
372
373 - **Cut a wave** — Settings → *Cut a wave* (repository admins, same-origin
374 POST; web-only, the terminal's settings hand over to the web) opens a pull
375 adding `waves/<next>.md`, listing the pulls merged since the last wave and
376 not already in an earlier wave. The headline and highlights are `TODO`
377 placeholders; no model is called. It refuses, writing nothing, when an
378 earlier wave does not parse (naming the file) or nothing new has merged.
379 `/:owner/:name/waves` lists the waves on the default branch, newest version
380 first, each line opening `/:owner/:name/waves/<version>` (headline, date,
381 highlights with why, pulls as links; linked from that settings card); in the
382 terminal, `V` in a repository opens the same list and Enter (or a click) on
383 a row opens the same wave as plain text (a live browse of another forge says
384 waves are only read on this forge).
385 `/:owner/:name/waves.atom` is the same list as an Atom feed (newest 50, each
386 entry the headline, its highlights and a link to the wave), advertised from
387 the waves page; a private repository's feed is a 404 to anyone who cannot
388 read it.
389
390 - **Accounts & access** — register/sign in, personal access tokens with coarse
391 scopes (ro/rw/admin), public and private repositories.
392 - **Git** — push and pull over HTTPS **and SSH**, browse code (tree, blob with line anchors,
393 commit history) with README rendering and **syntax highlighting**, protected
394 default branches (force-push and deletion refused, honest fast-forwards
395 allowed).
396 - **System memory** — `/system`, for a forge admin who is a person (anyone else gets 403): the forge's RSS beside its JS heap (`heapStats()` from bun:jsc), hourly mean and max for the last 24 h, drawn on the server. Sampled once a minute into `memory_hour` in `app.db` (a week kept). RSS rising while the heap stays flat is native memory growth. In the terminal the same admin has a **System memory** command in the palette: the last 24 hours as text rows (hour, RSS mean/max, heap mean/max in MiB), read-only. The account menu links `/system` for that admin.
397 - **Flow alarms** — a firing alarm (red main, a stuck approved pull, an idle crew, no release for 90 min) shows as one linked line above home's feed and on `/boards` (admin), under the vitals line of the terminal's Flow dialog and home, as one phone notification when it starts (not repeated while it stands), and in the `flow` tool's `alarms` (MCP, `/api/v1/telemetry/flow`; an agent's cut to public repositories). Nothing firing draws nothing.
398 - **Boards** — `/boards` also shows the pipeline (a row per stage, the bottleneck named) and the held counts (why each autonomous repository's ready issues wait; private repositories included), for the same admin who is a person; they sit as two cards under the board list, not as panels of a board, because they are not saved queries. The terminal's Boards screen does not draw them: its door to the same rows is the **Flow** dialog. `/boards/data`, for a forge admin who is a person (anyone else
399 gets a 404 on the page): the query is the URL (`?source=runs&aggregate=count&by=day&fcolumn=state&fvalue=failed`),
400 drawn server-side as the chart chosen beside it (`chart=line|bar|area|number|table|heat`)
401 with its points in a table; a point opens the rows behind it, keeping your filter.
402 `series=<column>` splits each group by a second column: stacked area draws one band per
403 value (eight at most, the rest folded into "other"), the heat strip one row per value
404 (twelve at most) with time across, the table a series column; line, bar and number set
405 it aside. Number is the latest group's value over a sparkline of all of them.
406 `/boards` holds the boards themselves: a board is one JSON file
407 (`{title, range, refresh: off|10s|1m, panels: [{title, chart, query, w, h}]}`) in
408 `forge/boards`, a repo the forge owns and makes on the first boot with an admin; every save is a commit
409 by the admin who made it, and a file that does not validate is not shown. Make, rename,
410 duplicate, delete, add/edit/remove/reorder panels, or pin the query on `/boards/data`.
411 Any panel has **Pin to home** / **Unpin** on its board page (a same-origin POST); pins are per admin, kept in `board_pin` (slug + panel title + position, `boards/pins.ts`), and home draws them under the KPI row in that person's order with Up, Down and Unpin beside each (`web/studio/pinned.ts`). A pin whose panel is gone is skipped, does not count toward the limit of 24, and is dropped at that person's next pin; nobody sees another's pins. All doors go through `actions/board-pins.ts` (an admin who is a person; anyone else gets not-found):
412 - the page (Pin to home / Unpin on `/boards/:slug`, Up / Down / Unpin on home);
413 - the terminal: on a board's screen `[` `]` mark a panel, `p` pins or unpins it, `<` `>` move a pinned one; the dashboard lists the pinned panels by title;
414 - `GET /api/v1/boards/pins` (the list, in order) and `POST /api/v1/boards/pins` with `{"op":"pin|unpin|move","slug":"…","title":"…","by":-1|1}` (`by` for move only);
415 - the `board_pins` MCP tool (no `op` lists; `op` pin, unpin or move changes). `pins` is therefore not a board slug.
416 The first boot also writes the starting boards, **Usage**, **Serving**, **Crew**, **Releases**, **Apps**, **Security**, **Throughput**, **Reach** and **Forges**; Serving is the forge's event-loop lag p99 beside the checks and crew runs running, so checks' concurrency is raised on numbers. Apps' per-app panels show repo ids until a source exposes a safe name; Security (sign-ins, failed sign-ins, tokens by scope, admin actions, from the admin-only `signins`, `tokens` and `adminActions` sources and `usage`) shows no token text, hash, payload or IP; Throughput is items entering each stage from issue to live, per hour, from the `stages` source. Each is written once: `.offered` in that repo records them, so an edit stays and a deletion sticks.
417 A live board (any `refresh` but `off`; the form offers off or on, and `10s` and `1m` are still accepted and mean the same) redraws its grid when a run, pull, check, issue or usage event arrives, through the live-region swap
418 (`/boards/:slug/live`, etag'd, one fetch per burst). The same doors are in the API: `GET /api/v1/boards`, `GET|PUT|DELETE /api/v1/boards/:slug`, and each panel's data as JSON or CSV at `GET /api/v1/boards/:slug/panels/:id?from=&to=&step=5m&format=csv`, bucketed in SQL to the step and never cut short (`docs/APPS.md`); each panel's card links `json` and `csv` (a plain download link) beside `explore`
419 (an agent or anyone but an admin is 403; a panel whose query cannot run is refused with 422). The same four doors are MCP tools (`boards`, `board_write`, `board_delete`; `boards` with a `slug` reads one, and with a `panel` too that panel's data, with the same `from`, `to` and `step`) and, for the same person, a **Boards** screen in the terminal that lists boards and shows a board's panels as text, each with what its data holds now (a time panel's last bucket and step, another's row count). No dependency was added.
420 - **Parked work** — the terminal's Flow dialog says how many issues the crew parked (an open issue whose newest comment is the crew's `Parked by the crew` note) in the last 24 h and 7 d, and names the newest three; the MCP `flow` tool returns the same as `parked`. See `docs/TOUR.md`.
421 - **Issues** — labels, threaded comments, close/reopen, markdown.
422 - **Pull requests** — open from a branch, a **beautiful diff viewer** (syntax
423 highlighting composed with intra-line word highlighting, mobile-first,
424 collapsible files), merge-commit and squash strategies, conflict detection,
425 and merge commits kept reachable forever via `refs/pull/N/merged`.
426 - **Review comments** — anchored to a diff line by content hash, so a
427 force-push re-anchors them or marks them outdated, never misplaces them.
428 - **Forks** — alternates-backed (instant, storage-bounded), with cross-repo
429 pull requests (fork → upstream) and detach-before-delete safety.
430 - **Organizations & teams** — orgs own repos; teams grant read/write/admin
431 resolved through membership.
432 - **Live updates** — a server-sent-event stream turns comment threads live and
433 flags new activity, no refresh.
434 - **Commit pages** — every commit's diff through the same viewer.
435 - **Settings** — visibility, branch protection, and labels.
436 - **The check lane is visible.** Checks run one at a time; an admin's home
437 strip, the terminal dashboard's strip and `GET /api/v1/health` (`lane`) show
438 the queue depth, the running check and its age, the median of the last
439 twenty and the time to clear, and the 30-minute queue card names that cause.
440 - **A terminal UI** — `ssh` in for the whole forge: dashboard, code browsing
441 with syntax highlighting, issues and pulls with live-updating threads, the
442 diff viewer with inline review comments, merges, settings, orgs, tokens and
443 keys. Six themes, remembered per account.
444 - **Origins** — connect GitHub, Forgejo or Gitea (device code, PKCE, or a
445 pasted token), mirror repositories in with their issues and pulls, and write
446 through to the origin on every comment, state change, label and push. Pins
447 say what the local client keeps cloned.
448 - **Automation** — a JSON API covering repos, issues, forks, and orgs (the
449 label-as-queue surface), a durable event outbox, and **webhooks** (HMAC-signed
450 delivery with retry). Every send goes through `src/kernel/outbound.ts`:
451 - https URLs only; the host is resolved, private addresses are refused, and no redirect is followed.
452 - A refused delivery fails at once, with the reason shown on the hooks page.
453 - An unreachable or erroring endpoint is retried with growing delay.
454 - **Where an account came from** — `GET /api/v1/admin/accounts` lists each
455 account with `via` (`signup`, `edge`, or null) and `signsInThrough`
456 (`"open-platform.sh"` for an account the platform edge made, else null);
457 Settings → Accounts and the new-account line in the inbox say "signs in
458 through open-platform.sh" for the same accounts. The terminal and the MCP
459 have no accounts list to carry it.
460 - **Removing an account** — `DELETE /api/v1/admin/accounts/:username` (admin
461 only). An account that is the last owner of an organization answers 409 with
462 the organizations named. Send `{"retireOrgs": true}` to preview: the answer
463 lists each organization, its repositories (and the app each runs) and its
464 teams, and changes nothing; the account's own repositories and the app each
465 runs are listed too, under `own`. Send `{"retireOrgs": true, "confirm": true}` to
466 do it: apps are stopped, repositories deleted, organizations retired (the
467 name stays taken), then the account. If a repository will not go, the answer
468 is 500 with `failed` and `removed`; what is left is intact and the same call
469 again finishes the job. The accounts page offers the same flow: Remove →
470 "Remove the organization too" → a confirm page listing everything that goes.
471 - **Apps** — a repository that runs: production on the default branch, a
472 preview per open pull with its own copy of the data, served by hostname
473 from the forge's own port, every deploy a run and every run a check.
474 - **The crew** — agents as accounts with nothing but a run-scoped token,
475 working issues in the background with Claude Code and proposing through
476 pulls a person merges.
477
478 Pages render without a line of JavaScript and are designed thumb-first. Six
479 premade themes (from warm paper to polar night) switch instantly and persist in
480 a cookie — even that works script-free. Still **zero runtime dependencies**.
481
482 Not yet: containers around apps, custom domains, and a gate that lands a
483 pull on its own once review and checks agree.
361 The running list of what the forge does, one bullet each, lives in
362 [`docs/STATUS.md`](docs/STATUS.md).
484363
485364 ## Where the rest is
486365
487366 - [`docs/DEVELOP.md`](docs/DEVELOP.md) — developing the forge: tests, checks, the gate, lint and types.
367 - [`docs/STATUS.md`](docs/STATUS.md) — what works today, one bullet per feature.
488368 This page is the front door. Everything else is a file of its own under
489369 `docs/`, each about one thing:
490370
docs/STATUS.md added +125
@@ -0,0 +1,125 @@
1 ## What works today
2
3 - **A repository's runs page filters by state**: a row of links (All, then each
4 state) above the list, or `/:owner/:name/runs?state=failed` by hand. An unknown
5 state is ignored, and the word filter and the pager keep the chosen state. The
6 terminal does the same: `H` in a repository opens its runs, and `s` cycles
7 All, Failed, Running, Succeeded.
8 - **A run page's live log** rides the page's one `/events` connection (its
9 `run.log` frames); it opens no stream of its own. Known gaps, both silent: a
10 frame the server cuts for size (64 lines or 16 KB) loses its omitted lines
11 until the next reconnect's catch-up read, and a run whose log was already cut
12 to its last 512 KB at render gets no catch-up read. The page says
13 "Reconnecting" while the connection is down.
14
15 - **Cut a wave** — Settings → *Cut a wave* (repository admins, same-origin
16 POST; web-only, the terminal's settings hand over to the web) opens a pull
17 adding `waves/<next>.md`, listing the pulls merged since the last wave and
18 not already in an earlier wave. The headline and highlights are `TODO`
19 placeholders; no model is called. It refuses, writing nothing, when an
20 earlier wave does not parse (naming the file) or nothing new has merged.
21 `/:owner/:name/waves` lists the waves on the default branch, newest version
22 first, each line opening `/:owner/:name/waves/<version>` (headline, date,
23 highlights with why, pulls as links; linked from that settings card); in the
24 terminal, `V` in a repository opens the same list and Enter (or a click) on
25 a row opens the same wave as plain text (a live browse of another forge says
26 waves are only read on this forge).
27 `/:owner/:name/waves.atom` is the same list as an Atom feed (newest 50, each
28 entry the headline, its highlights and a link to the wave), advertised from
29 the waves page; a private repository's feed is a 404 to anyone who cannot
30 read it.
31
32 - **Accounts & access** — register/sign in, personal access tokens with coarse
33 scopes (ro/rw/admin), public and private repositories.
34 - **Git** — push and pull over HTTPS **and SSH**, browse code (tree, blob with line anchors,
35 commit history) with README rendering and **syntax highlighting**, protected
36 default branches (force-push and deletion refused, honest fast-forwards
37 allowed).
38 - **System memory** — `/system`, for a forge admin who is a person (anyone else gets 403): the forge's RSS beside its JS heap (`heapStats()` from bun:jsc), hourly mean and max for the last 24 h, drawn on the server. Sampled once a minute into `memory_hour` in `app.db` (a week kept). RSS rising while the heap stays flat is native memory growth. In the terminal the same admin has a **System memory** command in the palette: the last 24 hours as text rows (hour, RSS mean/max, heap mean/max in MiB), read-only. The account menu links `/system` for that admin.
39 - **Flow alarms** — a firing alarm (red main, a stuck approved pull, an idle crew, no release for 90 min) shows as one linked line above home's feed and on `/boards` (admin), under the vitals line of the terminal's Flow dialog and home, as one phone notification when it starts (not repeated while it stands), and in the `flow` tool's `alarms` (MCP, `/api/v1/telemetry/flow`; an agent's cut to public repositories). Nothing firing draws nothing.
40 - **Boards** — `/boards` also shows the pipeline (a row per stage, the bottleneck named) and the held counts (why each autonomous repository's ready issues wait; private repositories included), for the same admin who is a person; they sit as two cards under the board list, not as panels of a board, because they are not saved queries. The terminal's Boards screen does not draw them: its door to the same rows is the **Flow** dialog. `/boards/data`, for a forge admin who is a person (anyone else
41 gets a 404 on the page): the query is the URL (`?source=runs&aggregate=count&by=day&fcolumn=state&fvalue=failed`),
42 drawn server-side as the chart chosen beside it (`chart=line|bar|area|number|table|heat`)
43 with its points in a table; a point opens the rows behind it, keeping your filter.
44 `series=<column>` splits each group by a second column: stacked area draws one band per
45 value (eight at most, the rest folded into "other"), the heat strip one row per value
46 (twelve at most) with time across, the table a series column; line, bar and number set
47 it aside. Number is the latest group's value over a sparkline of all of them.
48 `/boards` holds the boards themselves: a board is one JSON file
49 (`{title, range, refresh: off|10s|1m, panels: [{title, chart, query, w, h}]}`) in
50 `forge/boards`, a repo the forge owns and makes on the first boot with an admin; every save is a commit
51 by the admin who made it, and a file that does not validate is not shown. Make, rename,
52 duplicate, delete, add/edit/remove/reorder panels, or pin the query on `/boards/data`.
53 Any panel has **Pin to home** / **Unpin** on its board page (a same-origin POST); pins are per admin, kept in `board_pin` (slug + panel title + position, `boards/pins.ts`), and home draws them under the KPI row in that person's order with Up, Down and Unpin beside each (`web/studio/pinned.ts`). A pin whose panel is gone is skipped, does not count toward the limit of 24, and is dropped at that person's next pin; nobody sees another's pins. All doors go through `actions/board-pins.ts` (an admin who is a person; anyone else gets not-found):
54 - the page (Pin to home / Unpin on `/boards/:slug`, Up / Down / Unpin on home);
55 - the terminal: on a board's screen `[` `]` mark a panel, `p` pins or unpins it, `<` `>` move a pinned one; the dashboard lists the pinned panels by title;
56 - `GET /api/v1/boards/pins` (the list, in order) and `POST /api/v1/boards/pins` with `{"op":"pin|unpin|move","slug":"…","title":"…","by":-1|1}` (`by` for move only);
57 - the `board_pins` MCP tool (no `op` lists; `op` pin, unpin or move changes). `pins` is therefore not a board slug.
58 The first boot also writes the starting boards, **Usage**, **Serving**, **Crew**, **Releases**, **Apps**, **Security**, **Throughput**, **Reach** and **Forges**; Serving is the forge's event-loop lag p99 beside the checks and crew runs running, so checks' concurrency is raised on numbers. Apps' per-app panels show repo ids until a source exposes a safe name; Security (sign-ins, failed sign-ins, tokens by scope, admin actions, from the admin-only `signins`, `tokens` and `adminActions` sources and `usage`) shows no token text, hash, payload or IP; Throughput is items entering each stage from issue to live, per hour, from the `stages` source. Each is written once: `.offered` in that repo records them, so an edit stays and a deletion sticks.
59 A live board (any `refresh` but `off`; the form offers off or on, and `10s` and `1m` are still accepted and mean the same) redraws its grid when a run, pull, check, issue or usage event arrives, through the live-region swap
60 (`/boards/:slug/live`, etag'd, one fetch per burst). The same doors are in the API: `GET /api/v1/boards`, `GET|PUT|DELETE /api/v1/boards/:slug`, and each panel's data as JSON or CSV at `GET /api/v1/boards/:slug/panels/:id?from=&to=&step=5m&format=csv`, bucketed in SQL to the step and never cut short (`docs/APPS.md`); each panel's card links `json` and `csv` (a plain download link) beside `explore`
61 (an agent or anyone but an admin is 403; a panel whose query cannot run is refused with 422). The same four doors are MCP tools (`boards`, `board_write`, `board_delete`; `boards` with a `slug` reads one, and with a `panel` too that panel's data, with the same `from`, `to` and `step`) and, for the same person, a **Boards** screen in the terminal that lists boards and shows a board's panels as text, each with what its data holds now (a time panel's last bucket and step, another's row count). No dependency was added.
62 - **Parked work** — the terminal's Flow dialog says how many issues the crew parked (an open issue whose newest comment is the crew's `Parked by the crew` note) in the last 24 h and 7 d, and names the newest three; the MCP `flow` tool returns the same as `parked`. See `docs/TOUR.md`.
63 - **Issues** — labels, threaded comments, close/reopen, markdown.
64 - **Pull requests** — open from a branch, a **beautiful diff viewer** (syntax
65 highlighting composed with intra-line word highlighting, mobile-first,
66 collapsible files), merge-commit and squash strategies, conflict detection,
67 and merge commits kept reachable forever via `refs/pull/N/merged`.
68 - **Review comments** — anchored to a diff line by content hash, so a
69 force-push re-anchors them or marks them outdated, never misplaces them.
70 - **Forks** — alternates-backed (instant, storage-bounded), with cross-repo
71 pull requests (fork → upstream) and detach-before-delete safety.
72 - **Organizations & teams** — orgs own repos; teams grant read/write/admin
73 resolved through membership.
74 - **Live updates** — a server-sent-event stream turns comment threads live and
75 flags new activity, no refresh.
76 - **Commit pages** — every commit's diff through the same viewer.
77 - **Settings** — visibility, branch protection, and labels.
78 - **The check lane is visible.** Checks run one at a time; an admin's home
79 strip, the terminal dashboard's strip and `GET /api/v1/health` (`lane`) show
80 the queue depth, the running check and its age, the median of the last
81 twenty and the time to clear, and the 30-minute queue card names that cause.
82 - **A terminal UI** — `ssh` in for the whole forge: dashboard, code browsing
83 with syntax highlighting, issues and pulls with live-updating threads, the
84 diff viewer with inline review comments, merges, settings, orgs, tokens and
85 keys. Six themes, remembered per account.
86 - **Origins** — connect GitHub, Forgejo or Gitea (device code, PKCE, or a
87 pasted token), mirror repositories in with their issues and pulls, and write
88 through to the origin on every comment, state change, label and push. Pins
89 say what the local client keeps cloned.
90 - **Automation** — a JSON API covering repos, issues, forks, and orgs (the
91 label-as-queue surface), a durable event outbox, and **webhooks** (HMAC-signed
92 delivery with retry). Every send goes through `src/kernel/outbound.ts`:
93 - https URLs only; the host is resolved, private addresses are refused, and no redirect is followed.
94 - A refused delivery fails at once, with the reason shown on the hooks page.
95 - An unreachable or erroring endpoint is retried with growing delay.
96 - **Where an account came from** — `GET /api/v1/admin/accounts` lists each
97 account with `via` (`signup`, `edge`, or null) and `signsInThrough`
98 (`"open-platform.sh"` for an account the platform edge made, else null);
99 Settings → Accounts and the new-account line in the inbox say "signs in
100 through open-platform.sh" for the same accounts. The terminal and the MCP
101 have no accounts list to carry it.
102 - **Removing an account** — `DELETE /api/v1/admin/accounts/:username` (admin
103 only). An account that is the last owner of an organization answers 409 with
104 the organizations named. Send `{"retireOrgs": true}` to preview: the answer
105 lists each organization, its repositories (and the app each runs) and its
106 teams, and changes nothing; the account's own repositories and the app each
107 runs are listed too, under `own`. Send `{"retireOrgs": true, "confirm": true}` to
108 do it: apps are stopped, repositories deleted, organizations retired (the
109 name stays taken), then the account. If a repository will not go, the answer
110 is 500 with `failed` and `removed`; what is left is intact and the same call
111 again finishes the job. The accounts page offers the same flow: Remove →
112 "Remove the organization too" → a confirm page listing everything that goes.
113 - **Apps** — a repository that runs: production on the default branch, a
114 preview per open pull with its own copy of the data, served by hostname
115 from the forge's own port, every deploy a run and every run a check.
116 - **The crew** — agents as accounts with nothing but a run-scoped token,
117 working issues in the background with Claude Code and proposing through
118 pulls a person merges.
119
120 Pages render without a line of JavaScript and are designed thumb-first. Six
121 premade themes (from warm paper to polar night) switch instantly and persist in
122 a cookie — even that works script-free. Still **zero runtime dependencies**.
123
124 Not yet: containers around apps, custom domains, and a gate that lands a

Stale: the forge now lands pulls through its gate, so this "Not yet" line misleads. Rewrite it in a follow-up, or drop the gate clause.

125 pull on its own once review and checks agree.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs