@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
@@ -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
- });
@@ -421,6 +421,50 @@ test('the SIDEBAR badge: the Collab item declares the help_requests count', () =
421
421
  assert.match(fn, /href: '\/collab'/, 'pointing at the page, not the retired hash view');
422
422
  });
423
423
 
424
+ // ---- THE MINGLE ROW SPEAKS THE ROLE ---------------------------------------
425
+ // task 1003967 (artist review of 1003960). Run, not grepped: the first version of this
426
+ // guard matched identifier names in the source, which would have agreed with a swapped
427
+ // article just as happily as with a correct one. The sentence is pure, so execute it.
428
+
429
+ test('the mingle sub-line reads as English for every pair, and never shows the column', () => {
430
+ const L = lib();
431
+ const OTB = domUtils();
432
+ const word = (d) => (OTB.roleIsUnset(d) ? '' : OTB.roleWord(d));
433
+ const line = (a, b) => L.pairSentence(word(a), word(b));
434
+
435
+ assert.equal(line('engineer', 'artist'), 'An engineer — you are an artist. Mingle pairs across roles.');
436
+ assert.equal(line('artist', 'ideator'), 'An artist — you are an ideator. Mingle pairs across roles.');
437
+
438
+ // 'ui' is the acronym exception roleWord() already makes, and it is why the article
439
+ // cannot be a plain vowel test — "an UI" is how that shortcut fails.
440
+ assert.equal(line('ui', 'engineer'), 'A UI — you are an engineer. Mingle pairs across roles.');
441
+ assert.equal(line('engineer', 'ui'), 'An engineer — you are a UI. Mingle pairs across roles.');
442
+ });
443
+
444
+ test('a pair with an unset role drops the comparison rather than naming the default', () => {
445
+ const L = lib();
446
+ const OTB = domUtils();
447
+ const word = (d) => (OTB.roleIsUnset(d) ? '' : OTB.roleWord(d));
448
+
449
+ // The defect this replaced rendered the database's own word — "A unclassified — you are
450
+ // a engineer" — which is the example V2 names. Nothing a person did not choose may reach
451
+ // the sentence, whichever side is missing.
452
+ for (const pair of [[OTB.ROLE_UNSET, 'engineer'], ['engineer', OTB.ROLE_UNSET], [null, 'artist'], ['', '']]) {
453
+ const out = L.pairSentence(word(pair[0]), word(pair[1]));
454
+ assert.equal(out, 'Mingle pairs across roles.', `pair ${JSON.stringify(pair)}`);
455
+ assert.doesNotMatch(out, /unclassified/);
456
+ }
457
+ });
458
+
459
+ test('aOrAn is the article rule, stated once', () => {
460
+ const L = lib();
461
+ assert.equal(L.aOrAn('engineer'), 'an');
462
+ assert.equal(L.aOrAn('artist'), 'an');
463
+ assert.equal(L.aOrAn('ideator'), 'an');
464
+ assert.equal(L.aOrAn('UI'), 'a', 'the acronym is spoken "you-eye", so it takes "a"');
465
+ assert.equal(L.aOrAn('ui'), 'a');
466
+ });
467
+
424
468
  // ---- THE ANSWERABLE CLAUSE ------------------------------------------------
425
469
  test('the asker can settle in place, with both outcomes', () => {
426
470
  const js = read('modules', 'hall-ui', 'public', 'collab.js');
@@ -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
@@ -107,7 +107,7 @@ test('every card is complete, and short enough to be read', () => {
107
107
 
108
108
  test('the restrictions that must never be worked around are marked by_design', () => {
109
109
  for (const code of ['REBASE_REQUIRED', 'INSUFFICIENT_RANK', 'rank_too_low_for_task', 'session_already_has_active_claim',
110
- 'main_checkout_claim', 'ship:protected_branch', 'ship:auth', 'box_scope', 'no_push_credential', 'grade_refused_authorization']) {
110
+ 'main_checkout_claim', 'ship:protected_branch', 'ship:auth', 'no_push_credential', 'grade_refused_authorization']) {
111
111
  assert.equal(cardFor(code)?.by_design, true, code);
112
112
  }
113
113
  assert.match(cardFor('no_push_credential').next, /never ask/i);
@@ -164,7 +164,7 @@ test('a person at a terminal gets no card — it is written in the agent\'s voic
164
164
 
165
165
  test('the sign-in printers, the release refusals and the claim guard each print their card', () => {
166
166
  const wired = {
167
- 'scripts/gds/cli-lib.js': ['session_invalid', 'box_scope', 'no_session'],
167
+ 'scripts/gds/cli-lib.js': ['session_invalid', 'no_session'],
168
168
  'scripts/gds/release.js': ['no_active_claim', 'CLAIM_SESSION_MISMATCH'],
169
169
  'scripts/gds/claim.js': ['main_checkout_claim'],
170
170
  'scripts/gds/ship-land.js': ['land_pending', 'reconcile_blocked'],
@@ -12,7 +12,7 @@
12
12
  // modules/discord/discord-channels.js): dependency-free substrate
13
13
  // (permission-path-check runs in a bare git hook), source-parsed literals
14
14
  // (src/bongos/db.js — gate-review.js + diagram-facts.js regex its text),
15
- // standalone CLIs (sequence, box-connect-lib), the require-free dev-box
15
+ // standalone CLIs (sequence), the require-free dev-box
16
16
  // module template (boxes.js), zero-dep catalog + its text-parsing auditor
17
17
  // (route-rank-check), and the hall browser statics.
18
18
  // 3. NO NEW parallel map can land: a repo scan flags any rank→number map or
@@ -38,7 +38,6 @@ const lifecycleDb = require(path.join(ROOT, 'modules', 'lifecycle', 'db.js'));
38
38
  const ppc = require(path.join(ROOT, 'src', 'bongos', 'permission-path-check.js'));
39
39
  const rrc = require(path.join(ROOT, 'src', 'bongos', 'route-rank-check.js'));
40
40
  const sequence = require(path.join(ROOT, 'scripts', 'gds', 'sequence.js'));
41
- const boxConnect = require(path.join(ROOT, 'scripts', 'gds', 'box-connect-lib.js'));
42
41
  const boxes = require(path.join(ROOT, 'modules', 'dev-box', 'boxes.js'));
43
42
  const catalog = require(path.join(ROOT, 'modules', 'government', 'catalog.js'));
44
43
 
@@ -82,8 +81,8 @@ test('1-based local copies match: permission-path-check, sequence CLI', () => {
82
81
  assert.deepEqual(sequence.RANK_TIER, TIER, 'sequence.js CLI copy drifted');
83
82
  });
84
83
 
85
- test('0-based local copies match: box-connect-lib CLI, dev-box boxes.js', () => {
86
- for (const [name, map] of [['box-connect-lib', boxConnect.RANK_ORDER], ['boxes', boxes.RANK_ORDER]]) {
84
+ test('0-based local copies match: dev-box boxes.js', () => {
85
+ for (const [name, map] of [['boxes', boxes.RANK_ORDER]]) {
87
86
  assert.deepEqual(Object.keys(map).sort(), [...RANK_KEYS].sort(), `${name} keys drifted`);
88
87
  for (const r of RANK_KEYS) {
89
88
  assert.equal(map[r], TIER[r] - 1, `${name}.RANK_ORDER.${r} must be tier-1 (0-based)`);
@@ -193,7 +192,6 @@ const MAP_ALLOWLIST = new Set([
193
192
  'src/bongos/db.js',
194
193
  'src/bongos/permission-path-check.js',
195
194
  'scripts/gds/sequence.js',
196
- 'scripts/gds/box-connect-lib.js',
197
195
  'modules/dev-box/boxes.js',
198
196
  'modules/hall-ui/public/builders.js',
199
197
  // task 1003206 carved the hall page scripts up. builders.js KEPT its map (the