@bongos/core 1.19.1032 → 1.19.1033

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.bongos-core.json +159 -54
  2. package/.claude/skills/grade-audit/SKILL.md +1 -1
  3. package/.claude/skills/grader-health/SKILL.md +1 -1
  4. package/docs/adr/0175-the-board-room-full-idea-ratification.md +3 -2
  5. package/docs/adr/0178-the-copy-desk-flag-and-queue.md +3 -3
  6. package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
  7. package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +1 -1
  8. package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +1 -1
  9. package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +11 -3
  10. package/docs/adr/README.md +3 -3
  11. package/docs/architecture.md +1 -1
  12. package/docs/file-map.md +4 -3
  13. package/docs/module-api-changelog.md +2 -0
  14. package/docs/recipes/autobongos-windows-host.md +3 -1
  15. package/docs/recipes/packaging-the-core.md +2 -0
  16. package/docs/recipes/redteam-patrol.md +2 -2
  17. package/docs/recipes/upgrading-the-core.md +1 -1
  18. package/modules/autonomy/gauge.js +57 -9
  19. package/modules/hall-ui/records/builders-roster.md +10 -0
  20. package/modules/hall-ui/records/catalog.md +10 -0
  21. package/modules/hall-ui/records/collaboration.md +14 -0
  22. package/modules/hall-ui/records/goal-dependency-forest.md +11 -0
  23. package/modules/hall-ui/records/oversight-family.md +19 -0
  24. package/modules/hall-ui/records/panel-family.md +19 -0
  25. package/modules/hall-ui/records/reading-rooms.md +12 -0
  26. package/modules/hall-ui/records/record-family.md +13 -0
  27. package/modules/hall-ui/records/reference-room.md +9 -0
  28. package/modules/hall-ui/records/roster-and-ledger.md +9 -0
  29. package/modules/hall-ui/records/sky.md +23 -0
  30. package/modules/lifecycle/dependency-advisory.js +2 -1
  31. package/modules/lifecycle/ship-preflight.js +91 -0
  32. package/package-lock.json +2 -2
  33. package/package.json +1 -1
  34. package/release-notes.json +7411 -0
  35. package/scripts/gds/address-park-sweep.sh +3 -1
  36. package/scripts/gds/autobongos-run.js +81 -1
  37. package/scripts/gds/baseline-staleness.js +1 -1
  38. package/scripts/gds/db-backup-nightly.sh +9 -4
  39. package/scripts/gds/db-backup-offsite.sh +4 -2
  40. package/scripts/gds/package-core.js +72 -1
  41. package/scripts/gds/provision-core-upgrade.js +128 -77
  42. package/scripts/gds/redteam-patrol.js +4 -4
  43. package/scripts/gds/release-notes.js +271 -0
  44. package/scripts/gds/seed-board-room-tasks.js +1 -1
  45. package/scripts/gds/ship-flow.js +5 -3
  46. package/scripts/gds/ship-land.js +86 -7
  47. package/scripts/gds/ship-preflight-steps.js +56 -3
  48. package/scripts/gds/stray-unit-sweep.sh +3 -1
  49. package/scripts/gds/upgrade-migrate.js +67 -0
  50. package/scripts/gds/upgrade.js +6 -4
  51. package/src/module-api.js +1 -1
  52. package/tests/adr_0162_citation.mjs +184 -0
  53. package/tests/autobongos_loop.mjs +114 -0
  54. package/tests/autonomy_gauge.mjs +56 -0
  55. package/tests/core_upgrade_runner.mjs +89 -1
  56. package/tests/db_backup_nightly.mjs +21 -3
  57. package/tests/db_backup_offsite.mjs +81 -0
  58. package/tests/hall_records.mjs +85 -0
  59. package/tests/helpers.mjs +25 -0
  60. package/tests/instance_db_migrated_on_code_move.mjs +8 -1
  61. package/tests/plain_cards.mjs +1 -1
  62. package/tests/redteam_patrol.mjs +1 -1
  63. package/tests/release_notes.mjs +327 -0
  64. package/tests/shell_stat_portability.mjs +49 -0
  65. package/tests/ship_land_conflict.mjs +181 -0
  66. package/tests/ship_preflight.mjs +160 -3
  67. package/tests/sweep_app_account.mjs +15 -2
  68. package/tests/upgrade_migrate.mjs +195 -0
@@ -0,0 +1,11 @@
1
+ # The per-goal dependency forest — on the Goals page (tasks 1003480, 1004200)
2
+
3
+ **THE PER-GOAL DEPENDENCY FOREST landed on the Goals page (task 1003480, goal 1000089; ADR 0227 §6 item 4 and its §10.5 amendment)** — the drill-down the goal map's own header pointed at (the map is the project sky since task 1004232): a task-level picture of which of THIS goal's tasks depend on which, from `GET /goals/:id/dependency-forest`. `goal-forest-lib.js` was the goal map's sibling — the same layered-layout idea (a task sits one column right of what it depends on, deterministic) at a smaller scale, since a per-goal forest is small BY MEASUREMENT (ADR 0227 §10.5: largest 127 tasks, median 9) rather than needing the goal map's cluster-and-shelf packing. Five things worth knowing:
4
+
5
+ - **Its own full-width section, not a band column.** The existing Completion / Members / Conflicts band (task 1828) is three narrow columns; a node-link picture needs real width, so "Task dependencies" sits as its own `.scroll` block between the band and Tasks, drawn with `createElementNS` + `textContent` (the atlas idiom, which the retired goal-map.js shared — a task title never becomes markup) inside `.gf-scroll`, which names BOTH overflow axes (task 1004130).
6
+ - **No cap, no "view all", unlike its band neighbours.** Completion/Members/Conflicts are per-item lists that can genuinely grow long, so they cap-and-link into a dedicated full view. A dependency forest cannot — ADR 0227 §3's own finding is that the tasks it omits are precisely the ones with nothing to connect them to, so what the server returns is already the honest whole picture, every time.
7
+ - **The states are three, and distinct on purpose:** "Reading…" while the request is in flight, "No task in this goal depends on another task in this goal" for a real empty forest (never a blank canvas), and "Couldn't load…" only for an actual failure — a redirecting-to-auth error is swallowed silently instead, the `loadGoalTasks` precedent, because the page is already navigating away.
8
+ - **`loadDependencyForest`/`renderDependencyForest` are exported from `createGoalsRender`'s return** specifically so `tests/goal_forest_page.mjs` can call them directly against a minimal DOM stub, without needing the much larger fixture a full `renderDetail()` boot would need for members/completion/invites this task never touches.
9
+ - **The harness fixture is a single generic `goals__dependency-forest.json`** (task ids real, drawn from goal 25's own rows in `tasks.json`) — every goal id in the harness sees the same forest, which is fine for looking at the picture and not meant to describe any goal's real dependencies.
10
+ - **The drawing code is its own file, `goal-forest-render.js`** — not inline in `goals-render.js`: that file sits at the 1,500-line ratchet, so a new self-contained feature is a new file. `window.OTBGoalForestRender(ctx)` takes the same `{ API, state, sameId, getJSON }` slice of `ctx` and returns `{ loadDependencyForest, renderDependencyForest }`, which `goals-render.js` destructures and re-exports unchanged.
11
+ - **A node title is fitted to MEASURED width, never a character count (task 1004200).** A 24-character budget ran every title 34–41px past its 168px card, because bold proportional glyphs vary per glyph. `fitLines` (pure, in the shared `text-fit.js`, with the caller's `measure`) wraps the title into two lines at spaces and ellipsises the last; the renderer measures with `getComputedTextLength()` AFTER the svg is appended, refits once web fonts land, and puts a `clipPath` on every line as the backstop for a forest drawn while hidden. The full title stays in the node's `<title>` and the table. The goal map fits its three node lines the same way with `fitText`, one line each, loading the same `text-fit.js` (task 1004204).
@@ -0,0 +1,19 @@
1
+ # The oversight family — government, watch and gate, and the Metic+ pages (tasks 1003469, 1002332; harbor was removed in task 1003890)
2
+
3
+ **The OVERSIGHT family landed next (task 1003469)** — government, watch, harbor and gate, then the four Metic+ pages behind `requireGovernmentPage`. This was the REDESIGN, not the re-skin: task 1003307 had put the four in the world token-wise and the owner read the result as "minimal effort reskins", so this pass changed composition. `oversight.css` is the third archetype sheet — what an oversight ROW is in this world, the figure band inside a panel (`.ov-figs`, the record family's band restated), the group head, the inline form, the grant toggle, and the ONE confirm modal harbor and gate now share on `--z-modal`. Five things it settled:
4
+
5
+ - **The sheet is named `oversight`, not `governance`, and that is the vocabulary wall, not taste.** `scripts/gds/fitness.js` `checkVocabularyWall` bans "governance" and "role" on the government surface (ADR 0174: it is the GOVERNMENT now), and a `<link href="governance.css">` in `government.html` reds the build. Name a thing this family shares for what the four pages do together — oversight — and keep the word out of `government.*` entirely, comments included.
6
+ - **The government copy test pins two class names by their literal markup.** `tests/government_board_room_copy.mjs` looks for `<h2 class="gov-h">Constitution</h2>` followed by `<p class="gov-grid-note">`, so those two names stay in `government.html` and `government.css` gives them the shell's own voice (`.scroll__h` / `.scroll__lede` recipes). Do not "converge" them onto the shell names — the test reads the markup, not the render.
7
+ - **Three pages threw a fact away, and the row now shows it.** A rank's `description` was collected by the create form and never rendered (it is the rank row's sub line); Watch fetched `/sessions/search?limit=5` and read only `totals` (the five rows are a ledger under the tally, in `#sessions-recent`); the box's `claude_active` was a suffix nobody scanned for (it is a fact on the row and a clause on the summary tile).
8
+ - **Every Watch list is the kit ledger, and a list that pages delegates its actions.** `renderTable` and `<table class="watch">` are gone; nine lists ride `makeLedger`, the six that can grow with a `makePager` (the bar hides itself under one page). A row the kit re-renders on a page flip has no listener of its own, so reports, overrides and the roster wire ONE listener on the section (`delegate()`), guarded so a reload does not stack a second.
9
+ - **The shell styles text and search inputs, not number inputs.** `#gov-assign-builder` rendered at the browser's 21px and failed `controls-24px` on every government render until `.ov-field` restated the shell's 30px hairline pill for every control it holds. The other floor trap: a `max-width` on a flex child caps its flex BASIS, so a `flex: 1 1 100%` explainer line rode beside the button instead of under it — `min-width: 100%` is what makes a line take the row.
10
+
11
+ Also gone, and the one rule in that list: a sealed state is a `.scroll` panel whose lede is written from `OTB.rankLabel('metic')`, **never a hardcoded "Metic+"**, so the floor cannot drift from ADR 0157. (The same rule holds on collab, the roster, the session ledger and modules.)
12
+
13
+ **Watch's access-requests section became the applications REVIEW queue (task 1002332, BV1.R13)** — a project's applications ARE its own `access_requests` rows (ADR 0182 §Scope), and this section is the owner's surface for them until R4's console page absorbs it. Three things it added, all inside `renderGate`/`loadGate`:
14
+
15
+ - **Every row says whether the platform vouched for the name on it** — a `.fact-pill--ok` "Identity vouched" or a `.fact-pill--warn` "Name unvouched", from the `vouched` the queue read already returned (`applicant_github_id IS NOT NULL`, core_230). `POST /access-requests` is the one PUBLIC unauthenticated write, so anyone may apply under anyone's name; ADR 0201 calls the distinction UI-load-bearing, not decoration. It is never authority (ADR 0016) — both classes offer the identical buttons, and `tests/watch_applications_queue.mjs` asserts exactly that.
16
+ - **The three lifecycle states sit behind one `makeFilterBar` switch**, so the decided history is readable and the RESCIND (`invited → dismissed`) the route has always taken is finally offered — ADR 0208 recorded that nothing on this page did. A declined row offers no button: reviving one is the invite form above, which is what the server's own 409 names. The switch is rebuilt each render (the same lifecycle `wireInviteForm` lives with, since `section` replaces the whole body).
17
+ - **The `att-access` tile counts pending only.** Its sub line says "pending", so the count is guarded on the pending view — reading the history must not repaint the board with a number that means something else.
18
+
19
+ Also gone: harbor's 280px card deck with a 4px coloured "roof" band per box and gate's crate with a verdict band (state is a `.fact-pill`; the fleet reads in three grouped runs — running, parked or in transition, no box); government's bordered `.gov-section` boxes and the one `.gov-assign-row` five unrelated lists shared (each renders as the row it is); watch's third copy of the stat tile, its `table.watch`, its `.watch-btn`; three bare `<p>` sealed states (each is a `.scroll` panel whose lede is written from `OTB.rankLabel('metic')`, so the floor cannot drift from ADR 0157). The harness gained fixtures for `/boxes`, `/gate-approvals` and five session rows, because a page that only ever renders its error state cannot be redesigned by looking.
@@ -0,0 +1,19 @@
1
+ # The panel family — settings and profile, and the profile's three screens (tasks 1003308, 1003489; pair was removed in task 1003890)
2
+
3
+ **The PANEL family landed next (task 1003308)** — settings, profile and pair: the pages where a builder reads a label, a sentence of consequence, and acts on ONE control. (A fourth, `not-ready`, shipped with them and was deleted in task 1003450 — it was a bespoke refusal page for a rank tier the hall no longer has, and the shell's `?denied=` banner says the same thing on any page, naming the permission actually missing.) `panel.css` is the fourth archetype sheet (mock `docs/design/mocks/hall/settings/v3.html`); `settings.css`, `profile.css` and `pair.css` then hold only what one page has and the others do not. **The hall's my-projects page is GONE** (owner decision in this task's review): "my projects" is a user-level page that already lives on the hub, so the hall ships neither the page, nor a link, nor a redirect — `tests/hall_settings_world.mjs` pins the absence. Five things it settled:
4
+
5
+ - **The page scripts keep their names; the archetype aliases them.** `.sound-row`, `.sessions-row`, `.settings-btn`, `.craft-chip` and the rest are grouped onto the family's rules by selector list (the way `style.css` aliases `.gov-pill` onto `.fact-pill`) rather than renamed across three scripts — a class name was not what was wrong with these pages, six hand-rolled rows were. The two HTML-only pages DID take the family names (`.prow`, `.pinput`, `.pbtn`), because nothing else spells theirs.
6
+ - **The family takes the ROOM's head voice, without the room's air.** The mock draws "settings" large and lowercase in the display face, and it is right — on a settings page the document is the page. `.page-head--panel` on `.page-head--room` zeroes the top padding so a form starts at the content edge. The h1 is static markup on all five, so every state has it (the family had none: `one-header-main-h1` failed on every render).
7
+ - **A toggle is a `role=switch`; a pick is a radio.** `switchHtml` in `settings.js` writes `aria-checked` and a 44x24 track inside a 46x38 target (`.sw`), and keeps `data-on`, which every delegated click handler reads. The wandering level is four `.prow--pick` radio rows on `change`, the rank ceiling a disabled radio that says so in the label voice — not four "Choose" buttons in the toggle's class. `.craft-pref` (13x13, the smallest control in the hall) takes the board's <redacted> recipe.
8
+ - **The Discord pair is the one literal, named as such.** Eight literals across three sheets are gone; `#5865F2`/`#4853c4` stay under a `THIRD-PARTY MARK, KEPT ON PURPOSE` comment, and `tests/hall_settings_world.mjs` pins that those two (and their white) are the ONLY literals in `settings.css` and sit only on `.settings-btn--discord`.
9
+ - **The harness had no feed for two of these pages.** Sessions 404'd and pairing had no request, so the row and the device had only ever rendered as empty states — the CLAUDE.md gotcha of the oversight family again. Fixtures added: `me__sessions`, `auth__app-pair__info`; `me__cross-project` gained the totals the settings row reads.
10
+
11
+ Also fixed here, as the board task handed forward: the two sub-24px links on Home's Standing card and the thirteen ledger links on the profile take the `style.css` prose-link recipe. **Not changed on purpose:** the CLI token still renders through an escaped template (idea 1000884 is filed narrowly for the `createElement` rewrite); and the dev-box group's composition, because the harness runs without the module.
12
+
13
+ **The profile became three screens, and grew charts (task 1003489)** — the owner accepted the profile re-skin at the 1003308 review and found the page too long: seven sections stacked, the ledgers under the portfolio. It now borrows settings' rail whole — `OTBKit.makeSectionRail` in switch mode, `.profile-groups[data-active]` in `profile.css` showing one group, a section's own `[hidden]` still winning — under four keys the owner confirmed: **overview**, **works**, **activity** (the craft ledgers and the idea trail exactly as task 1003281 built them — the SAME works seen one craft at a time, plus goals and the idea trail, which is a different question and so a second key, not a duplicate) and **connections** (hub-only). **The key follows the section**: profile.js watches `#conn-scroll`'s `hidden` attribute and refreshes the rail, so a vanilla instance never lists a key that opens an empty screen. Deep links are the key (`/profile?id=95#activity`); the `#/…` overlay namespace passes through. Four things it settled:
14
+
15
+ - **The charts are `profile-charts.js`, and they need no library.** Works shipped and drachmae earned are cumulative step-lines (the value holds until the next day with a ship, then runs flat to today), the craft mix is bars, and on the viewer's own profile their pace by week (`/analytics/builder/:id` is own-or-archon, so it is asked for only when `selfId` is the builder — never a 403 toast). Works and craft derive client-side from `/builders/:id/activity?section=tasks`, followed to `total` up to 500 rows. **Drachmae earned is the credits ledger** (task 1003491) — `/me/credits` on your own profile, `/builders/:id/credits` (`builder.roster.read`, metic+) on another's — `delta` cumulated by day from `recorded_at`, so the line ends at the headline balance; a viewer the ledger refuses gets the activity rows' `credits_reward` estimate, labelled "estimated". Never the per-task `credits_awarded`: on a cost-plus instance (ADR 0146) it is 0 for every task, and summing it flatlined the owner's chart from July. The pure builders (`cumulativeByDay`, `craftMix`, `weeklyBuckets`, `estimateOf`, `clipToWindow`) are exposed for tests.
16
+ - **Dataviz rules, kept.** One axis; one series per chart, drawn in `--accent` (Home's per-chart colours were not copied — a single series has no identity to encode); 2px lines, ≤24px columns with a 4px rounded cap on a square base, a hairline solid axis in `--rule-soft`, an r=4 end dot with a 2px `--bg-card` ring; only the endpoint labelled, in ink; a crosshair readout on hover that the arrow keys walk too; a `<details>` table twin under each time chart, so no number is picture-only. The readout is written with `textContent`.
17
+ - **Honest empties, one line each.** Signed out (or a 401 mid-way) the grid is the same permission sentence the activity ledgers use; an empty record is "No works shipped yet"; a failed read says so. Never three empty cards.
18
+ - **The Works key, and its public fallback.** Works is the full server-paged ledger from `/builders/:id/activity?section=tasks` (any signed-in viewer, any builder); signed out, the page's PUBLIC `recent_ships` stand in with a sign-in line, so a stranger still sees the last few. The Overview no longer carries a works strip — that removes the duplicate the DONE-WHEN named.
19
+ - **A lookback control (owner request, task 1003489) scopes every chart** — 30 days / 90 days / 1 year / All time, one segmented row above the grid (the dataviz date-range-first rule). The cumulative charts clip their axis to the window and ENTER it at the running total, not at zero (`clipToWindow`); the craft mix counts within it; the pace chart passes the window to `/analytics/builder/:id?days=`. Remembered per viewer; changing it re-draws from the rows already in hand — no re-fetch. (A chart LIBRARY was floated by the owner and deliberately NOT taken here — the hall ships none, the hand-drawn charts satisfy the ask, and the swap is its own idea to weigh CSP/weight/theming/table-twin against.)
@@ -0,0 +1,12 @@
1
+ # The reading rooms — the primer and the diagrams (task 1003306)
2
+
3
+ **The READING ROOMS landed next (task 1003306).** `room.css` is the archetype, the second after `record.css`; `primer.css` and `diagrams.css` then hold only what one room has and the other does not. **The classes stay `primer-`-prefixed on purpose**: `diagram-viewer.js` upgrades `figure.diagram img, figure.primer-figure img` with one selector and `hall_audit` pins `.primer-article table`'s 760px scrollport by name, so the sheet is named for the room and the prefix is left alone. Four things it settled:
4
+
5
+ - **ONE markdown parser.** `primer.js` carried its own copy of `md-reader.js`'s grammar *minus blockquotes*, so every `> aside` in the primer silently collapsed into a paragraph. The copy is gone; the primer keeps only the two things that are genuinely its own — the diagram injected after a named h2, and the one rewrite that sends `drachmae-and-karma.md` to `/builders/drachmae` instead of GitHub. **Anything the primer's markdown needs next belongs in `md-reader.js`**, where the economy reader gets it too.
6
+ - **A soft gate must be `aria-disabled`, not `disabled`.** A real `disabled` button leaves the tab order, so a keyboard reader could never land on the acknowledge control and never heard why it was locked. The sheet paints the OPEN state off the same attribute the script writes, and the gate now shows a *measurement* (`Read 46% of the 90% needed`) rather than a refusal.
7
+ - **The `ch` trap, and its inverse.** `primer.css` capped the article at `72ch` under a comment claiming 60–72 characters; a `ch` is the font's ZERO glyph and Manrope's is wide, so it rendered about 90 — the biggest reading defect on the page. The measure is `--measure`, spent on **prose children only**: the card itself is uncapped so a table, a figure and a code block may take its full width. The room does **not** redeclare `--measure` at its larger body size — a `ch` scales with the font, so one measured token reads the same at 15px and 17px.
8
+ - **A fallback for a token that cannot be missing is a second, stale source of truth.** `diagram-viewer.css` carried eleven `var(--x, LITERAL)` pairs whose tokens all ship in `style.css`, so the literal never rendered and only encoded drift (its `--ink` fallback said `#2a2a2a` where the sheet ships `#17181a`). Gone with them: two raw shadows, a hand-written `z-index: 9999` (now `--z-diagram-full`, named in the shell ladder beside the new `--z-progress`), and a scroll lock on `body` where the shell's own drawer locks `html`.
9
+
10
+ Also gone, and the one rule in that list: **a page-local `:root` is forbidden both ways** (`roadmap.css` had the hall's only one) — the pack's `:root` is spliced at the end of `<head>` at (0,1,0), so a page's own `:root` neither wins reliably nor loses cleanly.
11
+
12
+ **Two page-head voices, one per archetype, and that is the pick, not drift.** `.page-head--board` is quiet because a working surface's own name is the least interesting thing on it; `.page-head--room` is a lowercase display line over the ground with a one-word eyebrow, because in a reading room the document *is* the page. The roadmap deck takes the **board's** voice — it is a figure surface, not a document. The modifier carries the voice only; the room's WIDTH (`--room-max`, 1010px — past that a reading card is more empty ground than page) lives in `room.css`, so Ranks can take the voice without loading the archetype and never ends up with a 1010px head over 1120px cards.
@@ -0,0 +1,13 @@
1
+ # The record family — the task page, the idea page and the idea composer (tasks 1003305, 1003437, 1003468)
2
+
3
+ **The RECORD family landed next (task 1003305)** — the task page, the idea page and the idea composer. Four things it settled:
4
+
5
+ - **A record is ONE archetype, in one sheet.** `record.css` holds the composition both record pages share (the way back, the kind eyebrow + status pill, the title with its mono id, the figure band, the sections, the dependency columns, the honest not-found heading); `task.css` and `idea.css` hold only what one has and the other does not. The panel is the shell's `.scroll` — v3 has exactly three card levels and a fourth is a design decision, not a local override. The **composer is not a record** and deliberately does not load `record.css`.
6
+ - **`--measure` is the BODY measure and nothing else.** 48ch was measured at the body size (~60 characters); spent on a 34px display heading it caps at ~950px and the title runs the panel's width. A heading's cap is its own em-based one (`24ch` on `.rec-title`). The inverse mistake is fresh `ch` arithmetic in prose, which is what `idea.css`'s old `70ch` was — it rendered ~88 characters.
7
+ - **A page must not reach into shell layout.** `ideas.css` set `.app-content > main { padding-right: 20rem }` and pinned its meter at a hand-copied `top: 6.5rem`. Both halves are the shell's now — `.app-content--railed` reserves the room and `.app-rail` places the panel, measured from `--rail-right-w` and `--topbar-h` so they cannot desync — and a page **opts in from its markup**. Don't re-paste the override.
8
+ - **`.btn` / `.btn--ghost` had no rule anywhere in the surface.** Six pages (ideas, idea, copy-desk, my-projects, collab, and the goals inbox) were rendering browser-default controls. They are defined in `style.css` now, saying what `.btn-accent` / `.btn-ghost` say, because the names are spelled into markup and into `tests/idea_spark_hall.mjs`. `.btn`'s edge is a **transparent** 1px, because `collab.css`'s `.btn--quiet` sets only a border-colour and a modifier cannot paint an edge that was never reserved.
9
+ - **Two rules the removal list left behind.** No `#fff` literal beside a token colour — that is the pairing `DESIGN.md` bans (it was on `.td-btn--primary`). And every page needs its `<h1>` **including in the not-found and error renders**, because `one-header-main-h1` measures the DOM it gets and not the happy path.
10
+
11
+ **The task record's grade panel, session log and claim ledger (task 1003437).** Two net-new regions the owner approved at the composition round (direction doc §6 decision 4) sit between Shipped and Actions on `task.html`. Four rules carry them: all three ride the ONE task read as opt-in siblings — `GET /tasks/:id?include=last_claim,grade,session_logs,claims`; the two new reads (`getTaskSessionLogs`, `getTaskClaims` in `modules/lifecycle/db-claim-reads.js`) are keyed by `task_id`, newest first, **rank never returned**; absent → **NOTHING** (the ship-visual precedent), except a grade row whose panel was `unavailable`, which is data and says so in one line; and verdicts and outcomes wear the fixed verification vocabulary through `.rec-pill--ok/warn/danger`, **never the accent**. Pinned in `tests/hall_record_world.mjs` (page) and `tests/task_detail_includes.mjs` (route + reads).
12
+
13
+ **The way into and out of a task record (task 1003468, ADR 0229).** Since 1759/1869 a to-do card's BODY toggles selection so a builder can bulk-claim — which is why the owner could not open a card, the only door being a 26px ⤢ glyph nobody found. Now **the title is the door**: `.task__link` on both surfaces that render the card (`work.js`, `goals-render.js`), bought up to the 24px floor with the `.scroll__lede a` recipe; both click handlers let it through; the glyph is gone. **The link carries where it was opened from** — `OTBTaskDetail.href(id)` appends `?from=<path+query+hash>` (a query param so the origin survives a reload and keeps a goal's `#/goal/N`, which a referrer drops) — and `task.js` resolves the exit once (`?from=` → same-origin referrer → the work board), points the crumb **before** the fetch so the way back exists in every state, and refuses anything that is not a path on this host. **Never write a bare `/task/N` link from a list again — call `OTBTaskDetail.href(id)`.** Pinned by §10 of `tests/hall_record_world.mjs` and §7 of `tests/hall_board_world.mjs`.
@@ -0,0 +1,9 @@
1
+ # The reference room — drachmae, at /drachmae (task 1003473)
2
+
3
+ **The REFERENCE room landed last (task 1003473)** — drachmae, the fifth and smallest redesign task under goal 1000081. It is the reading-room archetype (`room.css`) with ONE body modifier, `room--reference`, and not a second archetype: the room's composition — the head, the glass contents beside the `--bg-card` article, the measure — is right for a document people scan as much as for one they read, so what changes is the content rules (`room.css` §8, ADR 0228). Five things it settled:
4
+
5
+ - **A reference has no end to reach, so the linear-read instruments are off.** No progress hairline (the markup is simply absent; §1 of the sheet stays for the primer, whose gate is fed by it), no section counters (`content: none` on the h2's and the contents' `::before`), no "About a five-minute read". `primer.css` is no longer loaded here: it holds only the acknowledge gate.
6
+ - **The outline goes to h3 depth and is read from the DOM.** `drachmae.js` collects `h2[id], h3[id]` from the rendered article (so `*not*` and a link in a heading arrive as words, and the inserted live section is one more heading in order), nests h3s under their part (`.primer-toc__sub`), and lights the entry AND its part (`.is-trail`). On a phone the collapsed toggle rides under the top bar (`position: sticky` at the room's 760px breakpoint, on the card's ground at 92% because light's glass has no blur and prose showed through the bare tint) and its middle (`#primer-toc-current`) names the part the reader is in.
7
+ - **Every heading is an address.** `.room-anchor` on h2/h3/h4: the heading's own id as an `<a href>`, a 24px box always visible in the faint ink, copying the absolute link on click, the accent when it is the one the reader arrived at. `:target` resolved against nothing on a page whose document renders after load, so `landOnHash()` scrolls to it after the document, the live section and the fonts (a swap moved the landing 300px) and marks it `.is-target` until the next hash change.
8
+ - **The parser reaches h4 and slugs a linked heading by its text.** `md-reader.js` stopped at `###`, so the doc's six earning sources rendered as paragraphs with a literal `####` — the largest defect on the page, invisible to every floor. It is `#{1,4}` now in both regexes, the bold rule is lazy so `**… *list* …**` renders, and a markdown link in a heading slugs by its text. The primer has no `####` and no linked heading, so its render is unchanged; `room.css` gives the h4 a rule (the body face at the body size, one weight step up).
9
+ - **The live rate card is a section ahead of the closing links,** not an appendix after "dig deeper": `insertBountySection` puts it before the h2 matching `/^see it/i` and falls back to appending.
@@ -0,0 +1,9 @@
1
+ # The roster and ledger pages — goals, sessions and people (task 1003470; scouting became /people in task 1003440)
2
+
3
+ **The ROSTER and LEDGER pages landed next (task 1003470)** — goals, sessions, people and scouting. Goals is the archetype page (`docs/design/mocks/hall/goals/v3.html`) and the page the owner was looking at when he called the re-skin a re-skin, so this pass changed its COMPOSITION rather than its tokens. Five things it settled:
4
+
5
+ - **The goals list is ONE panel.** The filter bar, the count line, the readiness band and the groups sit on one `.scroll` (`.goals-panel`); the bar of twelve controls on the ground over a `--bg-card` panel PER GROUP is gone. Three lenses stay in the open (version, state, sort) and the five rarely turned sit behind the kit's Filters disclosure, **which counts and chips whatever is active so a hidden filter can never narrow the board silently.** `rows|cards` is a VIEW, not a filter: a segmented toggle on the count line writes a hidden `#f-goal-view`, so task 1003136's fork in `renderList` and the `otb.goals.view` pref are unchanged; the pressed side takes `--accent-tint` (the tint the world gives a current thing), **never the accent fill.**
6
+ - **Three languages on a row, not four.** The ring is the goal's own done-when (satisfied over total, **the arc in INK — the accent lights the pressable and a progress arc is neither**); the facts line is every label a goal carries; the right column is ONE thing — `claim next` as the shell's `.btn-accent` (**the row's only accent**), `what's blocking` as a ghost pill, or a one-line state with a drawn check. The readiness is the family's `.ov-figs` band with the roll line's own `.gr-mark` dots (**classes, not inline styles**), so the band and the rows read as one language.
7
+ - **The card is the row folded.** The toggle stays because the mock keeps it (a card wall is the better read when scanning for a NAME), but a card inside the panel draws no box — a `--bg-card` tile on a `--bg-card` panel is the card-in-a-card v3 removed — so `.goal-grid` paints the hairline BETWEEN its rows (a 1px row gap over `--rule-soft`, each cell on the panel's ground) and every cell renders the same facts from `goalRenderFacts`.
8
+ - **People and scouting are ONE page.** `/scouting` was the legacy door to the same `/api/gds/scouting` rollup, drawn a third way; it now renders `people.html`'s body and `scouting.js` is deleted. **Task 1003440 then deleted the door itself**: `scouting.html` and `scouting.states.json` are gone, the `SCOUTING_PAGE_RE` page gate with them, and `/builders/scouting` now 301s to `/people` (`SCOUTING_LEGACY_RE` in `serve-internal.js`) so an old bookmark still lands on the directory. `tests/hall_roster_world.mjs` pins both halves — the files are absent, and the redirect exists with no dead gate behind it. On the row the name is the shell's `.lb-name` to the builder's **GitHub** page — the one door every listed builder has, since the directory is cross-project and no local profile id exists. The gate copy is written from `OTB.rankLabel('metic')`; it used to say "Archon-only", wrong since ADR 0210.
9
+ - **The session ledger's nowrap + wrapper pair was a live bug.** `sessions.css` forced `white-space: nowrap` on every cell, `.ledger-table-wrap` existed only to give that a scrollport, and the shell's sticky `th` then stuck to the WRAPPER — the column heads rendered over the first row on desktop. Both are gone; the shell's card strip handles a phone as on every other ledger. **Every id on a row is a door** (the session id was painted in `--info` — a link to nowhere); the row shows what the session EARNED; the totals ride the shell's top strip (`.ov-summary`) with **six fixed columns so the sixth tile is never stranded on its own row**. The hand-rolled server-side pager is untouched (the sanctioned exception below).
@@ -0,0 +1,23 @@
1
+ # The sky — /thinking and /goal-map (tasks 1004231, 1004232, 1004233)
2
+
3
+ **THE SKY landed at `/thinking` (task 1004231 / sky part 3 of 5, goal 1000095; [ADR 0331](../../../docs/adr/0331-the-sky-is-the-ideators-home.md), ADR 0227 §11)** — the ideator's home. It replaced BOTH the room (task 1004098, `/thinking`) and the first ambient sky (task 1004105, `/sky` — the URL 301s here now) with the prototype the owner adopted as the design of record: `docs/design/mocks/sky/sky.template.html` is the **pixel contract** — copy, layout, colours, the thirteen tour steps, the toggles, the keys (`L` list, `?` tour, `0` fit, `/` compose) — built for real over `GET /sky` and `GET /sky/mine` (part 2). The room survives inside it as the list drawer: every drawn thought is a row with its whole sentence, one key away. Eight things it settled:
4
+
5
+ - **The page has no hall chrome, on purpose.** `thinking.html` is the template's markup (no shell.js, no sidebar); `thinking.css` is its `<style>`, one file; the world's tokens are not loaded — the template's values ARE the contract. The hall's doors (the record, the goal, the task) open from the panel, resolved against `hallBase()` (apex `/builders` or the subdomain). The title stamps `{{worldName}}` like every hall page.
6
+ - **One script became seven files under the 1,500-line ratchet, on ONE shared object.** `window.OTBSky`: `SKY.S` is every `let` the prototype's closure held; everything else is a function or a constant. Load order is pinned in the HTML — `sky-data.js` (helpers, the vocabulary, the two adaptors, the bands, the world, the thoughts, the five reads) → `sky-layout.js` (clock, routes, explore, camera) → `sky-draw.js` (the drawing, the hit test) → `sky-interaction.js` (card, menu, pointer, keys, lenses, stats, jump, legend) → `sky-panel.js` (the panel, the drawer) → `sky-composer.js` (gravity, quick + full filing, the nod) → `sky-tour.js` (the thirteen steps, the frame loop, the boot). A file only CALLS a later file through `SKY.name(...)`, never at load time. The README says a split is not a deviation; a changed behaviour inside one is — and every deviation a gate or a route forced is listed in the task's ship notes, one line each.
7
+ - **The bands are DERIVED from `GET /versions`, never named by hand.** The prototype listed this instance's version ids; the core cannot (a scaffolded instance would have every goal outside every band and crash on `g.band`). `bandsFor`: the versions still building sit at the centre (the present), shipped versions ring outward newest first, and small versions share a band until it holds six goals so no ring is razor-thin — the grouping the prototype drew by eye. "Trace only the latest" reads the present band's versions for the same reason. `tests/hall_sky.mjs` pins that no `sky-*.js` file names a version.
8
+ - **The two heuristic layers are LEDGER FACTS since part 5** (task 1004233; the README's "must not ship" rows, shipped). `filed_via` rides on every `GET /sky/mine` row (`hall | cli | discord | planning | api`, or NULL for a row older than the column): `sky-data.js` `SESSION_VIAS` puts cli / api / discord behind the "claude layer" pill (off by default), hall and planning always show, and a NULL row shows by default (the owner's rule) with the panel saying "filed before the ledger recorded where" — nothing is guessed from text. Born-of marks read `S.BORN`, filled from `GET /sky`'s `born` (the ideas module's read of the ratify tables through the `ideas.bornOf` port): a goal born of a thought wears the nucleus ring, a ring born of one the accent notch, and the thought sits at the well's core or on its ring. An absent read is an empty map and no mark, never a heuristic.
9
+ - **Gravity is the real verdict, and the filing is the hall form's own body.** Typing runs the composer's debounce (300ms) → `GET /goals/suggestions` per building version (the hall composer's read) → `POST /inbox/preview-landing` over the pick; the sentence READS the verdict and decides nothing (a `floats` verdict lights no well however well the words match). Quick files `{ title, body_md: '', kind: 'feature' }` plus the `goal_id` the verdict named; full files `{ title, grade: 'full', template_key, field_values }` — the five questions are `GET /inbox/template`'s, asked one at a time after the line, with skip and cancel, never a copy kept in the page (`tests/idea_hall_composer.mjs`'s rule). A filing that fails puts the line back and says why in the receipt. Without a template read the "full idea" switch is hidden: there is nothing to file as.
10
+ - **The nod is drawn only for `idea.triage`, and runs the existing promote path.** `/me` now resolves that atom (`NAV_PERMISSION_ATOMS`, `src/bongos/routes/me.js`); the panel draws "nod — make it claimable" on a protostar in a well awaiting a nod, and pressing it is `POST /tasks/:id/promote` on the routed task in the thought's lineage — which stays the wall (it gates on `task.promote`; both floors are metic under the compat seed).
11
+ - **A title never becomes markup.** The prototype builds HTML strings; every title, criterion and goal name passes through `esc()` on the way in, and `tests/hall_sky.mjs` runs a hostile title through the drawer, the panel and the hover card.
12
+ - **THE THREE ANSWERS STAY THREE, and size is one curve.** `diameter()` is affine in the server's `scale` (6 + 22·scale) and nothing else — a remnant is as wide as its work was; an unreadable brightness (`attributable: false`) leaves the star at `lit: 1` and says "still alive — unknown"; a thought with no work says nothing about what survived. `tests/ideator_large_dead_thought_proof.mjs` runs the real size and state hops into this page and reads the diameter and the light back.
13
+
14
+ Pinned by `tests/hall_sky.mjs` (31 cases: the reads, the five objects, the two channels, the wells, the drawer, empty and failed skies, the two hidden layers, the bands, the tour and the keys) and `tests/hall_thinking_room.mjs` (18: the drawer's rows and filters, quick and full filing byte for byte, gravity, the nod), both booting the real page through `bootSkyPage` in `tests/helpers.mjs` — the page's OWN markup parsed into a mini-DOM with a stub canvas, so a lost button fails a test. **The harness fixtures are `sky.json` + `sky__mine.json`** (`scripts/hall-preview/fixtures/`): the wells are `goals__graph.json`'s nodes with the criteria the per-goal fixtures carry, and the thoughts are `inbox__by-builder.json`'s rows, so the drawer and the old room can never disagree about a thought. Against the kit's own stub neither read exists, so `thinking.states.json` renders the EMPTY sky — a real state — and skips the first-visit tour first.
15
+
16
+ **THE PROJECT SKY landed at `/goal-map` (task 1004232 / sky part 4 of 5, goal 1000095; ADR 0331, ADR 0227 §11)** — the same page as `/thinking`, opened in its "the whole project" lens, and it REPLACED the goal map that held the URL (task 1003479: the layered node-link picture, the stand-alone list and the table — `goal-map.{html,css,js}` + `goal-map-lib.js` and `tests/goal_map_lib.mjs` are gone). It holds the nav slot the map was denied — "Sky", directly under Goals, gate `member` (owner, 2026-09-24; ADR 0227 §11 is the licence) — and Goals' own "see which goals wait on each other" link still lands here. Six things it settled:
17
+
18
+ - **One page, two doors.** `goal-map.html` is `thinking.html`'s markup, verbatim but for the body (`data-page="goal-map" data-sky-lens="all"`), the brand line and the switcher starting in the whole-project lens, and the canvas's own words. The sheet (`thinking.css`, which names both bodies) and the seven `sky-*.js` files are SHARED, never copied: `S.home` (read once in `sky-data.js` from `data-sky-lens`) is the lens `sky-tour.js` boots into and hands back to after the tour, and `sky-interaction.js`'s switcher LEAVES for the other page when the lens asked for lives there — "the whole project" goes to `/goal-map` from `/thinking`, "simple" and "your thinking" go to `/thinking` from here. Every lens still exists inside both pages because the tour walks all three. One cache-bust between them (`?v=…-sky4` on both).
19
+ - **What the lens draws is the template's, from the same reads.** Wells sized by credits (settled 0.8×, collapsed 0.5×, both keeping their mass), alive as MOTION (a builder holding a task now, or more than three landing this week — never status), routes as rim arrows charted by a shuttle that pulls the camera back to hold both ends, the shuttle's five-line menu (every route from this goal · every route in the sky · only the latest · hide unrelated · land every shuttle), the clock, band click to fit. `GET /goals/graph` is no longer read by any page — the sky's own reads carry the wells and the routes.
20
+ - **Explore is on by default, and "my ideas" is off.** A first visit asks "where do you begin" (where my thoughts are · one door per open well · show me everything); a goal is revealed when you begin in it or chart a route to it, and the ledger, the filters and the search know only what you have reached (`sky-seen`, `sky-explore` in localStorage — shared with `/thinking`, since it is one sky). No thought is laid over the project until the "my ideas" pill is pressed; "what if" waits behind it.
21
+ - **THE LEDGER DRAWER IS THE ACCESSIBLE PATH** — the old goal map's table, kept (ADR 0227 §11): `L` opens it; every goal is a row that counts both directions of its dependencies ("waits on N · waited on by N") and the panel names them as doors; the filters are alive / wells / settled / collapsed / with routes. Every drawn thing is a row, one key away — spec decision 11's surviving rule.
22
+ - **ADR 0227 §1's refusal stands.** Tasks resolve onto their rings only inside ONE well past the zoom threshold (dust density on the rings below it), never for every goal at once — at the whole-sky fit no task is a body — and there is no "show every task" control anywhere on the page or in the shuttle menu. `tests/goal_map_page.mjs` pins both, plus the nav slot, the two doors, the wells, alive, the routes, the menu, explore, "my ideas", the ledger, the clock, the bands, the tour and a hostile title.
23
+ - **Same tier, same gates as `/thinking`.** `requireBuilderPage` (the tier the map sat on); no hall chrome (`STANDALONE_EXEMPT` in `tests/hall_audit.mjs`); `goal-map.states.json` renders the EMPTY sky against the kit stub like `thinking.states.json`, and the populated sky against the harness (`sky.json` + `sky__mine.json`). `goal-forest-*` (the per-goal forest on the goal detail page) is untouched.
@@ -21,7 +21,8 @@
21
21
  // `node scripts/gds/audit-deps.js --min high` as one more housekeeping
22
22
  // step (idea 1000276's half — see .claude/skills/goal-review/SKILL.md).
23
23
  //
24
- // HARD CONSTRAINT, same as goal-advisory.js and ADR 0162: advisory only. This
24
+ // HARD CONSTRAINT, same as goal-advisory.js: advisory only (a finding addressed
25
+ // to a human never acts by itself — ADR 0158 §2). This
25
26
  // must never refuse a POST, and `buildDependencyAdvisory` swallows its own
26
27
  // failures for the same reason goal-advisory.js's does — the task row is
27
28
  // ALREADY COMMITTED by the time this runs, so a throw here would turn a
@@ -15,6 +15,9 @@
15
15
  // touches[] was empty, leaving empty-touches tasks unprotected — with this
16
16
  // plain clean-tree check. Strictly stronger; untracked / .gitignored files
17
17
  // are ignored so autonomous sessions with scratch files aren't refused.)
18
+ // Ignored, but no longer UNMENTIONED: untrackedLeftBehind +
19
+ // untrackedWarningLines (task 1001436) name the untracked files a ship
20
+ // with committed work is about to leave behind, as a warning only.
18
21
  //
19
22
  // 2. cleanupMacFinderDupRefs — find + delete `.git/refs/.../FOO 2` files
20
23
  // that macOS Finder / iCloud creates when it duplicates a regular file
@@ -96,6 +99,91 @@ function classifyEmptyShip(porcelainOut, { allowEmpty = false } = {}) {
96
99
  return { refuse: true, reason: 'nothing-to-ship', uncommitted: [] };
97
100
  }
98
101
 
102
+ // ---- The RESIDUAL phantom ship (task 1001436) --------------------------------
103
+ //
104
+ // classifyEmptyShip closes the case where NOTHING is committed and the
105
+ // deliverable sits uncommitted. It cannot see the weaker case beside it: the
106
+ // builder commits real work, so the branch is ahead of main and the ship goes
107
+ // ahead, but one more NEW file was never `git add`ed. The merge carries only
108
+ // committed history, so that file silently does not ship — the task reads
109
+ // "shipped" with part of its artifact missing, and nothing said so.
110
+ //
111
+ // The answer is a loud WARNING, never a refusal. trackedDirty ignores untracked
112
+ // files on purpose: autonomous sessions leave scratch in the tree, and refusing
113
+ // on it would false-refuse real ships. So this only names the files — before the
114
+ // grade and the merge, while stopping is still free.
115
+ //
116
+ // SCOPE. The filing suggested narrowing the list to touches[] or to docs/ src/
117
+ // scripts/. Neither is used, because each hides exactly the file this exists to
118
+ // catch: touches[] is advisory and often empty, and a forgotten file is by
119
+ // definition one nobody thought to declare; a directory allowlist misses tests/,
120
+ // modules/ and migrations/, where a forgotten new file most often lives (a new
121
+ // test beside a committed fix). The one SYSTEMATIC source of noise is the ship's
122
+ // own input — the builder-ship skill writes notes.md + summary.txt into the tree
123
+ // and passes them as --notes-file / --summary-file — so those are excluded
124
+ // exactly, by path, via `inputs`. Surveyed before choosing (2026-09-25, 16 live
125
+ // worktrees): 12 untracked entries, none of them root-level scratch, and 5 under
126
+ // tests/ or modules/ — where a docs/ src/ scripts/ allowlist would have hidden them.
127
+
128
+ // Pure: the untracked ('??') paths in raw `git status --porcelain` output, in
129
+ // output order. .gitignored files never appear without --ignored, so this is
130
+ // exactly "untracked and not ignored". Quoted paths are unquoted the same way
131
+ // parsePorcelain does it.
132
+ function untrackedPaths(porcelainOut) {
133
+ if (!porcelainOut) return [];
134
+ const files = [];
135
+ for (const raw of porcelainOut.split('\n')) {
136
+ if (raw.slice(0, 2) !== '??') continue;
137
+ const file = raw.slice(3).replace(/^"|"$/g, '');
138
+ if (file) files.push(file);
139
+ }
140
+ return files;
141
+ }
142
+
143
+ // One spelling for a repo-relative path, so an input given as `./notes.md` or
144
+ // with Windows separators still matches porcelain's `notes.md`.
145
+ function normalizeRelPath(p) {
146
+ return String(p || '').replace(/\\/g, '/').replace(/^(\.\/)+/, '');
147
+ }
148
+
149
+ // Pure: which untracked files will be LEFT BEHIND by this ship.
150
+ // committedFileCount — files in the branch's committed diff vs the grader
151
+ // baseline. Zero returns [] on purpose: with nothing committed,
152
+ // classifyEmptyShip already REFUSES and lists these same files, and a second
153
+ // message about them would only compete with the refusal.
154
+ // inputs — repo-relative paths the ship itself was handed (notes, summary,
155
+ // response, visual). They are inputs to the ship, not deliverables.
156
+ function untrackedLeftBehind(porcelainOut, { committedFileCount = 0, inputs = [] } = {}) {
157
+ if (!(Number(committedFileCount) > 0)) return [];
158
+ const skip = new Set((inputs || []).map(normalizeRelPath).filter(Boolean));
159
+ return untrackedPaths(porcelainOut).filter((f) => !skip.has(normalizeRelPath(f)));
160
+ }
161
+
162
+ // Pure: the warning block for untrackedLeftBehind's result; [] when there is
163
+ // nothing to say. `cap` bounds the listing so a tree full of scratch cannot bury
164
+ // the rest of the ship output. The wording holds on all three preflight callers
165
+ // (first ship, --regrade, the confirmed-task resume): each runs this before it
166
+ // resolves, grades or merges anything.
167
+ function untrackedWarningLines(files, { taskId = null, cap = 15 } = {}) {
168
+ if (!files || files.length === 0) return [];
169
+ const n = files.length;
170
+ const task = taskId != null && taskId !== '' ? ` with task ${taskId}` : '';
171
+ const lines = [
172
+ '',
173
+ `⚠ Untracked-file warning: ${n} untracked file(s) in this worktree will NOT ship${task}:`,
174
+ ];
175
+ for (const f of files.slice(0, cap)) lines.push(` - ${f}`);
176
+ if (n > cap) lines.push(` … and ${n - cap} more`);
177
+ lines.push(
178
+ ' The merge carries committed work only, so these stay behind.',
179
+ ' If one of them belongs to this task: stop now (Ctrl-C — nothing has been resolved at this step),',
180
+ ' commit it, and re-run. If they are scratch, ignore this.',
181
+ ' Warning only — not blocking; the ship proceeds.',
182
+ '',
183
+ );
184
+ return lines;
185
+ }
186
+
99
187
  function gitDirFor(cwd) {
100
188
  // Resolve the *actual* refs directory for a given worktree. In a linked
101
189
  // worktree, `.git` is a file containing `gitdir: <path>` that points at
@@ -218,6 +306,9 @@ module.exports = {
218
306
  parsePorcelain,
219
307
  trackedDirty,
220
308
  classifyEmptyShip,
309
+ untrackedPaths,
310
+ untrackedLeftBehind,
311
+ untrackedWarningLines,
221
312
  cleanupMacFinderDupRefs,
222
313
  gitDirFor,
223
314
  findMainWorktree,
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1032",
3
+ "version": "1.19.1033",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.1032",
9
+ "version": "1.19.1033",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.1032",
3
+ "version": "1.19.1033",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",