@bongos/core 1.19.902 → 1.19.903

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.
@@ -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.903'; // 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
 
@@ -205,7 +205,7 @@ function guidanceOutput(fn, env) {
205
205
  return out;
206
206
  }
207
207
 
208
- const GUIDANCE = ['printUnauthGuidanceAndExit', 'printBoxScopeGuidanceAndExit', 'requireSession'];
208
+ const GUIDANCE = ['printUnauthGuidanceAndExit', 'requireSession'];
209
209
 
210
210
  test('no guidance path speaks slash commands to a terminal reader', () => {
211
211
  for (const fn of GUIDANCE) {
@@ -220,7 +220,6 @@ test('the agent still gets its slash commands from those same paths', () => {
220
220
  // The inverse guard: a blanket rewrite that "fixed" the leak by deleting the slash commands
221
221
  // would break the reader they were written for, and nothing else here would notice.
222
222
  assert.match(runGuidance('printUnauthGuidanceAndExit', {}), /\/builder-reauth/);
223
- assert.match(runGuidance('printBoxScopeGuidanceAndExit', {}), /\/builder-reauth/);
224
223
  assert.match(runGuidance('requireSession', {}), /\/builder-setup/);
225
224
  });
226
225
 
@@ -231,7 +230,6 @@ test('every shipped script that names a slash command routes it through the surf
231
230
  const ALLOWED = new Set([
232
231
  'scripts/gds/cli-lib.js', // agent side of the three branches, proven above
233
232
  'scripts/gds/setup.js', // re-run + newcomer-path lines, asserted below
234
- 'scripts/gds/box-connect-lib.js',// key-registration hint, asserted below
235
233
  'scripts/gds/surface.js', // the switch itself
236
234
  'scripts/gds/start.js', // section 3's documented agent-only exemptions
237
235
  ]);
@@ -257,88 +255,3 @@ test('setup.js routes its newcomer path and re-run lines through the surface', (
257
255
  assert.ok(!/console\.log\(' 2\. Survey work: \/builder-start'\)/.test(src), 'hard-coded /builder-start left in setup.js');
258
256
  assert.match(src, /re-run \$\{surfaceAction\('setup'\)\}/, 'the re-run lines must use surfaceAction');
259
257
  });
260
-
261
- test('box-connect-lib names the box command for the reader it has', () => {
262
- const src = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'box-connect-lib.js'), 'utf8');
263
- assert.ok(!/add it via \/builder-box/.test(src), 'hard-coded /builder-box left in box-connect-lib.js');
264
- assert.match(src, /add it via \$\{surfaceAction\('box'\)\}/);
265
- });
266
-
267
- // ── 5. The dead end: an instance with no dev box (task 1003730) ─────────────────────────────
268
- //
269
- // `bongos claim N` refuses and points at `bongos shell`. On an instance whose manifest carries
270
- // no dev-box module that call 404s, so the public CLI's only offered route to a checkout was a
271
- // hard error — a newcomer could see the work and not start it. The manifest always said so;
272
- // nothing read it.
273
-
274
- const cliLib = require(path.join(REPO_ROOT, 'scripts', 'gds', 'cli-lib.js'));
275
-
276
- test('the manifest decides, and only an explicit roster can prove absence', () => {
277
- assert.equal(cliLib.devBoxEnabledInManifest({ modules: { enabled: { 'dev-box': true } } }), true);
278
- assert.equal(cliLib.devBoxEnabledInManifest({ modules: { enabled: { economy: true } } }), false);
279
- });
280
-
281
- test('the check fails OPEN — a hiccup must never block a working box', () => {
282
- // Every one of these is "I could not tell", and the safe answer to that is to proceed and let
283
- // the real API call speak. Failing closed here would break `bongos shell` on every instance
284
- // running a core too old to publish a module roster.
285
- for (const manifest of [null, undefined, {}, { modules: {} }, { modules: { enabled: null } }, 'nonsense']) {
286
- assert.equal(cliLib.devBoxEnabledInManifest(manifest), true, `should fail open for ${JSON.stringify(manifest)}`);
287
- }
288
- });
289
-
290
- test('the explanation names a command the reader can actually run', () => {
291
- // It routes through the same switch as everything else: a terminal reader must not be handed
292
- // `/builder-claim`, and an agent in a checkout must still get it.
293
- const prev = process.env.BONGOS_SURFACE;
294
- try {
295
- process.env.BONGOS_SURFACE = 'terminal';
296
- const term = cliLib.devBoxUnavailableMessage();
297
- assert.ok(!/\/builder-/.test(term), `slash command in the terminal dev-box message: ${term}`);
298
- assert.match(term, /bongos claim N/);
299
- assert.match(term, /doesn't offer cloud dev boxes/);
300
- assert.match(term, /checkout/, 'must name the actual alternative, not just refuse');
301
-
302
- delete process.env.BONGOS_SURFACE;
303
- assert.match(cliLib.devBoxUnavailableMessage(), /\/builder-claim N/, 'agents keep the slash command');
304
- } finally {
305
- if (prev === undefined) delete process.env.BONGOS_SURFACE;
306
- else process.env.BONGOS_SURFACE = prev;
307
- }
308
- });
309
-
310
- test('both box entry points check BEFORE they touch the box API', () => {
311
- // Ordering is the whole point: key registration hits the box API too, so gating only the
312
- // ensure call still printed "Could not register your key (not_found)" first.
313
- const src = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'box-connect-lib.js'), 'utf8');
314
- assert.match(
315
- src,
316
- /async function prepareConnection\([^)]*\) \{[\s\S]{0,400}?await assertDevBoxAvailable\(\);[\s\S]{0,200}?ensureSshKey/,
317
- 'prepareConnection must assert before ensureSshKey/key registration',
318
- );
319
- assert.match(
320
- src,
321
- /async function ensureBoxActive\([^)]*\) \{\s*await assertDevBoxAvailable\(\);/,
322
- 'ensureBoxActive must assert before postBoxEnsure',
323
- );
324
- });
325
-
326
- test('the raw box passthrough explains a 404 too', () => {
327
- // `bongos box` is api.js aimed at POST /box/ensure, so without this it dumped a bare 404 body.
328
- const src = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'api.js'), 'utf8');
329
- assert.match(src, /instanceDevBoxSupport/, 'api.js must consult the dev-box check');
330
- assert.match(src, /res\.status === 404/, 'only on a 404');
331
- assert.match(src, /\\\/box\\b/, 'and only for a box path — api.js stays generic');
332
- });
333
-
334
- test('the generated dispatcher does not promise a dev box it cannot verify', () => {
335
- // The dispatcher has no session or manifest yet, so it must present `bongos shell` as
336
- // conditional. Stating it flatly is what sent newcomers into the 404.
337
- const gen = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'build-cli-package.js'), 'utf8');
338
- assert.ok(
339
- !/'To get a checkout without installing anything locally/.test(gen),
340
- 'the unconditional promise is back in the checkout-only refusal',
341
- );
342
- assert.match(gen, /If this instance offers cloud dev boxes/);
343
- assert.match(gen, /Not every instance runs dev boxes/);
344
- });
@@ -15,6 +15,7 @@
15
15
 
16
16
  import { test } from 'node:test';
17
17
  import assert from 'node:assert/strict';
18
+ import { readFileSync } from 'node:fs';
18
19
  import fs from 'node:fs';
19
20
  import path from 'node:path';
20
21
  import vm from 'node:vm';
@@ -642,6 +643,43 @@ test('CAL: a one-week window says "week", and the full window still says "the la
642
643
  assert.match(full, /over the last year/, 'the 53-week window is a year and should read like one');
643
644
  });
644
645
 
646
+ // --- task 1003621 (artist review of 1003583): ONE name for the drawn window ---------
647
+
648
+ test('CAL: the window carries its own name, and the aria summary uses that name', () => {
649
+ // The half the arithmetic fix left behind. 1003583 made the summary and the caption
650
+ // COUNT the same window; they still DESCRIBED it differently — the aria summary said
651
+ // "the last year" (a rolling 53 weeks, which is what is drawn) while the caption under
652
+ // the same grid said "this year" (a calendar year, which is not). V3 asks the
653
+ // accessible line and the visible one to state the same fact, so the phrase now lives
654
+ // on the window itself and both read it.
655
+ for (const weeks of [1, 6, 53]) {
656
+ const win = kit.calendarWindow([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03', weeks });
657
+ assert.ok(win && typeof win.spanLabel === 'string' && win.spanLabel.length,
658
+ `calendarWindow must name its own window (weeks=${weeks})`);
659
+ const html = kit.calendarGridHtml([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03', weeks });
660
+ assert.ok(html.includes(`over ${win.spanLabel}:`),
661
+ `the aria summary must use the window's own name, not a second copy of the rule (weeks=${weeks})`);
662
+ }
663
+ });
664
+
665
+ test('CAL: the caption under the grid cannot drift back to "this year"', () => {
666
+ // The caption is rendered by profile-charts.js from win.spanLabel. What is pinned here
667
+ // is the phrase it must never go back to: a calendar year is not the window drawn, and
668
+ // a reader comparing the number to the picture is the person who pays for the gap.
669
+ const win = kit.calendarWindow([{ local_date: '2026-09-03', count: 1 }], { today: '2026-09-03', weeks: 53 });
670
+ assert.equal(win.spanLabel, 'the last year');
671
+
672
+ // Asserted against the SOURCE because the caption is assembled in a browser global the
673
+ // unit lane does not render. The same idiom tests/modules_route_wiring.mjs uses.
674
+ const src = readFileSync(new URL('../modules/hall-ui/public/profile-charts.js', import.meta.url), 'utf8');
675
+ assert.match(src, /spanLabel/, 'the caption must take the window name from the window');
676
+ assert.doesNotMatch(
677
+ src, /shipped this year/,
678
+ 'the caption said "this year" while the grid drew a rolling 53 weeks and the aria '
679
+ + 'summary said "the last year" — one window, two names, which is the V3 break',
680
+ );
681
+ });
682
+
645
683
  test('CAL: the summary counts only the days the grid actually draws', () => {
646
684
  // 2026-09-03 is a Thursday, so a 2-week grid starts at Sunday 2026-08-23. A ship on
647
685
  // 2026-08-01 is real history the caller supplied, but it is off this picture, and it