@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.
- package/.bongos-core.json +37 -67
- package/bin/bongos.js +7 -13
- package/docs/copy-inventory.md +6 -7
- package/docs/copy-registry.json +9 -18
- 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/hall-ui/public/collab-lib.js +22 -0
- package/modules/hall-ui/public/collab.js +17 -2
- package/modules/hall-ui/public/hall-kit.js +11 -5
- package/modules/hall-ui/public/profile-charts.js +1 -1
- 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/collab_page.mjs +44 -0
- package/tests/hall_kit_charts.mjs +38 -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
|
@@ -418,11 +418,25 @@
|
|
|
418
418
|
const whoName = o.name || o.login || `builder ${o.id}`;
|
|
419
419
|
const who = escapeHtml(whoName);
|
|
420
420
|
const face = { kind: 'person', id: o.id, name: whoName, avatar: null, initial: L.initialOf(whoName) };
|
|
421
|
+
|
|
422
|
+
// A SENTENCE TAKES roleWord() — the rule dom-utils.js writes down beside the helper
|
|
423
|
+
// (task 1004041). This row took neither helper and interpolated the raw column, so a
|
|
424
|
+
// pair whose role was unset rendered the database's own word: "A unclassified — you
|
|
425
|
+
// are a engineer", which is V2's named failure and a wrong article in the same line.
|
|
426
|
+
// Where a role is missing the comparison has nothing to compare, so the row says the
|
|
427
|
+
// one thing that is still true rather than naming a value nobody chose.
|
|
428
|
+
// roleIsUnset() is how a caller asks (dom-utils.js:198). Spelling 'unclassified' here
|
|
429
|
+
// would be a second definition of the axis in a third file — the drift V1 forbids, and
|
|
430
|
+
// the exact shape this review was filed to remove.
|
|
431
|
+
const theirs = window.OTB.roleIsUnset(o.craft) ? '' : window.OTB.roleWord(o.craft);
|
|
432
|
+
const mine = window.OTB.roleIsUnset(o.myCraft) ? '' : window.OTB.roleWord(o.myCraft);
|
|
433
|
+
const pairLine = L.pairSentence(theirs, mine);
|
|
434
|
+
|
|
421
435
|
return `<li class="ov-row ov-row--lead cb-row">
|
|
422
436
|
${faceHtml(face)}
|
|
423
437
|
<div class="ov-row__main">
|
|
424
438
|
<a class="ov-row__title" href="/builders/profile?id=${escapeHtml(String(o.id))}">${who}</a>
|
|
425
|
-
<p class="ov-row__sub"
|
|
439
|
+
<p class="ov-row__sub">${escapeHtml(pairLine)}</p>
|
|
426
440
|
</div>
|
|
427
441
|
<div class="ov-row__right">
|
|
428
442
|
<div class="ov-row__actions">
|
|
@@ -511,6 +525,7 @@
|
|
|
511
525
|
|
|
512
526
|
load().catch(() => {
|
|
513
527
|
const sub = $('#cb-sub');
|
|
514
|
-
|
|
528
|
+
// V6: a message that stops someone points at the next move in the same breath.
|
|
529
|
+
if (sub) sub.textContent = 'Could not load requests — reload to try again.';
|
|
515
530
|
});
|
|
516
531
|
})();
|
|
@@ -1141,7 +1141,13 @@
|
|
|
1141
1141
|
activeDays += 1;
|
|
1142
1142
|
totalShips += c;
|
|
1143
1143
|
}
|
|
1144
|
-
|
|
1144
|
+
// The window's NAME belongs with the window (task 1003621, artist review of 1003583).
|
|
1145
|
+
// The aria summary described it as "the last year" while the caption under the same
|
|
1146
|
+
// grid said "this year" — a rolling 53 weeks and a calendar year are not the same
|
|
1147
|
+
// span, and V3 asks the accessible line and the visible one to state the same fact.
|
|
1148
|
+
// Both now read this.
|
|
1149
|
+
const spanLabel = weeks === 53 ? 'the last year' : `the last ${weeks} week${weeks === 1 ? '' : 's'}`;
|
|
1150
|
+
return { counts, startSun, endOrd, weeks, activeDays, totalShips, spanLabel };
|
|
1145
1151
|
}
|
|
1146
1152
|
|
|
1147
1153
|
// calendarGridHtml(days, opts) — a year of ship-days as a contribution calendar:
|
|
@@ -1168,7 +1174,7 @@
|
|
|
1168
1174
|
function calendarGridHtml(days, opts = {}) {
|
|
1169
1175
|
const win = calendarWindow(days, opts);
|
|
1170
1176
|
if (!win) return ''; // nothing real to draw → caller's empty state
|
|
1171
|
-
const { counts, startSun, endOrd, weeks, activeDays, totalShips } = win;
|
|
1177
|
+
const { counts, startSun, endOrd, weeks, activeDays, totalShips, spanLabel } = win;
|
|
1172
1178
|
const ordToYmd = calOrdToYmd;
|
|
1173
1179
|
|
|
1174
1180
|
// level 0 = empty; 1..5 map to the anti-gaming daily cap (streak.js caps a day at
|
|
@@ -1188,10 +1194,10 @@
|
|
|
1188
1194
|
}
|
|
1189
1195
|
|
|
1190
1196
|
// The window is DESCRIBED, not assumed: a caller passing weeks:6 used to be handed
|
|
1191
|
-
// an aria label saying "over the last year" (task 1003583).
|
|
1192
|
-
|
|
1197
|
+
// an aria label saying "over the last year" (task 1003583). The phrase now comes from
|
|
1198
|
+
// calendarWindow so the caption beneath the grid can say the same words.
|
|
1193
1199
|
const summary = opts.ariaLabel
|
|
1194
|
-
|| `Ships per day over ${
|
|
1200
|
+
|| `Ships per day over ${spanLabel}: ${totalShips} ship${totalShips === 1 ? '' : 's'} across ${activeDays} day${activeDays === 1 ? '' : 's'}.`;
|
|
1195
1201
|
// The legend is aria-hidden: it decorates the bands, it carries no fact the cell
|
|
1196
1202
|
// titles and the summary do not already state in words.
|
|
1197
1203
|
const legend = '<div class="kit-cal__key" aria-hidden="true"><span class="kit-cal__key-label">less</span>'
|
|
@@ -284,7 +284,7 @@
|
|
|
284
284
|
// The scalars in words, in the .pchart__read slot the other figures use for their
|
|
285
285
|
// crosshair readout — here it is static because the calendar's reading is per-cell.
|
|
286
286
|
const read = `Current streak ${cur} day${cur === 1 ? '' : 's'} \u00b7 longest ${lng} \u00b7 `
|
|
287
|
-
+ `${active} day${active === 1 ? '' : 's'} shipped
|
|
287
|
+
+ `${active} day${active === 1 ? '' : 's'} shipped in ${win ? win.spanLabel : 'the last year'}`;
|
|
288
288
|
const html = `<figure class="pchart" id="${esc(id)}">
|
|
289
289
|
<figcaption class="pchart__title">Your shipping year</figcaption>
|
|
290
290
|
<p class="pchart__read">${esc(read)}</p>
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.904",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.904",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.904",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/scripts/gds/api.js
CHANGED
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
// on a 2xx, 1 on any non-2xx or transport error, 2 on bad invocation.
|
|
19
19
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
|
-
const { cliClient, arg, stripBom, resolveQuiet,
|
|
22
|
-
instanceDevBoxSupport, devBoxUnavailableMessage } = require('./cli-lib');
|
|
23
|
-
const { plainCardLines } = require('./plain-cards');
|
|
21
|
+
const { cliClient, arg, stripBom, resolveQuiet, cliExit, repairMsysMangledPath } = require('./cli-lib');
|
|
24
22
|
|
|
25
23
|
|
|
26
24
|
(async () => {
|
|
@@ -77,31 +75,9 @@ const { plainCardLines } = require('./plain-cards');
|
|
|
77
75
|
return cliExit(1); // task 1002734: never process.exit() after a request
|
|
78
76
|
}
|
|
79
77
|
if (!quiet) console.error(`${method} ${urlPath} → ${res.status}`);
|
|
80
|
-
// task 1501: on a dev box the baked session is box-scoped (ADR 0053) and every
|
|
81
|
-
// non-bootstrap call (e.g. /me, /inbox) returns 403 box_scope. We pass
|
|
82
|
-
// allowUnauthenticated above (so a 401 stays a plain error, not the setup
|
|
83
|
-
// short-circuit), which ALSO suppresses apiCall's own box_scope guidance — so
|
|
84
|
-
// surface it here. Without this the raw {error:'box_scope'} body was dumped and
|
|
85
|
-
// rank-gated skills (/idea-triage, /blocker-review, …) read the missing `rank`
|
|
86
|
-
// as "pre-rank passthrough" and silently did nothing (reported an empty inbox
|
|
87
|
-
// on a dev box). The guided message tells the builder to /builder-reauth.
|
|
88
|
-
if (isBoxScope(res)) {
|
|
89
|
-
printBoxScopeGuidanceAndExit(); // prints the re-auth guidance + exit(10)
|
|
90
|
-
}
|
|
91
78
|
if (res.data !== undefined && res.data !== null) {
|
|
92
79
|
console.log(JSON.stringify(res.data, null, 2));
|
|
93
80
|
}
|
|
94
|
-
// task 1003730: `bongos box` in the public CLI is this passthrough aimed at
|
|
95
|
-
// POST /box/ensure, so on an instance without the dev-box module a builder got a raw
|
|
96
|
-
// 404 body and no idea why. api.js stays generic — it just adds the one explanation
|
|
97
|
-
// that a /box/ 404 always means — and only for a reader who typed a box command.
|
|
98
|
-
if (res.status === 404 && /^\/api\/(?:bongos\/v1|bongos|gds)\/box\b/.test(urlPath)) {
|
|
99
|
-
const { available } = await instanceDevBoxSupport();
|
|
100
|
-
if (!available) {
|
|
101
|
-
console.error(`\n✖ ${devBoxUnavailableMessage()}`);
|
|
102
|
-
for (const line of plainCardLines('dev_box_unavailable')) console.error(line);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
81
|
// task 1002734: process.exit() here aborted node 5/5 on Windows —
|
|
106
82
|
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c"
|
|
107
83
|
// — AFTER the JSON had already printed, so every caller that checks the exit code
|
|
@@ -77,11 +77,6 @@ const FILES = [
|
|
|
77
77
|
'scripts/gds/release.js',
|
|
78
78
|
'scripts/gds/worktree-claim-guard.js', // release.js warns through it; pure, zero requires
|
|
79
79
|
|
|
80
|
-
// Get onto a real environment without installing one: the cloud dev box.
|
|
81
|
-
'scripts/gds/shell.js',
|
|
82
|
-
'scripts/gds/code.js',
|
|
83
|
-
'scripts/gds/box-connect-lib.js',
|
|
84
|
-
|
|
85
80
|
// Module files the above touch. Each imports nothing from the kernel at load time.
|
|
86
81
|
'modules/lifecycle/task-classifier.js',
|
|
87
82
|
'modules/lifecycle/task-visuals.js',
|
|
@@ -126,7 +121,6 @@ const FORBIDDEN_PREFIXES = [
|
|
|
126
121
|
'public/',
|
|
127
122
|
'modules/hall-ui/',
|
|
128
123
|
'.github/',
|
|
129
|
-
'devbox-app/',
|
|
130
124
|
];
|
|
131
125
|
const FORBIDDEN_BASENAMES = [
|
|
132
126
|
'pool.js', 'auth.js', 'db.js', 'server.js', 'serve-internal.js',
|
|
@@ -141,9 +135,6 @@ const VERBS = {
|
|
|
141
135
|
login: { script: 'login.js', group: 'account', summary: 'Sign in to an instance (bongos login <url> — browser click, no paste)' },
|
|
142
136
|
reauth: { script: 'reauth.js', group: 'account', summary: 'Refresh your session' },
|
|
143
137
|
setup: { script: 'setup.js', group: 'account', summary: 'Finish builder setup on this instance (disciplines, consent)' },
|
|
144
|
-
box: { script: 'api.js', prefixArgs: ['POST', '/api/bongos/box/ensure'], group: 'account', summary: 'Turn on / wake your cloud dev box' },
|
|
145
|
-
shell: { script: 'shell.js', group: 'account', summary: 'Open a terminal on your dev box' },
|
|
146
|
-
code: { script: 'code.js', group: 'account', summary: 'Open VS Code on your dev box over Remote-SSH' },
|
|
147
138
|
|
|
148
139
|
start: { script: 'start.js', group: 'work', summary: 'List tasks you can claim right now' },
|
|
149
140
|
status: { script: 'status.js', group: 'work', summary: 'Version / criterion progress (bongos status C8)', selfHelp: true },
|
|
@@ -159,9 +150,11 @@ const VERBS = {
|
|
|
159
150
|
// the reason and the actual next step, because a newcomer hitting a bare "unknown command" learns
|
|
160
151
|
// nothing — that silence is the exact failure this whole task is fixing.
|
|
161
152
|
//
|
|
162
|
-
// The next step
|
|
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.904'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -15,7 +15,7 @@ const { VERBS, GDS_DIR, helpText } = bongos;
|
|
|
15
15
|
const BIN = fileURLToPath(new URL('../bin/bongos.js', import.meta.url));
|
|
16
16
|
|
|
17
17
|
// The 7 verbs the task's "done when" explicitly requires to dispatch.
|
|
18
|
-
const REQUIRED = ['start', 'claim', 'ship', '
|
|
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
|
|