@bongos/core 1.19.902 → 1.19.904

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 (37) hide show
  1. package/.bongos-core.json +37 -67
  2. package/bin/bongos.js +7 -13
  3. package/docs/copy-inventory.md +6 -7
  4. package/docs/copy-registry.json +9 -18
  5. package/docs/module-api-changelog.md +4 -0
  6. package/docs/recipes/bongos-cli-release.md +3 -2
  7. package/docs/recipes/instance-first-run.md +1 -2
  8. package/docs/recipes/local-dev.md +0 -2
  9. package/modules/hall-ui/public/collab-lib.js +22 -0
  10. package/modules/hall-ui/public/collab.js +17 -2
  11. package/modules/hall-ui/public/hall-kit.js +11 -5
  12. package/modules/hall-ui/public/profile-charts.js +1 -1
  13. package/package-lock.json +2 -2
  14. package/package.json +1 -1
  15. package/scripts/gds/api.js +1 -25
  16. package/scripts/gds/build-cli-package.js +17 -35
  17. package/scripts/gds/claude-materialize.js +1 -1
  18. package/scripts/gds/cli-lib.js +5 -127
  19. package/scripts/gds/go-live.js +1 -1
  20. package/scripts/gds/login.js +1 -1
  21. package/scripts/gds/plain-cards.js +0 -8
  22. package/scripts/gds/ship-land.js +2 -18
  23. package/src/module-api.js +1 -1
  24. package/tests/bongos_dispatch.mjs +4 -4
  25. package/tests/claude_materialize.mjs +1 -1
  26. package/tests/cli_package.mjs +1 -4
  27. package/tests/cli_surface.mjs +1 -88
  28. package/tests/collab_page.mjs +44 -0
  29. package/tests/hall_kit_charts.mjs +38 -0
  30. package/tests/plain_cards.mjs +2 -2
  31. package/tests/rank_tier_single_source.mjs +3 -5
  32. package/scripts/gds/box-connect-lib.js +0 -550
  33. package/scripts/gds/code.js +0 -105
  34. package/scripts/gds/shell.js +0 -102
  35. package/tests/bongos_box_connect.mjs +0 -166
  36. package/tests/box_scope_predicate.mjs +0 -117
  37. package/tests/cli_box_scope.mjs +0 -92
@@ -418,11 +418,25 @@
418
418
  const whoName = o.name || o.login || `builder ${o.id}`;
419
419
  const who = escapeHtml(whoName);
420
420
  const face = { kind: 'person', id: o.id, name: whoName, avatar: null, initial: L.initialOf(whoName) };
421
+
422
+ // A SENTENCE TAKES roleWord() — the rule dom-utils.js writes down beside the helper
423
+ // (task 1004041). This row took neither helper and interpolated the raw column, so a
424
+ // pair whose role was unset rendered the database's own word: "A unclassified — you
425
+ // are a engineer", which is V2's named failure and a wrong article in the same line.
426
+ // Where a role is missing the comparison has nothing to compare, so the row says the
427
+ // one thing that is still true rather than naming a value nobody chose.
428
+ // roleIsUnset() is how a caller asks (dom-utils.js:198). Spelling 'unclassified' here
429
+ // would be a second definition of the axis in a third file — the drift V1 forbids, and
430
+ // the exact shape this review was filed to remove.
431
+ const theirs = window.OTB.roleIsUnset(o.craft) ? '' : window.OTB.roleWord(o.craft);
432
+ const mine = window.OTB.roleIsUnset(o.myCraft) ? '' : window.OTB.roleWord(o.myCraft);
433
+ const pairLine = L.pairSentence(theirs, mine);
434
+
421
435
  return `<li class="ov-row ov-row--lead cb-row">
422
436
  ${faceHtml(face)}
423
437
  <div class="ov-row__main">
424
438
  <a class="ov-row__title" href="/builders/profile?id=${escapeHtml(String(o.id))}">${who}</a>
425
- <p class="ov-row__sub">A ${escapeHtml(String(o.craft))} — you are a ${escapeHtml(String(o.myCraft))}. Mingle pairs across roles.</p>
439
+ <p class="ov-row__sub">${escapeHtml(pairLine)}</p>
426
440
  </div>
427
441
  <div class="ov-row__right">
428
442
  <div class="ov-row__actions">
@@ -511,6 +525,7 @@
511
525
 
512
526
  load().catch(() => {
513
527
  const sub = $('#cb-sub');
514
- if (sub) sub.textContent = 'Could not load requests.';
528
+ // V6: a message that stops someone points at the next move in the same breath.
529
+ if (sub) sub.textContent = 'Could not load requests — reload to try again.';
515
530
  });
516
531
  })();
@@ -1141,7 +1141,13 @@
1141
1141
  activeDays += 1;
1142
1142
  totalShips += c;
1143
1143
  }
1144
- return { counts, startSun, endOrd, weeks, activeDays, totalShips };
1144
+ // The window's NAME belongs with the window (task 1003621, artist review of 1003583).
1145
+ // The aria summary described it as "the last year" while the caption under the same
1146
+ // grid said "this year" — a rolling 53 weeks and a calendar year are not the same
1147
+ // span, and V3 asks the accessible line and the visible one to state the same fact.
1148
+ // Both now read this.
1149
+ const spanLabel = weeks === 53 ? 'the last year' : `the last ${weeks} week${weeks === 1 ? '' : 's'}`;
1150
+ return { counts, startSun, endOrd, weeks, activeDays, totalShips, spanLabel };
1145
1151
  }
1146
1152
 
1147
1153
  // calendarGridHtml(days, opts) — a year of ship-days as a contribution calendar:
@@ -1168,7 +1174,7 @@
1168
1174
  function calendarGridHtml(days, opts = {}) {
1169
1175
  const win = calendarWindow(days, opts);
1170
1176
  if (!win) return ''; // nothing real to draw → caller's empty state
1171
- const { counts, startSun, endOrd, weeks, activeDays, totalShips } = win;
1177
+ const { counts, startSun, endOrd, weeks, activeDays, totalShips, spanLabel } = win;
1172
1178
  const ordToYmd = calOrdToYmd;
1173
1179
 
1174
1180
  // level 0 = empty; 1..5 map to the anti-gaming daily cap (streak.js caps a day at
@@ -1188,10 +1194,10 @@
1188
1194
  }
1189
1195
 
1190
1196
  // The window is DESCRIBED, not assumed: a caller passing weeks:6 used to be handed
1191
- // an aria label saying "over the last year" (task 1003583).
1192
- const span = weeks === 53 ? 'the last year' : `the last ${weeks} week${weeks === 1 ? '' : 's'}`;
1197
+ // an aria label saying "over the last year" (task 1003583). The phrase now comes from
1198
+ // calendarWindow so the caption beneath the grid can say the same words.
1193
1199
  const summary = opts.ariaLabel
1194
- || `Ships per day over ${span}: ${totalShips} ship${totalShips === 1 ? '' : 's'} across ${activeDays} day${activeDays === 1 ? '' : 's'}.`;
1200
+ || `Ships per day over ${spanLabel}: ${totalShips} ship${totalShips === 1 ? '' : 's'} across ${activeDays} day${activeDays === 1 ? '' : 's'}.`;
1195
1201
  // The legend is aria-hidden: it decorates the bands, it carries no fact the cell
1196
1202
  // titles and the summary do not already state in words.
1197
1203
  const legend = '<div class="kit-cal__key" aria-hidden="true"><span class="kit-cal__key-label">less</span>'
@@ -284,7 +284,7 @@
284
284
  // The scalars in words, in the .pchart__read slot the other figures use for their
285
285
  // crosshair readout — here it is static because the calendar's reading is per-cell.
286
286
  const read = `Current streak ${cur} day${cur === 1 ? '' : 's'} \u00b7 longest ${lng} \u00b7 `
287
- + `${active} day${active === 1 ? '' : 's'} shipped this year`;
287
+ + `${active} day${active === 1 ? '' : 's'} shipped in ${win ? win.spanLabel : 'the last year'}`;
288
288
  const html = `<figure class="pchart" id="${esc(id)}">
289
289
  <figcaption class="pchart__title">Your shipping year</figcaption>
290
290
  <p class="pchart__read">${esc(read)}</p>
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.902",
3
+ "version": "1.19.904",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.902",
9
+ "version": "1.19.904",
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.902",
3
+ "version": "1.19.904",
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",
@@ -18,9 +18,7 @@
18
18
  // on a 2xx, 1 on any non-2xx or transport error, 2 on bad invocation.
19
19
 
20
20
  const fs = require('node:fs');
21
- const { cliClient, arg, stripBom, resolveQuiet, isBoxScope, printBoxScopeGuidanceAndExit, cliExit, repairMsysMangledPath,
22
- instanceDevBoxSupport, devBoxUnavailableMessage } = require('./cli-lib');
23
- const { plainCardLines } = require('./plain-cards');
21
+ const { cliClient, arg, stripBom, resolveQuiet, cliExit, repairMsysMangledPath } = require('./cli-lib');
24
22
 
25
23
 
26
24
  (async () => {
@@ -77,31 +75,9 @@ const { plainCardLines } = require('./plain-cards');
77
75
  return cliExit(1); // task 1002734: never process.exit() after a request
78
76
  }
79
77
  if (!quiet) console.error(`${method} ${urlPath} → ${res.status}`);
80
- // task 1501: on a dev box the baked session is box-scoped (ADR 0053) and every
81
- // non-bootstrap call (e.g. /me, /inbox) returns 403 box_scope. We pass
82
- // allowUnauthenticated above (so a 401 stays a plain error, not the setup
83
- // short-circuit), which ALSO suppresses apiCall's own box_scope guidance — so
84
- // surface it here. Without this the raw {error:'box_scope'} body was dumped and
85
- // rank-gated skills (/idea-triage, /blocker-review, …) read the missing `rank`
86
- // as "pre-rank passthrough" and silently did nothing (reported an empty inbox
87
- // on a dev box). The guided message tells the builder to /builder-reauth.
88
- if (isBoxScope(res)) {
89
- printBoxScopeGuidanceAndExit(); // prints the re-auth guidance + exit(10)
90
- }
91
78
  if (res.data !== undefined && res.data !== null) {
92
79
  console.log(JSON.stringify(res.data, null, 2));
93
80
  }
94
- // task 1003730: `bongos box` in the public CLI is this passthrough aimed at
95
- // POST /box/ensure, so on an instance without the dev-box module a builder got a raw
96
- // 404 body and no idea why. api.js stays generic — it just adds the one explanation
97
- // that a /box/ 404 always means — and only for a reader who typed a box command.
98
- if (res.status === 404 && /^\/api\/(?:bongos\/v1|bongos|gds)\/box\b/.test(urlPath)) {
99
- const { available } = await instanceDevBoxSupport();
100
- if (!available) {
101
- console.error(`\n✖ ${devBoxUnavailableMessage()}`);
102
- for (const line of plainCardLines('dev_box_unavailable')) console.error(line);
103
- }
104
- }
105
81
  // task 1002734: process.exit() here aborted node 5/5 on Windows —
106
82
  // "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c"
107
83
  // — AFTER the JSON had already printed, so every caller that checks the exit code
@@ -77,11 +77,6 @@ const FILES = [
77
77
  'scripts/gds/release.js',
78
78
  'scripts/gds/worktree-claim-guard.js', // release.js warns through it; pure, zero requires
79
79
 
80
- // Get onto a real environment without installing one: the cloud dev box.
81
- 'scripts/gds/shell.js',
82
- 'scripts/gds/code.js',
83
- 'scripts/gds/box-connect-lib.js',
84
-
85
80
  // Module files the above touch. Each imports nothing from the kernel at load time.
86
81
  'modules/lifecycle/task-classifier.js',
87
82
  'modules/lifecycle/task-visuals.js',
@@ -126,7 +121,6 @@ const FORBIDDEN_PREFIXES = [
126
121
  'public/',
127
122
  'modules/hall-ui/',
128
123
  '.github/',
129
- 'devbox-app/',
130
124
  ];
131
125
  const FORBIDDEN_BASENAMES = [
132
126
  'pool.js', 'auth.js', 'db.js', 'server.js', 'serve-internal.js',
@@ -141,9 +135,6 @@ const VERBS = {
141
135
  login: { script: 'login.js', group: 'account', summary: 'Sign in to an instance (bongos login <url> — browser click, no paste)' },
142
136
  reauth: { script: 'reauth.js', group: 'account', summary: 'Refresh your session' },
143
137
  setup: { script: 'setup.js', group: 'account', summary: 'Finish builder setup on this instance (disciplines, consent)' },
144
- box: { script: 'api.js', prefixArgs: ['POST', '/api/bongos/box/ensure'], group: 'account', summary: 'Turn on / wake your cloud dev box' },
145
- shell: { script: 'shell.js', group: 'account', summary: 'Open a terminal on your dev box' },
146
- code: { script: 'code.js', group: 'account', summary: 'Open VS Code on your dev box over Remote-SSH' },
147
138
 
148
139
  start: { script: 'start.js', group: 'work', summary: 'List tasks you can claim right now' },
149
140
  status: { script: 'status.js', group: 'work', summary: 'Version / criterion progress (bongos status C8)', selfHelp: true },
@@ -159,9 +150,11 @@ const VERBS = {
159
150
  // the reason and the actual next step, because a newcomer hitting a bare "unknown command" learns
160
151
  // nothing — that silence is the exact failure this whole task is fixing.
161
152
  //
162
- // The next step it names is CONDITIONAL prose, not a promise: dev boxes are a control-plane
163
- // capability many instances run without (task 1003730). The dispatcher has no session or manifest
164
- // yet, so it must not claim `bongos shell` will work — that verb makes the determination itself.
153
+ // The next step used to be CONDITIONAL prose offering a cloud dev box first. The dev box was
154
+ // retired (task 1003889), so the one unconditional route — a checkout of the project repo — is
155
+ // now the only branch. ADR 0258's exclusion of claim/ship is UNCHANGED and deliberate: this is
156
+ // the PRE-checkout CLI, and a claim that cannot materialise into a checkout is worse than one
157
+ // that says where to go.
165
158
  const CHECKOUT_ONLY = {
166
159
  claim: 'Claiming a task writes code, so it needs a real checkout and a worktree.',
167
160
  ship: 'Shipping grades, merges and deploys from a real checkout.',
@@ -298,8 +291,8 @@ function helpText() {
298
291
  }
299
292
  lines.push('', ' help Show this help', ' version Show the bongos version');
300
293
  lines.push('');
301
- lines.push('Writing code (claim, ship) happens in a checkout. \`bongos shell\` opens one');
302
- lines.push('on a cloud dev box with nothing to install locally.');
294
+ lines.push('Writing code (claim, ship) happens in a checkout of the project repo,');
295
+ lines.push('where the full CLI is already installed.');
303
296
  return lines.join('\\n');
304
297
  }
305
298
 
@@ -329,13 +322,8 @@ function main(argv) {
329
322
  if (CHECKOUT_ONLY[verb]) {
330
323
  process.stderr.write(
331
324
  'bongos: "' + verb + '" is not in the public CLI.\\n ' + CHECKOUT_ONLY[verb] + '\\n\\n' +
332
- 'If this instance offers cloud dev boxes, you can get a checkout without\\n' +
333
- 'installing anything locally:\\n' +
334
- ' bongos shell open a terminal on your cloud dev box\\n' +
335
- ' bongos code open VS Code on it over Remote-SSH\\n\\n' +
336
- 'Not every instance runs dev boxes. If yours does not, bongos shell says so,\\n' +
337
- 'and the route is a checkout of the project repo instead. The full CLI\\n' +
338
- '(including ' + verb + ') is installed in both.\\n'
325
+ 'The route is a checkout of the project repo, where the full CLI\\n' +
326
+ '(including ' + verb + ') is installed. Ask the project owner for repo access.\\n'
339
327
  );
340
328
  return 2;
341
329
  }
@@ -379,7 +367,7 @@ function packageJsonSource(version) {
379
367
  return JSON.stringify({
380
368
  name: PACKAGE_NAME,
381
369
  version,
382
- description: 'The Cloud Bongos CLI — sign in to an instance, see what you can build, and get onto a dev box. Client only: no server.',
370
+ description: 'The Cloud Bongos CLI — sign in to an instance and see what you can build. Client only: no server.',
383
371
  bin: { bongos: 'bin/bongos.js' },
384
372
  files: ['bin/', ...roots, 'README.md'],
385
373
  engines: { node: '>=20' },
@@ -398,8 +386,8 @@ function packageJsonSource(version) {
398
386
  function readmeSource(version) {
399
387
  return `# ${PACKAGE_NAME}
400
388
 
401
- The [Cloud Bongos](https://cloudbongos.com) CLI. Sign in to an instance, see what you can pick
402
- up, and get onto a dev box — from a terminal, with nothing installed.
389
+ The [Cloud Bongos](https://cloudbongos.com) CLI. Sign in to an instance and see what you can
390
+ pick up — from a terminal, with nothing installed.
403
391
 
404
392
  ## Start here
405
393
 
@@ -421,9 +409,8 @@ bongos start
421
409
 
422
410
  ## Commands
423
411
 
424
- Run \`bongos help\` for the current list. In short: \`login\`, \`reauth\`, \`setup\`, \`box\`,
425
- \`shell\`, \`code\` for your account and environment; \`start\`, \`status\`, \`task\`, \`recall\`,
426
- \`cost\`, \`release\`, \`api\` for the work.
412
+ Run \`bongos help\` for the current list. In short: \`login\`, \`reauth\`, \`setup\` for your
413
+ account; \`start\`, \`status\`, \`task\`, \`recall\`, \`cost\`, \`release\`, \`api\` for the work.
427
414
 
428
415
  \`bongos api\` is the escape hatch — it calls any endpoint on the instance as you, so anything
429
416
  the web hall can do is reachable from the terminal:
@@ -434,14 +421,9 @@ bongos api GET /api/bongos/me
434
421
 
435
422
  ## Writing code
436
423
 
437
- \`claim\` and \`ship\` need a real checkout, so they aren't in this package. The shortest path to
438
- one is a cloud dev box, which needs nothing on your machine:
439
-
440
- \`\`\`sh
441
- bongos box # turn it on
442
- bongos shell # a terminal on it, full CLI already installed
443
- bongos code # or VS Code over Remote-SSH
444
- \`\`\`
424
+ \`claim\` and \`ship\` need a real checkout, so they aren't in this package. Ask the project's
425
+ owner for repo access and clone it — the full CLI is installed there, and every command in
426
+ this package works the same way from inside it.
445
427
 
446
428
  ## What this package is
447
429
 
@@ -37,7 +37,7 @@ const { renderInstanceClaudeMd } = require('./claude-md-template'); // task 2104
37
37
 
38
38
  // script basename (e.g. "ship.js") → dedicated bongos verb, for verbs that map to a bare script with
39
39
  // NO prefixArgs. Verbs whose script is driven by prefixArgs (box → api.js) are EXCLUDED so a generic
40
- // `node scripts/gds/api.js …` never mis-maps to `bongos box`; those fall to `bongos exec`. `exec`
40
+ // `node scripts/gds/dev.js …` never mis-maps to `bongos serve`; those fall to `bongos exec`. `exec`
41
41
  // itself has no script and is skipped.
42
42
  function scriptToVerb(verbs = VERBS) {
43
43
  const m = {};
@@ -533,50 +533,6 @@ function printUnauthGuidanceAndExit() {
533
533
  process.exit(2);
534
534
  }
535
535
 
536
- // task 1050 (C): is this response the dev-box bootstrap-scoped 403? A box's baked
537
- // session (source='box', #919/ADR 0053) may only call the 4 on-box bootstrap
538
- // endpoints; everything else (claim/ship/me/…) returns a 403 box_scope.
539
- //
540
- // task 1002162: accept BOTH body shapes. The live server emits the ADR 0116 (R01)
541
- // envelope — `{ error: { code, message, details? } }` from api-errors.js
542
- // `envelope()` — so `res.data.error` is an OBJECT and the old `=== 'box_scope'`
543
- // string test could never match. Both call sites below were therefore dead code
544
- // (box-scope was only ever caught by box-auth-check.js instead). Older/flat paths
545
- // still return `{ error: 'box_scope' }`, so normalize the same way
546
- // claim.js:formatClaimFailure does rather than swapping one shape for the other.
547
- //
548
- // Pure + exported so the detector is unit-tested against the real envelope.
549
- function isBoxScope(res) {
550
- if (!res || res.status !== 403 || !res.data) return false;
551
- const errRaw = res.data.error;
552
- const code = errRaw !== null && typeof errRaw === 'object' ? errRaw.code : errRaw;
553
- return code === 'box_scope';
554
- }
555
-
556
- let _boxScopeExitInFlight = false;
557
-
558
- // task 1050 (C): a box_scope 403 used to surface as a raw `failed to load
559
- // profile: { error: 'box_scope', … }` dump (the #1027 nightmare's first wall).
560
- // Turn it into the same UI-first guided recovery as a broken session: tell the
561
- // builder this is a bootstrap-only box session and walk them to re-auth. PRINT
562
- // only (no auto-open: the command runs ON the box, but the builder's browser is
563
- // on their own machine — they open the hall there).
564
- function printBoxScopeGuidanceAndExit() {
565
- console.error('');
566
- console.error('✖ This is a box-scoped session — it can only run dev-box bootstrap calls,');
567
- console.error(' not claim / ship / grade / etc. (the box holds no full-authority token by');
568
- console.error(' design — ADR 0053). Re-auth to a real CLI session, then re-run:');
569
- console.error('');
570
- console.error(isTerminalSurface() ? ' bongos reauth' : ' /builder-reauth (or: node scripts/gds/reauth.js)');
571
- console.error('');
572
- console.error(` It opens your Settings page (${hallBase()}/settings) on`);
573
- console.error(' YOUR machine — under "CLI Access" click "Re-issue CLI token", copy the one-liner, paste it back.');
574
- console.error(' No terminal Device Flow. Once re-authed, re-run whichever command you were');
575
- console.error(' trying — if it had already done work, it resumes from where it stopped (task 941).');
576
- for (const line of plainCardLines('box_scope')) console.error(line);
577
- console.error('');
578
- process.exit(10); // 10 = "re-auth needed" (matches reauth.js's exit code)
579
- }
580
536
 
581
537
  // ---- fail-closed instance guard (task 2102) --------------------------------
582
538
  // A half-configured instance folder used to fail THROUGH: with no local session
@@ -659,7 +615,7 @@ function printInstanceMismatchAndExit({ expected, actual }) {
659
615
  console.error(` ${pfx}_API_BASE=${actual} <your command>`);
660
616
  for (const line of plainCardLines('instance_mismatch')) console.error(line);
661
617
  console.error('');
662
- process.exit(3); // 3 = instance mismatch (distinct from 2 = unauth, 10 = box-scope)
618
+ process.exit(3); // 3 = instance mismatch (distinct from 2 = unauth)
663
619
  }
664
620
 
665
621
  // Fail-closed instance guard for RAW-fetch READ commands (task 2124 / demo-audit C2).
@@ -689,7 +645,7 @@ async function assertInstanceMatch({ allowInstanceMismatch = false } = {}) {
689
645
  * decide whether to re-auth automatically).
690
646
  */
691
647
  async function apiCall(method, urlPath, body = null, opts = {}) {
692
- const { allowUnauthenticated = false, allowBoxScope = false, allowInstanceMismatch = false } = opts;
648
+ const { allowUnauthenticated = false, allowInstanceMismatch = false } = opts;
693
649
  const session = await loadSession();
694
650
  // Fail CLOSED (task 2102) if the session we'd authenticate with belongs to a
695
651
  // different instance than this repo is configured for — never silently borrow
@@ -740,13 +696,6 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
740
696
  _unauthExitInFlight = true;
741
697
  printUnauthGuidanceAndExit();
742
698
  }
743
- // task 1050 (C): a box-scoped session hitting a non-bootstrap endpoint gets a
744
- // guided re-auth path instead of a raw box_scope dump. allowUnauthenticated
745
- // opts out (e.g. a probe that wants to inspect the 403 itself).
746
- if (isBoxScope({ status: res.status, data }) && !allowBoxScope && !allowUnauthenticated && !_boxScopeExitInFlight) {
747
- _boxScopeExitInFlight = true;
748
- printBoxScopeGuidanceAndExit();
749
- }
750
699
  return { status: res.status, ok: res.ok, data };
751
700
  }
752
701
 
@@ -759,7 +708,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
759
708
  // throws on HTTP status; network errors still reject), so the long-standing
760
709
  // apiCall contract holds and status-inspecting callers port 1:1.
761
710
  // • fetch — a wrapper that keeps apiCall's behavior: the founder CLI
762
- // User-Agent, plus the 401 and box_scope 403 auto-exit guidance.
711
+ // User-Agent, plus the 401 auto-exit guidance.
763
712
  // Named methods (api.tasks.getTasksId({ id })) replace hand-built apiCall paths;
764
713
  // api.request(method, path, opts) is the escape hatch for non-spec routes.
765
714
  // The generated client is ESM; we import() it once and cache per (base, token,
@@ -767,7 +716,7 @@ async function apiCall(method, urlPath, body = null, opts = {}) {
767
716
  let _clientModPromise = null;
768
717
  const _clientCache = new Map();
769
718
  async function cliClient(opts = {}) {
770
- const { allowUnauthenticated = false, allowBoxScope = false, allowInstanceMismatch = false } = opts;
719
+ const { allowUnauthenticated = false, allowInstanceMismatch = false } = opts;
771
720
  const session = await loadSession();
772
721
  // Fail CLOSED (task 2102) — the same guard as apiCall, on the OTHER HTTP entry
773
722
  // point. cliClient() is the typed client (api.js + newer scripts route through
@@ -783,7 +732,7 @@ async function cliClient(opts = {}) {
783
732
  }
784
733
  const base = ic.resolveApiBase({ session: session || null });
785
734
  const token = (session && session.token) || null;
786
- const key = `${base}|${token || ''}|${allowUnauthenticated}|${allowBoxScope}`;
735
+ const key = `${base}|${token || ''}|${allowUnauthenticated}`;
787
736
  if (_clientCache.has(key)) return _clientCache.get(key);
788
737
  if (!_clientModPromise) _clientModPromise = import('../../clients/bongos-client/index.mjs');
789
738
  const { createClient } = await _clientModPromise;
@@ -796,13 +745,6 @@ async function cliClient(opts = {}) {
796
745
  _unauthExitInFlight = true;
797
746
  printUnauthGuidanceAndExit();
798
747
  }
799
- if (res.status === 403 && !allowBoxScope && !allowUnauthenticated && !_boxScopeExitInFlight) {
800
- const peek = await res.clone().json().catch(() => null);
801
- if (isBoxScope({ status: 403, data: peek })) {
802
- _boxScopeExitInFlight = true;
803
- printBoxScopeGuidanceAndExit();
804
- }
805
- }
806
748
  return res;
807
749
  };
808
750
  const client = createClient({ baseUrl: `${base}/api/bongos/v1`, token, fetch: authedFetch, throwOnError: false });
@@ -810,64 +752,6 @@ async function cliClient(opts = {}) {
810
752
  return client;
811
753
  }
812
754
 
813
- // ---- does this instance even HAVE dev boxes? (task 1003730) -----------------
814
- // The dev-box module is a CONTROL-PLANE capability: it provisions DigitalOcean droplets, so a
815
- // hosted co-tenant instance runs without it. The instance manifest has always said so —
816
- // GET /api/bongos/instance carries modules.enabled — but no CLI script read it, so `bongos shell`
817
- // on such an instance died with a bare `POST /box/ensure failed (HTTP 404: not_found)`.
818
- //
819
- // That mattered far more than a bad error string: the public CLI (ADR 0258) answers
820
- // `bongos claim` by pointing at `bongos shell`, so on every dev-box-less instance the ONLY route
821
- // it offered to a checkout was a dead end — a newcomer could see the work and not start it.
822
- //
823
- // Cached per process, and FAILS OPEN: a manifest hiccup must never block a working box.
824
- let _devBoxCheck = null;
825
-
826
- // The decision, kept pure so it is testable without a network: does THIS manifest prove the
827
- // instance has no dev box? Anything short of an explicit module roster answers "assume yes" —
828
- // an older core, a truncated body or an unreachable manifest must never block a working box.
829
- function devBoxEnabledInManifest(manifest) {
830
- const enabled = manifest && manifest.modules && manifest.modules.enabled;
831
- if (!enabled || typeof enabled !== 'object') return true;
832
- return enabled['dev-box'] === true;
833
- }
834
-
835
- async function instanceDevBoxSupport() {
836
- if (_devBoxCheck) return _devBoxCheck;
837
- _devBoxCheck = (async () => {
838
- try {
839
- const api = await cliClient();
840
- const res = await api.instance.getInstance();
841
- return { available: devBoxEnabledInManifest(res && res.ok ? res.data : null) };
842
- } catch (_) {
843
- return { available: true };
844
- }
845
- })();
846
- return _devBoxCheck;
847
- }
848
-
849
- // The explanation a reader can act on. Deliberately does NOT name the instance: a co-tenant's
850
- // manifest currently reports the platform's name rather than its own, so naming it would be wrong
851
- // more often than helpful.
852
- function devBoxUnavailableMessage() {
853
- return (
854
- `This instance doesn't offer cloud dev boxes.\n` +
855
- ` It runs without the dev-box module, so there is no box to open a shell on.\n` +
856
- ` Claiming and shipping happen in a checkout of the project's repo, where the full\n` +
857
- ` CLI is installed.\n` +
858
- `\n` +
859
- ` → Ask the project's owner for repo access, then run ${surfaceAction('claim', 'N')} from that checkout.`
860
- );
861
- }
862
-
863
- // Throw that explanation when this instance has no dev box. Callers run it BEFORE any box call,
864
- // so nothing prints a confusing intermediate 404 first.
865
- async function assertDevBoxAvailable() {
866
- const { available } = await instanceDevBoxSupport();
867
- if (available) return;
868
- throw new Error(devBoxUnavailableMessage());
869
- }
870
-
871
755
  async function requireSession() {
872
756
  const s = await loadSession();
873
757
  if (!s || !s.token) {
@@ -1248,13 +1132,7 @@ module.exports = {
1248
1132
  requireSession,
1249
1133
  surfaceAction,
1250
1134
  isTerminalSurface,
1251
- devBoxEnabledInManifest,
1252
- instanceDevBoxSupport,
1253
- devBoxUnavailableMessage,
1254
- assertDevBoxAvailable,
1255
1135
  printUnauthGuidanceAndExit,
1256
- isBoxScope,
1257
- printBoxScopeGuidanceAndExit,
1258
1136
  detectInstanceMismatch,
1259
1137
  assertInstanceMatch,
1260
1138
  instanceOriginHost,
@@ -375,7 +375,7 @@ const ARTIST_GATE_LEVELS = ['off', 'advisory', 'strict'];
375
375
 
376
376
  async function readArtistGate(call = apiCall) {
377
377
  try {
378
- const res = await call('GET', ARTIST_GATE_PATH, null, { allowUnauthenticated: true, allowBoxScope: true });
378
+ const res = await call('GET', ARTIST_GATE_PATH, null, { allowUnauthenticated: true });
379
379
  if (res.status === 404) return { unread: `this instance does not serve ${ARTIST_GATE_PATH} — it predates ADR 0241` };
380
380
  // A 5xx is a gate that EXISTS and BROKE, which is not the same class of
381
381
  // unknown as "no session" or "too old to have the route". The gate is
@@ -324,7 +324,7 @@ async function main() {
324
324
  log('');
325
325
  log(` ✓ Signed in to ${instanceName} as ${who}.`);
326
326
  logOtherInstances(base, log);
327
- log(' Next: bongos start (see what you can claim) · bongos code / bongos shell (get on your dev box)');
327
+ log(' Next: bongos start (see what you can claim)');
328
328
  }
329
329
 
330
330
  // Name the OTHER instances this machine is signed into, and how to move between them (task
@@ -295,18 +295,10 @@ const CARDS = Object.freeze({
295
295
  'Your sign-in to the project has ended.',
296
296
  'I will walk you through signing in again — Settings, then one copy and paste.',
297
297
  'You, to sign in again.', RULE),
298
- box_scope: card(
299
- 'This sign-in is limited on purpose: it can do the work, but not hand it in.',
300
- 'I will walk you through the full sign-in, then hand the work in.',
301
- 'You, to sign in again.', RULE),
302
298
  instance_mismatch: card(
303
299
  'This computer is signed in to a different project than the one this folder belongs to.',
304
300
  'I will show which project each one is, and help you sign in to the right one.',
305
301
  'You, to choose the project.', FAULT),
306
- dev_box_unavailable: card(
307
- 'This project does not offer hosted computers to work from.',
308
- 'We can work from this computer instead.',
309
- null, RULE),
310
302
  no_push_credential: card(
311
303
  'This computer is not allowed to send work to the code host itself. That is deliberate: the server sends it for you.',
312
304
  'I will hand the work in with /builder-ship and read its progress from the server. I will never ask anyone for a password, key or sign-in to work round this.',
@@ -11,7 +11,7 @@
11
11
  const fs = require('node:fs');
12
12
  const os = require('node:os');
13
13
  const path = require('node:path');
14
- const { cliClient, isBoxScope } = require('./cli-lib');
14
+ const { cliClient } = require('./cli-lib');
15
15
  const { gitArgvOk, gitOk, sanitizeGitHubRefs, shellOk } = require('./ship-git.js');
16
16
  const { buildPrCreateArgv, buildPrMergeArgv, deployRunSucceeded, deployedCommitStatus, fetchDeployedVersion, ghArgvOk, ghJson, pushVia, resolveInstanceBase } = require('./ship-deploy-target.js');
17
17
  const { normalizeApiError } = require('./ship-io.js');
@@ -177,26 +177,10 @@ async function ciLandServer(taskId, branch, valueSummary) {
177
177
  return landBailed('could not build the branch bundle', strandCommand(taskId, 'server'));
178
178
  }
179
179
  const bundleB64 = fs.readFileSync(bundlePath).toString('base64');
180
- // allowBoxScope: handle the box_scope 403 locally (graceful "stays at
181
- // confirmed, re-auth + re-ship") instead of cli-lib's central exit — task
182
- // 1050. (In practice unreachable: a box session can't have claimed/confirmed
183
- // to get here. Kept as a defensive, graceful path.)
184
- const r = await (await cliClient({ allowBoxScope: true })).tasks.postTasksIdPublishBranch({
180
+ const r = await (await cliClient()).tasks.postTasksIdPublishBranch({
185
181
  id: taskId,
186
182
  body: { branch, head_sha: headSha, value_summary: valueSummary, bundle_b64: bundleB64 },
187
183
  });
188
- // task 1002593: ONE definition of "is this a box-scope refusal". This site used
189
- // to hand-roll the comparison, which is how it silently died when routes moved
190
- // to the object error envelope — `r.data.error` stopped being the bare string
191
- // and the branch could never fire. Normalizing here fixed the symptom but left
192
- // a SECOND predicate for the same question, and two copies of a predicate are
193
- // how one of them gets a fix the other never sees (migration 177 dropping 176's
194
- // blocker clause is the same shape). cli-lib's isBoxScope is the one every
195
- // other caller already uses; this is the last site that did not.
196
- if (isBoxScope(r)) {
197
- console.log(' ci-land: this session is BOX-SCOPED — run /builder-reauth (browser, no terminal) to mint a real session, then re-ship. Task stays at confirmed.');
198
- return landBailed('this CLI session is box-scoped', '/builder-reauth');
199
- }
200
184
  if (r.status === 503) {
201
185
  console.log(' ci-land: server-mediated push is not configured on the server yet (no GitHub App nor GITHUB_PUSH_TOKEN — operator action, ADR 0055). Task stays at confirmed.');
202
186
  return landBailed('server-mediated push is not configured (operator action)', strandCommand(taskId, 'server'));
package/src/module-api.js CHANGED
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.902'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.904'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -15,7 +15,7 @@ const { VERBS, GDS_DIR, helpText } = bongos;
15
15
  const BIN = fileURLToPath(new URL('../bin/bongos.js', import.meta.url));
16
16
 
17
17
  // The 7 verbs the task's "done when" explicitly requires to dispatch.
18
- const REQUIRED = ['start', 'claim', 'ship', 'box', 'cost', 'status', 'recall'];
18
+ const REQUIRED = ['start', 'claim', 'ship', 'cost', 'status', 'recall'];
19
19
 
20
20
  test('every required verb is registered', () => {
21
21
  for (const v of REQUIRED) assert.ok(VERBS[v], `missing required verb: ${v}`);
@@ -68,7 +68,7 @@ test('bare `bongos` prints help and exits 0', () => {
68
68
  assert.match(r.stdout, /Usage: bongos/);
69
69
  });
70
70
 
71
- test('a prefix-args verb (box) targets the generic api.js caller', () => {
72
- assert.equal(VERBS.box.script, 'api.js');
73
- assert.deepEqual(VERBS.box.prefixArgs, ['POST', '/api/gds/box/ensure']);
71
+ test('a prefix-args verb (serve) carries the args pinned in front of the user\'s', () => {
72
+ assert.equal(VERBS.serve.script, 'dev.js');
73
+ assert.deepEqual(VERBS.serve.prefixArgs, ['--server']);
74
74
  });
@@ -5,7 +5,7 @@
5
5
  // 1. bongosify — dedicated verb where one exists (bongos ship), else `bongos exec scripts/gds/<x>.js`;
6
6
  // $CLAUDE_PROJECT_DIR/quotes stripped; args preserved; .claude/hooks/ refs left instance-local;
7
7
  // idempotent.
8
- // 2. scriptToVerb — excludes prefixArgs verbs so api.js never mis-maps to `bongos box`.
8
+ // 2. scriptToVerb — excludes prefixArgs verbs so dev.js never mis-maps to `bongos serve`.
9
9
  // 3. transformSettings — rewrites the scripts/gds hook commands AND permission rules, keeps .claude/hooks/ ones.
10
10
  // 4. materializeClaude — copies + transforms skills into a target dir (no leftover node scripts/gds),
11
11
  // writes settings.json, copies hooks; and NO-OPS when the target IS the core root (self-copy guard).
@@ -219,9 +219,6 @@ test('the packed tarball installs and every verb runs with no repo present', (t)
219
219
  // guidance, still exits 10, so every assertion below is unchanged.
220
220
  reauth: ['reauth', '--no-open'],
221
221
  setup: ['setup'],
222
- box: ['box'],
223
- shell: ['shell'],
224
- code: ['code'],
225
222
  start: ['start'],
226
223
  status: ['status'],
227
224
  task: ['task', 'show', '1'],
@@ -290,7 +287,7 @@ test('the packed tarball installs and every verb runs with no repo present', (t)
290
287
  const res = runVerb([verb]);
291
288
  const all = `${res.stdout}\n${res.stderr}`;
292
289
  assert.match(all, /is not in the public CLI/, `bongos ${verb} must say why it is absent`);
293
- assert.match(all, /bongos shell/, `bongos ${verb} must point at a way to get a checkout`);
290
+ assert.match(all, /checkout of the project repo/, `bongos ${verb} must point at a way to get a checkout`);
294
291
  assert.ok(!/unknown command/.test(all), `bongos ${verb} must not read as a typo`);
295
292
  }
296
293