@bongos/core 1.19.901 → 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.
- package/.bongos-core.json +43 -68
- package/bin/bongos.js +7 -13
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/bongos-cli-release.md +3 -2
- package/docs/recipes/instance-first-run.md +1 -2
- package/docs/recipes/local-dev.md +0 -2
- package/modules/economy/credits.js +216 -9
- package/modules/economy/reward-policy.js +34 -1
- package/modules/economy/reward.js +4 -0
- package/modules/hall-ui/public/hall-kit.js +11 -5
- package/modules/hall-ui/public/profile-charts.js +1 -1
- package/modules/ideas/module.json +2 -1
- package/modules/ideas/routes/inbox.js +26 -0
- package/modules/lifecycle/db-ship.js +15 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/api.js +1 -25
- package/scripts/gds/build-cli-package.js +17 -35
- package/scripts/gds/claude-materialize.js +1 -1
- package/scripts/gds/cli-lib.js +5 -127
- package/scripts/gds/go-live.js +1 -1
- package/scripts/gds/login.js +1 -1
- package/scripts/gds/plain-cards.js +0 -8
- package/scripts/gds/ship-land.js +2 -18
- package/src/module-api.js +1 -1
- package/tests/bongos_dispatch.mjs +4 -4
- package/tests/claude_materialize.mjs +1 -1
- package/tests/cli_package.mjs +1 -4
- package/tests/cli_surface.mjs +1 -88
- package/tests/hall_kit_charts.mjs +38 -0
- package/tests/idea_descent_ship_credit.mjs +481 -0
- package/tests/plain_cards.mjs +2 -2
- package/tests/rank_tier_single_source.mjs +3 -5
- package/scripts/gds/box-connect-lib.js +0 -550
- package/scripts/gds/code.js +0 -105
- package/scripts/gds/shell.js +0 -102
- package/tests/bongos_box_connect.mjs +0 -166
- package/tests/box_scope_predicate.mjs +0 -117
- package/tests/cli_box_scope.mjs +0 -92
|
@@ -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
|
|
163
|
-
//
|
|
164
|
-
//
|
|
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
|
|
302
|
-
lines.push('
|
|
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
|
-
'
|
|
333
|
-
'
|
|
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
|
|
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
|
|
402
|
-
up
|
|
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
|
|
425
|
-
|
|
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.
|
|
438
|
-
|
|
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/
|
|
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 = {};
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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,
|
|
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}
|
|
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,
|
package/scripts/gds/go-live.js
CHANGED
|
@@ -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
|
|
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
|
package/scripts/gds/login.js
CHANGED
|
@@ -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)
|
|
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.',
|
package/scripts/gds/ship-land.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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', '
|
|
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 (
|
|
72
|
-
assert.equal(VERBS.
|
|
73
|
-
assert.deepEqual(VERBS.
|
|
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
|
|
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).
|
package/tests/cli_package.mjs
CHANGED
|
@@ -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, /
|
|
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
|
|
package/tests/cli_surface.mjs
CHANGED
|
@@ -205,7 +205,7 @@ function guidanceOutput(fn, env) {
|
|
|
205
205
|
return out;
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
const GUIDANCE = ['printUnauthGuidanceAndExit', '
|
|
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
|