@bongos/core 1.19.580 → 1.19.581
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 +27 -17
- package/bin/bongos.js +10 -1
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/build-cli-package.js +8 -2
- package/scripts/gds/cli-lib.js +11 -3
- package/scripts/gds/recall.js +1 -1
- package/scripts/gds/start.js +39 -26
- package/scripts/gds/surface.js +89 -0
- package/src/module-api.js +1 -1
- package/tests/cli_surface.mjs +146 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.581",
|
|
6
|
+
"core_contract": "1.19.581",
|
|
7
|
+
"source_commit": "03f7d02a4e669a2fa53f34e6375ebb6b7a046a27",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-07T20:15:39.494Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 453,
|
|
13
13
|
"agent_docs_stubbed": 24,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2053,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2530,
|
|
20
|
+
"tree_sha256": "5c954594f6fc90e5180384a00a4a9cb3258580a0f9df36d629dbf71731a1fd4e",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -367,7 +367,7 @@
|
|
|
367
367
|
{
|
|
368
368
|
"path": "bin/bongos.js",
|
|
369
369
|
"mode": "0000644",
|
|
370
|
-
"sha256": "
|
|
370
|
+
"sha256": "d585fc874c0a45f5d02d5753025d4761ca9b817ae9e5c556891b5c5c444cffde"
|
|
371
371
|
},
|
|
372
372
|
{
|
|
373
373
|
"path": "clients/bongos-client/README.md",
|
|
@@ -2697,7 +2697,7 @@
|
|
|
2697
2697
|
{
|
|
2698
2698
|
"path": "docs/file-map.md",
|
|
2699
2699
|
"mode": "0000644",
|
|
2700
|
-
"sha256": "
|
|
2700
|
+
"sha256": "8b9c303b7cb35b420ff13c0a265fe2e505769c6d1625977eb85c0d077ff1ddf2"
|
|
2701
2701
|
},
|
|
2702
2702
|
{
|
|
2703
2703
|
"path": "docs/handoff-template.md",
|
|
@@ -2707,7 +2707,7 @@
|
|
|
2707
2707
|
{
|
|
2708
2708
|
"path": "docs/module-api-changelog.md",
|
|
2709
2709
|
"mode": "0000644",
|
|
2710
|
-
"sha256": "
|
|
2710
|
+
"sha256": "fad24c9257355302696388602bba42f251696c20d4e2380f668339a823a84558"
|
|
2711
2711
|
},
|
|
2712
2712
|
{
|
|
2713
2713
|
"path": "docs/modules-contract.md",
|
|
@@ -7572,12 +7572,12 @@
|
|
|
7572
7572
|
{
|
|
7573
7573
|
"path": "package-lock.json",
|
|
7574
7574
|
"mode": "0000644",
|
|
7575
|
-
"sha256": "
|
|
7575
|
+
"sha256": "06ccc86510eed776b32a243487781c83fefcf48d33053f9f3a313e90b8364a49"
|
|
7576
7576
|
},
|
|
7577
7577
|
{
|
|
7578
7578
|
"path": "package.json",
|
|
7579
7579
|
"mode": "0000644",
|
|
7580
|
-
"sha256": "
|
|
7580
|
+
"sha256": "f145cacd4b2198eb4c6f6cd07a1ac30ebe653b3aaf34c7768cf0ad8dc0561db4"
|
|
7581
7581
|
},
|
|
7582
7582
|
{
|
|
7583
7583
|
"path": "public-docs/index.html",
|
|
@@ -7777,7 +7777,7 @@
|
|
|
7777
7777
|
{
|
|
7778
7778
|
"path": "scripts/gds/build-cli-package.js",
|
|
7779
7779
|
"mode": "0000644",
|
|
7780
|
-
"sha256": "
|
|
7780
|
+
"sha256": "760dbc00a1d8ed5dfa2184e93f9106383bbb36c7fdd973ddfc67b7e03f3bc2f8"
|
|
7781
7781
|
},
|
|
7782
7782
|
{
|
|
7783
7783
|
"path": "scripts/gds/bump-version.js",
|
|
@@ -7837,7 +7837,7 @@
|
|
|
7837
7837
|
{
|
|
7838
7838
|
"path": "scripts/gds/cli-lib.js",
|
|
7839
7839
|
"mode": "0000644",
|
|
7840
|
-
"sha256": "
|
|
7840
|
+
"sha256": "beb9d466f7623cae12788f23d0e30fbf3a520fec769e416ce3ad6458b23ceb2b"
|
|
7841
7841
|
},
|
|
7842
7842
|
{
|
|
7843
7843
|
"path": "scripts/gds/client-baseurl-guard.js",
|
|
@@ -8402,7 +8402,7 @@
|
|
|
8402
8402
|
{
|
|
8403
8403
|
"path": "scripts/gds/recall.js",
|
|
8404
8404
|
"mode": "0000644",
|
|
8405
|
-
"sha256": "
|
|
8405
|
+
"sha256": "d0ad3b72fd0db0384f0d2212b50650e7e081beb62296ac3ea256047d6c3d8ec0"
|
|
8406
8406
|
},
|
|
8407
8407
|
{
|
|
8408
8408
|
"path": "scripts/gds/redteam-patrol.js",
|
|
@@ -8812,7 +8812,7 @@
|
|
|
8812
8812
|
{
|
|
8813
8813
|
"path": "scripts/gds/start.js",
|
|
8814
8814
|
"mode": "0000644",
|
|
8815
|
-
"sha256": "
|
|
8815
|
+
"sha256": "061a477c618ca76aba4f3abfb2d22ad89ceb0bee46bac65b139ad8ee2f30413a"
|
|
8816
8816
|
},
|
|
8817
8817
|
{
|
|
8818
8818
|
"path": "scripts/gds/status.js",
|
|
@@ -8824,6 +8824,11 @@
|
|
|
8824
8824
|
"mode": "0000644",
|
|
8825
8825
|
"sha256": "072c493cc549e16c6045abf5bf74a8cfe0004b45844c6f4fe9e846d55d88bf34"
|
|
8826
8826
|
},
|
|
8827
|
+
{
|
|
8828
|
+
"path": "scripts/gds/surface.js",
|
|
8829
|
+
"mode": "0000644",
|
|
8830
|
+
"sha256": "cc730dc2d59f5d6f06bad92e9c2b69cb7835142deefd31a8dfdd58b300c38e30"
|
|
8831
|
+
},
|
|
8827
8832
|
{
|
|
8828
8833
|
"path": "scripts/gds/task.js",
|
|
8829
8834
|
"mode": "0000644",
|
|
@@ -9282,7 +9287,7 @@
|
|
|
9282
9287
|
{
|
|
9283
9288
|
"path": "src/module-api.js",
|
|
9284
9289
|
"mode": "0000644",
|
|
9285
|
-
"sha256": "
|
|
9290
|
+
"sha256": "b928f49545fb1adb6ce26a620a73083be9d71257e6171a533d672190e86495a6"
|
|
9286
9291
|
},
|
|
9287
9292
|
{
|
|
9288
9293
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9884,6 +9889,11 @@
|
|
|
9884
9889
|
"mode": "0000644",
|
|
9885
9890
|
"sha256": "a57516ed9e904cf1c86fe99688415f834dcae1a19f01b22e986f199fa5473258"
|
|
9886
9891
|
},
|
|
9892
|
+
{
|
|
9893
|
+
"path": "tests/cli_surface.mjs",
|
|
9894
|
+
"mode": "0000644",
|
|
9895
|
+
"sha256": "589ecc771ab18829049eabc6183939843072160e8476828e246eadd8ff6f0709"
|
|
9896
|
+
},
|
|
9887
9897
|
{
|
|
9888
9898
|
"path": "tests/cli_token_reissue.mjs",
|
|
9889
9899
|
"mode": "0000644",
|
package/bin/bongos.js
CHANGED
|
@@ -204,7 +204,16 @@ function main(argv) {
|
|
|
204
204
|
|
|
205
205
|
if (COMPILED) return runBundled(verb, def, forwarded);
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
// A person typed `bongos <verb>`, so this output is for a person: no `[otb-card]` directives
|
|
208
|
+
// addressed to a model, no `/builder-*` slash commands that exist only inside Claude Code
|
|
209
|
+
// (task 1003680, scripts/gds/surface.js). The skills and the card-delivery hook run
|
|
210
|
+
// `node scripts/gds/<verb>.js` DIRECTLY and never pass through here, so the agent card shape
|
|
211
|
+
// is untouched. `bongos exec` above is deliberately left alone — it is the agent's escape
|
|
212
|
+
// hatch into a core script.
|
|
213
|
+
const res = spawnSync(process.execPath, [scriptPath, ...forwarded], {
|
|
214
|
+
stdio: 'inherit',
|
|
215
|
+
env: { ...process.env, BONGOS_SURFACE: 'terminal' },
|
|
216
|
+
});
|
|
208
217
|
if (res.error) {
|
|
209
218
|
process.stderr.write(`bongos: failed to run "${verb}": ${res.error.message}\n`);
|
|
210
219
|
return 1;
|
package/docs/file-map.md
CHANGED
|
@@ -168,6 +168,7 @@
|
|
|
168
168
|
│ ├── ship-visual.js ← the OPTIONAL `--visual <image> [--visual-alt "…"]` leg of a ship (task 1003109): screens the file BEFORE the claim resolves (bad input costs no claim), uploads it AFTER (a failed picture never fails a ship)
|
|
169
169
|
│ ├── package-core.js ← package the core as a versioned, installable artifact + pin manifest (`bongos package-core`; R84, ADR 0100 §1, [#1688](https://example.com/builders#/task/1688)): isPublishable() selection + mirror redaction + fail-closed no-leak gate → dist/bongos-core-<version>.{tgz,manifest.json}; version = src/module-api CORE_VERSION. Recipe: docs/recipes/packaging-the-core.md
|
|
170
170
|
│ ├── build-cli-package.js ← generate the PUBLIC, client-only `@cloudbongos/cli` npm package from this repo (task 1003679, ADR 0258): declared FILES manifest (no server/routes/auth/pool/provisioning) + generated dispatcher, README and package.json whose `files` array is DERIVED from the manifest; refuses to emit on a relative `import()` the manifest omits, and on a redaction hit (non-loopback IPv4, token/key shapes, instance domains + owner login read from config/branding.json). Sibling of package-core.js, opposite audience: that one packages the private core for an instance, this one packages the client surface for the public. Proven by tests/cli_package.mjs, which installs the packed tarball into an empty dir and runs every verb. `npm publish` is the OWNER's step, never a builder's → dist/cloudbongos-cli/
|
|
171
|
+
│ ├── surface.js ← who the CLI output is FOR: agent (default) or terminal (task 1003680). The `bongos` dispatcher sets BONGOS_SURFACE=terminal when a person types a verb; the skills and the card hook run `node scripts/gds/<verb>.js` directly and keep the agent card byte-identical. Never inferred from isTTY — a session's stdout is not a TTY either, so sniffing would invert it. Helpers: action() (/builder-ship N vs bongos ship N), taskRef(), strong()/em()/code(), table() (markdown vs aligned columns). Guarded by tests/cli_surface.mjs
|
|
171
172
|
│ ├── sandbox-stage.js ← stage the working tree on the live game preview (box OR local) for browser review before ship; resolvePreviewContext picks the context (`/builder-stage`; #927, ADR 0046; local: task 1056)
|
|
172
173
|
│ ├── local-preview.js ← builder CLI for the LOCAL sandbox: start/stop/status/restart/logs/url of the game-only preview at http://localhost:3100 (`npm run preview`; task 1056)
|
|
173
174
|
│ ├── local-preview-lib.js ← core of the local sandbox launcher (detached `node src/preview-server.js`, pid/health/port mgmt); shared by local-preview.js + sandbox-stage.js (task 1056)
|
|
@@ -1609,5 +1609,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1609
1609
|
landed since 1.19.578 with no explicit bump. run 34149317186. (task 1002620)
|
|
1610
1610
|
1.19.580 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1611
1611
|
landed since 1.19.579 with no explicit bump. run 34153217035. (task 1002620)
|
|
1612
|
+
1.19.581 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1613
|
+
landed since 1.19.580 with no explicit bump. run 34158683008. (task 1002620)
|
|
1612
1614
|
---------------------------------------------------------------------------
|
|
1613
1615
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.581",
|
|
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.581",
|
|
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.581",
|
|
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",
|
|
@@ -35,7 +35,7 @@ const REPO_ROOT = path.join(__dirname, '..', '..');
|
|
|
35
35
|
|
|
36
36
|
// The package's OWN version — deliberately independent of the core's. The core moves many times
|
|
37
37
|
// a day (CI auto-patch, ADR 0161); the CLI's public contract should not.
|
|
38
|
-
const PACKAGE_VERSION = '0.1.
|
|
38
|
+
const PACKAGE_VERSION = '0.1.1';
|
|
39
39
|
const PACKAGE_NAME = '@cloudbongos/cli';
|
|
40
40
|
|
|
41
41
|
// ── What the package carries ────────────────────────────────────────────────────────────────
|
|
@@ -54,6 +54,7 @@ const FILES = [
|
|
|
54
54
|
'scripts/gds/onboarding-config.js', // lazy dep of setup.js
|
|
55
55
|
'scripts/gds/install-git-hooks.js', // lazy dep of setup.js
|
|
56
56
|
'scripts/gds/preflight.js',
|
|
57
|
+
'scripts/gds/surface.js', // who the output is for: agent vs terminal (task 1003680)
|
|
57
58
|
|
|
58
59
|
// Read the board and move your own work along.
|
|
59
60
|
'scripts/gds/start.js',
|
|
@@ -327,7 +328,12 @@ function main(argv) {
|
|
|
327
328
|
|
|
328
329
|
const target = path.join(SCRIPTS_DIR, def.script);
|
|
329
330
|
const spawnArgs = [target, ...(def.prefixArgs || []), ...args.slice(1)];
|
|
330
|
-
|
|
331
|
+
// Everyone reaching this package is a person at a terminal, so say so: scripts/gds/surface.js
|
|
332
|
+
// switches off the [otb-card] model directives and the /builder-* slash commands (task 1003680).
|
|
333
|
+
const res = spawnSync(process.execPath, spawnArgs, {
|
|
334
|
+
stdio: 'inherit',
|
|
335
|
+
env: { ...process.env, BONGOS_SURFACE: 'terminal' },
|
|
336
|
+
});
|
|
331
337
|
if (res.error) {
|
|
332
338
|
process.stderr.write('bongos: could not run ' + def.script + ': ' + res.error.message + '\\n');
|
|
333
339
|
return 1;
|
package/scripts/gds/cli-lib.js
CHANGED
|
@@ -749,6 +749,14 @@ function tableTask(t) {
|
|
|
749
749
|
// Maps stage -> a concrete CLI/web action the builder can take RIGHT NOW.
|
|
750
750
|
// Stage-internal hints (STAGE_HINTS in src/bongos/onboarding-state.js) are
|
|
751
751
|
// the canonical description; the per-CLI commands are the action verbs.
|
|
752
|
+
// The nudges name an action the reader can run. Which spelling is right depends on who is
|
|
753
|
+
// reading — see scripts/gds/surface.js (task 1003680). Fail-open: a missing surface module must
|
|
754
|
+
// never break a nudge.
|
|
755
|
+
function surfaceAction(verb, arg) {
|
|
756
|
+
try { return require('./surface').action(verb, arg); }
|
|
757
|
+
catch (_) { return `/builder-${verb}${arg == null ? '' : ` ${arg}`}`; }
|
|
758
|
+
}
|
|
759
|
+
|
|
752
760
|
function nudgeActionForStage(stage) {
|
|
753
761
|
switch (stage) {
|
|
754
762
|
case 'auth_complete':
|
|
@@ -756,11 +764,11 @@ function nudgeActionForStage(stage) {
|
|
|
756
764
|
case 'pick_disciplines':
|
|
757
765
|
return `Choose the work that calls you — open the hall at ${hallBase()} and pick engineer, artist, ideator, or any mix.`;
|
|
758
766
|
case 'first_start':
|
|
759
|
-
return
|
|
767
|
+
return `Run ${surfaceAction('start')} to survey what you can claim.`;
|
|
760
768
|
case 'first_claim':
|
|
761
|
-
return
|
|
769
|
+
return `Pick a task from ${surfaceAction('start')} and claim it with ${surfaceAction('claim', 'N')}.`;
|
|
762
770
|
case 'ship_three':
|
|
763
|
-
return
|
|
771
|
+
return `Carry three tasks through with ${surfaceAction('ship')} — the third makes you a Thetes.`;
|
|
764
772
|
default:
|
|
765
773
|
return null;
|
|
766
774
|
}
|
package/scripts/gds/recall.js
CHANGED
|
@@ -45,7 +45,7 @@ function truncate(s, n) {
|
|
|
45
45
|
|
|
46
46
|
function reportError(r) {
|
|
47
47
|
if (r.status === 401) {
|
|
48
|
-
console.error(
|
|
48
|
+
console.error(`Not signed in. Run ${require('./surface').action('setup')} first — recall searches as you, so it can scope results to your rank.`);
|
|
49
49
|
} else if (r.status === 429) {
|
|
50
50
|
console.error(`Rate limited (${r.data && r.data.retry_after_seconds ? r.data.retry_after_seconds + 's' : 'slow down'}). Recall is capped per builder per minute.`);
|
|
51
51
|
} else {
|
package/scripts/gds/start.js
CHANGED
|
@@ -156,7 +156,7 @@ function quietStatusLine(header) {
|
|
|
156
156
|
if (ideas != null) parts.push(`${ideas} idea${ideas === 1 ? '' : 's'} in inbox`);
|
|
157
157
|
if (blockers != null) parts.push(`${blockers} open blocker${blockers === 1 ? '' : 's'}`);
|
|
158
158
|
if (parts.length === 0) return null;
|
|
159
|
-
return
|
|
159
|
+
return surface.em(parts.join(' · '));
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// Calibrated estimate, compact: ~8m / ~2h / ~1.5h.
|
|
@@ -201,6 +201,7 @@ function whatItIs(t) {
|
|
|
201
201
|
// The claimable table — # / Version / Type / Reward / Est. / What it is.
|
|
202
202
|
// Ids are emitted as hall links (renders as a clean #N) — a bare "#NNN" in a
|
|
203
203
|
// relayed chat message autolinks to GitHub and 404s (CLAUDE.md ref rule).
|
|
204
|
+
const surface = require('./surface');
|
|
204
205
|
const HALL_TASK = `${hallBase()}#/task/`;
|
|
205
206
|
function claimableTable(rows, rewardFromServer) {
|
|
206
207
|
// On a cost-plus-only instance the Reward column is an advertised estimate that
|
|
@@ -208,15 +209,18 @@ function claimableTable(rows, rewardFromServer) {
|
|
|
208
209
|
// and footnote what IS paid below the table (task 1002469).
|
|
209
210
|
const perTask = rewardPolicy(rewardFromServer).perTaskCredit;
|
|
210
211
|
const rewardHead = perTask ? 'Reward' : 'Reward (est.)';
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
const headers = ['#', 'Version', 'Type', rewardHead, 'Est.', 'What it is'];
|
|
213
|
+
const body = rows.map((t) => [
|
|
214
|
+
surface.taskRef(t.id, HALL_TASK),
|
|
215
|
+
cell(t.version_id || '?'),
|
|
216
|
+
cell(disciplineLabel(t) || '—'),
|
|
217
|
+
`${t.credits_reward ?? 0}c`,
|
|
218
|
+
estimateShort(t),
|
|
219
|
+
cell(whatItIs(t)),
|
|
220
|
+
]);
|
|
221
|
+
const out = [surface.table(headers, body)];
|
|
218
222
|
const cpNote = costPlusOnlyNote(rewardFromServer);
|
|
219
|
-
if (cpNote) out.push('',
|
|
223
|
+
if (cpNote) out.push('', surface.em(cpNote));
|
|
220
224
|
return out.join('\n');
|
|
221
225
|
}
|
|
222
226
|
|
|
@@ -268,7 +272,8 @@ function rebaseWarningModel(needsRebaseTasks) {
|
|
|
268
272
|
function rebaseWarningMarkdown(needsRebaseTasks) {
|
|
269
273
|
const w = rebaseWarningModel(needsRebaseTasks);
|
|
270
274
|
if (!w) return null;
|
|
271
|
-
return `⚠️
|
|
275
|
+
return `⚠️ ${surface.strong('Rebase owed — your queue is GATED.')} ${w.count} ${w.noun} can't land and block new claims`
|
|
276
|
+
+ ` until you rebase + re-ship (or ${surface.code(surface.action('release'))}) ${w.pronoun}: ${w.refs}.`;
|
|
272
277
|
}
|
|
273
278
|
|
|
274
279
|
// The widget warning block (Tabler alert icon, danger-tinted), or '' when none.
|
|
@@ -302,6 +307,9 @@ const CARD_DIRECTIVE = {
|
|
|
302
307
|
markdown: '[otb-card] stdout is the finished /builder-start card (markdown). Output it verbatim as your reply, then stop — no rebuilt table, recommendation, nudge, or recap.',
|
|
303
308
|
};
|
|
304
309
|
function emitCardDirective(form) {
|
|
310
|
+
// Addressed to the model, not the reader. A terminal user has no card to relay and no
|
|
311
|
+
// show_widget to call, so this line is pure confusion there (task 1003680).
|
|
312
|
+
if (surface.isTerminal()) return;
|
|
305
313
|
process.stderr.write(CARD_DIRECTIVE[form] + '\n');
|
|
306
314
|
}
|
|
307
315
|
|
|
@@ -550,20 +558,20 @@ function isLocalOrigin(origin) {
|
|
|
550
558
|
// ships inside the PRIVATE @bongos/core package) even exists.
|
|
551
559
|
function instanceNotRunningCard({ origin, depsMissing } = {}) {
|
|
552
560
|
const out = [];
|
|
553
|
-
out.push(
|
|
561
|
+
out.push(surface.strong("builder-start — this instance isn't reachable yet"));
|
|
554
562
|
out.push('');
|
|
555
563
|
|
|
556
564
|
if (!isLocalOrigin(origin)) {
|
|
557
|
-
out.push(`I couldn't reach
|
|
565
|
+
out.push(`I couldn't reach ${surface.code(origin)}. That instance is ${surface.strong('hosted')} — it isn't this machine's server to start, so there's nothing to bring up here.`);
|
|
558
566
|
out.push('');
|
|
559
|
-
out.push(`1.
|
|
567
|
+
out.push(`1. ${surface.strong('The board is a website, and it needs nothing installed')} — open ${origin}/builders to see and claim work right now.`);
|
|
560
568
|
out.push(`2. If that doesn't load either, the instance itself is down: \`curl -sI ${origin}/healthz\` says whether it's answering.`);
|
|
561
|
-
out.push(`3. Once it answers, re-run
|
|
569
|
+
out.push(`3. Once it answers, re-run ${surface.code(surface.action('start'))}.`);
|
|
562
570
|
out.push('');
|
|
563
|
-
out.push(
|
|
571
|
+
out.push(`${surface.strong(`Don't run ${surface.code('bongos dev')} for this.`)} It stands up a separate local instance with its own empty database — it cannot show you this board, and it would hide the outage behind an empty one.`);
|
|
564
572
|
if (depsMissing) {
|
|
565
573
|
out.push('');
|
|
566
|
-
out.push(`(The
|
|
574
|
+
out.push(`(The ${surface.code('bongos')} CLI isn't installed here. It's optional — the website above is the same board. ${surface.code('@bongos/core')} is a ${surface.strong('private')} package, so installing it needs an ${surface.code('NPM_TOKEN')}; a 404 without one is expected, not a missing version.)`);
|
|
567
575
|
}
|
|
568
576
|
return out.join('\n');
|
|
569
577
|
}
|
|
@@ -575,7 +583,7 @@ function instanceNotRunningCard({ origin, depsMissing } = {}) {
|
|
|
575
583
|
out.push(`${n++}. \`npm install\` — installs the toolchain first (the \`bongos\` CLI ships inside \`@bongos/core\`, so nothing else runs until this does).`);
|
|
576
584
|
}
|
|
577
585
|
out.push(`${n++}. \`bongos dev\` — one command: starts Postgres, builds the tables, runs the server, and signs you in.`);
|
|
578
|
-
out.push(`${n++}. Re-run
|
|
586
|
+
out.push(`${n++}. Re-run ${surface.code(surface.action('start'))}.`);
|
|
579
587
|
out.push('');
|
|
580
588
|
out.push(`Meant to work on a *different* instance that's already running? Sign in to it with \`bongos login <url>\` first.`);
|
|
581
589
|
return out.join('\n');
|
|
@@ -800,7 +808,7 @@ async function main() {
|
|
|
800
808
|
? b.preferred_disciplines.join(' + ')
|
|
801
809
|
: 'all disciplines';
|
|
802
810
|
const credits = Number(b.total_credits || 0).toLocaleString();
|
|
803
|
-
console.log(
|
|
811
|
+
console.log(`${surface.strong('builder-start')} · ${b.github_login} (${rankLabel}) · ${credits} credits · ${discLabel}`);
|
|
804
812
|
const sk = me.data.streak;
|
|
805
813
|
if (sk && Number(sk.current) > 0) {
|
|
806
814
|
console.log(`🔥 ${sk.current}-day streak${Number(sk.longest) > Number(sk.current) ? ` · longest ${sk.longest}` : ''}`);
|
|
@@ -847,16 +855,18 @@ async function main() {
|
|
|
847
855
|
// not block — feedback_parallel_sessions). Ids are hall links (ref rule).
|
|
848
856
|
if (me.ok) {
|
|
849
857
|
const myClaims = me.data.active_claims || (me.data.active_claim ? [me.data.active_claim] : []);
|
|
850
|
-
const heldActions = (id) => ` → ship:
|
|
858
|
+
const heldActions = (id) => ` → ship: ${surface.code(surface.action('ship', id))}`
|
|
859
|
+
+ ` · release: ${surface.code(surface.action('release', id))}`
|
|
860
|
+
+ ` · open: ${surface.taskRef(id, HALL_TASK)}`;
|
|
851
861
|
if (myClaims.length === 1) {
|
|
852
862
|
const c = myClaims[0];
|
|
853
|
-
console.log(`You're holding
|
|
863
|
+
console.log(`You're holding ${surface.taskRef(c.task_id, HALL_TASK)} — ${clip(c.title, 56)}`);
|
|
854
864
|
console.log(heldActions(c.task_id));
|
|
855
865
|
console.log('');
|
|
856
866
|
} else if (myClaims.length > 1) {
|
|
857
867
|
console.log(`You're holding ${myClaims.length} claims (parallel sessions OK):`);
|
|
858
868
|
for (const c of myClaims) {
|
|
859
|
-
console.log(`
|
|
869
|
+
console.log(` ${surface.taskRef(c.task_id, HALL_TASK)} — ${clip(c.title, 50)}`);
|
|
860
870
|
console.log(heldActions(c.task_id));
|
|
861
871
|
}
|
|
862
872
|
console.log('');
|
|
@@ -869,7 +879,7 @@ async function main() {
|
|
|
869
879
|
console.log(
|
|
870
880
|
`🚦 ${s.versionId} looks ready to close ` +
|
|
871
881
|
`(${s.criteriaSatisfied}/${s.criteriaTotal} criteria, ${s.percentComplete}% weighted; ` +
|
|
872
|
-
`${s.tasksShipped} shipped, ${s.tasksCarryOver} carry-over). Run
|
|
882
|
+
`${s.tasksShipped} shipped, ${s.tasksCarryOver} carry-over). Run ${surface.action('version-close', s.versionId)}.`
|
|
873
883
|
);
|
|
874
884
|
console.log('');
|
|
875
885
|
}
|
|
@@ -882,7 +892,7 @@ async function main() {
|
|
|
882
892
|
fit && `fit=${fit}`,
|
|
883
893
|
].filter(Boolean);
|
|
884
894
|
const filterNote = filterBits.length ? ` (${filterBits.join(', ')})` : '';
|
|
885
|
-
console.log(
|
|
895
|
+
console.log(`${surface.strong('Claim one to start')} — ${filtered.length} ready for you right now${matched}${filterNote}:`);
|
|
886
896
|
console.log('');
|
|
887
897
|
if (claimable.length) {
|
|
888
898
|
const cardRows = claimable.slice(0, CARD_ROWS);
|
|
@@ -890,10 +900,13 @@ async function main() {
|
|
|
890
900
|
const moreN = filtered.length - cardRows.length;
|
|
891
901
|
if (moreN > 0) {
|
|
892
902
|
console.log('');
|
|
893
|
-
console.log(
|
|
903
|
+
console.log(surface.em(`…and ${moreN} more — say "show all", or run with --limit 30.`));
|
|
894
904
|
}
|
|
895
905
|
} else {
|
|
896
|
-
console.log(
|
|
906
|
+
console.log(surface.em(
|
|
907
|
+
"Nothing claimable right now — everything is in flight, blocked, or shipped."
|
|
908
|
+
+ (surface.isTerminal() ? '' : " Run /blocker-review to see what's stuck.")
|
|
909
|
+
));
|
|
897
910
|
}
|
|
898
911
|
|
|
899
912
|
// Cross-discipline pointer (one line) — only when preference-routed.
|
|
@@ -908,7 +921,7 @@ async function main() {
|
|
|
908
921
|
|
|
909
922
|
// ---------- next ----------
|
|
910
923
|
console.log('');
|
|
911
|
-
console.log('
|
|
924
|
+
console.log(`→ ${surface.isTerminal() ? `Claim one with ${surface.code('bongos claim <id>')} in a checkout` : `${surface.strong('Tell me a number')} to claim it`}.`);
|
|
912
925
|
if (full) {
|
|
913
926
|
console.log('');
|
|
914
927
|
console.log('Commands:');
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// scripts/gds/surface.js — who is reading this output: an agent, or a person in a terminal?
|
|
2
|
+
// (task 1003680, follow-up to ADR 0258.)
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS
|
|
5
|
+
// The CLI's cards were written for exactly one reader: a Claude Code session. They carry
|
|
6
|
+
// `[otb-card]` directives addressed to the model, `/builder-ship N` slash commands, and markdown
|
|
7
|
+
// link syntax — all correct in a chat feed, all wrong in a terminal. That stayed invisible while
|
|
8
|
+
// the CLI lived inside the private core, because the only way to run it was from a session.
|
|
9
|
+
// `@cloudbongos/cli` (ADR 0258) put the same output in front of newcomers on a bare laptop, where
|
|
10
|
+
// the first thing they read is an instruction written to somebody else and a command that does
|
|
11
|
+
// not exist.
|
|
12
|
+
//
|
|
13
|
+
// HOW THE SURFACE IS DECIDED — and why not isTTY
|
|
14
|
+
// AGENT is the default, so nothing about the Claude Code path changes unless something opts out.
|
|
15
|
+
// The opt-out is `BONGOS_SURFACE=terminal`, set by the `bongos` DISPATCHER (bin/bongos.js, and the
|
|
16
|
+
// generated one in the public package) when it spawns a verb. The skills and the card-delivery
|
|
17
|
+
// hook run `node scripts/gds/<verb>.js` DIRECTLY, never through the dispatcher, so they keep the
|
|
18
|
+
// agent shape byte-for-byte.
|
|
19
|
+
//
|
|
20
|
+
// `process.stdout.isTTY` would get this exactly backwards: a Claude Code session's stdout is not a
|
|
21
|
+
// TTY either, so TTY-sniffing would classify every agent run as a terminal and break the card
|
|
22
|
+
// contract the skills depend on. The signal has to be an explicit statement of intent, not a guess.
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
const AGENT = 'agent';
|
|
27
|
+
const TERMINAL = 'terminal';
|
|
28
|
+
|
|
29
|
+
// Which reader is this output for? Pure: takes the env, returns a string.
|
|
30
|
+
function resolveSurface(env = process.env) {
|
|
31
|
+
const raw = String((env && env.BONGOS_SURFACE) || '').trim().toLowerCase();
|
|
32
|
+
return raw === TERMINAL ? TERMINAL : AGENT;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isTerminal(env = process.env) {
|
|
36
|
+
return resolveSurface(env) === TERMINAL;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// A task reference. Agents get a hall link because a bare `#NNN` autolinks to GitHub issues and
|
|
40
|
+
// 404s (the CLAUDE.md ref rule); a terminal reader gets the plain id and the URL beside it, since
|
|
41
|
+
// `[#123](https://…)` is just noise on a console.
|
|
42
|
+
function taskRef(id, url, env = process.env) {
|
|
43
|
+
return isTerminal(env) ? `#${id}` : `[#${id}](${url}${id})`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// An action the reader can actually run. In a session that is the slash command; in a terminal it
|
|
47
|
+
// is the `bongos` verb. `/builder-ship 123` does not exist outside Claude Code.
|
|
48
|
+
function action(verb, arg, env = process.env) {
|
|
49
|
+
const tail = arg == null ? '' : ` ${arg}`;
|
|
50
|
+
return isTerminal(env) ? `bongos ${verb}${tail}` : `/builder-${verb}${tail}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Markdown emphasis is decoration in a feed and literal asterisks on a console.
|
|
54
|
+
function strong(s, env = process.env) {
|
|
55
|
+
return isTerminal(env) ? String(s) : `**${s}**`;
|
|
56
|
+
}
|
|
57
|
+
function em(s, env = process.env) {
|
|
58
|
+
return isTerminal(env) ? String(s) : `_${s}_`;
|
|
59
|
+
}
|
|
60
|
+
function code(s, env = process.env) {
|
|
61
|
+
return isTerminal(env) ? String(s) : `\`${s}\``;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A table. Agents get GitHub-flavoured markdown (the feed renders it); a terminal gets columns
|
|
65
|
+
// padded to width with a rule under the header, which is what a console can actually align.
|
|
66
|
+
function table(headers, rows, env = process.env) {
|
|
67
|
+
if (!isTerminal(env)) {
|
|
68
|
+
return [
|
|
69
|
+
`| ${headers.join(' | ')} |`,
|
|
70
|
+
`|${headers.map(() => '---').join('|')}|`,
|
|
71
|
+
...rows.map((r) => `| ${r.join(' | ')} |`),
|
|
72
|
+
].join('\n');
|
|
73
|
+
}
|
|
74
|
+
const widths = headers.map((h, i) => Math.max(
|
|
75
|
+
String(h).length,
|
|
76
|
+
...rows.map((r) => String(r[i] == null ? '' : r[i]).length),
|
|
77
|
+
));
|
|
78
|
+
const line = (cells) => cells
|
|
79
|
+
.map((c, i) => String(c == null ? '' : c).padEnd(widths[i]))
|
|
80
|
+
.join(' ')
|
|
81
|
+
.replace(/\s+$/, '');
|
|
82
|
+
return [
|
|
83
|
+
line(headers),
|
|
84
|
+
widths.map((w) => '─'.repeat(w)).join(' '),
|
|
85
|
+
...rows.map(line),
|
|
86
|
+
].join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { AGENT, TERMINAL, resolveSurface, isTerminal, taskRef, action, strong, em, code, table };
|
package/src/module-api.js
CHANGED
|
@@ -55,7 +55,7 @@ const { buildInfo } = require('./build-info');
|
|
|
55
55
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
56
56
|
// the entry to that file. Look for a version's history there, not here.
|
|
57
57
|
// ---------------------------------------------------------------------------
|
|
58
|
-
const CORE_VERSION = '1.19.
|
|
58
|
+
const CORE_VERSION = '1.19.581'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
59
59
|
|
|
60
60
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
61
61
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// tests/cli_surface.mjs — the CLI addresses the reader it actually has (task 1003680).
|
|
2
|
+
//
|
|
3
|
+
// Two obligations, and the second is the one that could silently break the whole methodology:
|
|
4
|
+
//
|
|
5
|
+
// 1. A terminal reader never sees output written to a model — no `[otb-card]` directive, no
|
|
6
|
+
// `/builder-*` slash command (those exist only inside Claude Code), no raw markdown link.
|
|
7
|
+
//
|
|
8
|
+
// 2. The AGENT shape is unchanged. The skills and the card-delivery hook relay `start.js`'s
|
|
9
|
+
// stdout verbatim and branch on its first character; a stray difference there breaks the card
|
|
10
|
+
// contract for every session. So the agent cases below assert the exact old strings.
|
|
11
|
+
|
|
12
|
+
import test from 'node:test';
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
18
|
+
const REPO_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const surface = require(path.join(REPO_ROOT, 'scripts', 'gds', 'surface.js'));
|
|
21
|
+
|
|
22
|
+
const AGENT_ENV = {};
|
|
23
|
+
const TERM_ENV = { BONGOS_SURFACE: 'terminal' };
|
|
24
|
+
|
|
25
|
+
// ── 1. The signal itself ────────────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
test('agent is the default — nothing changes unless something opts out', () => {
|
|
28
|
+
assert.equal(surface.resolveSurface({}), 'agent');
|
|
29
|
+
assert.equal(surface.resolveSurface({ BONGOS_SURFACE: '' }), 'agent');
|
|
30
|
+
assert.equal(surface.resolveSurface({ BONGOS_SURFACE: 'nonsense' }), 'agent');
|
|
31
|
+
assert.equal(surface.isTerminal({}), false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('terminal is opt-in, case- and space-insensitive', () => {
|
|
35
|
+
assert.equal(surface.resolveSurface({ BONGOS_SURFACE: 'terminal' }), 'terminal');
|
|
36
|
+
assert.equal(surface.resolveSurface({ BONGOS_SURFACE: ' TERMINAL ' }), 'terminal');
|
|
37
|
+
assert.equal(surface.isTerminal(TERM_ENV), true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('the surface is never inferred from isTTY', () => {
|
|
41
|
+
// A Claude Code session's stdout is not a TTY either, so TTY-sniffing would classify every
|
|
42
|
+
// agent run as a terminal and break the card contract. The module must not consult it.
|
|
43
|
+
const src = require('node:fs').readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'surface.js'), 'utf8');
|
|
44
|
+
const code = src.split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
|
|
45
|
+
assert.ok(!/isTTY/.test(code), 'surface.js must not branch on isTTY');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// ── 2. What each reader gets ────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
test('actions name a command the reader can actually run', () => {
|
|
51
|
+
assert.equal(surface.action('ship', 123, AGENT_ENV), '/builder-ship 123');
|
|
52
|
+
assert.equal(surface.action('ship', 123, TERM_ENV), 'bongos ship 123');
|
|
53
|
+
assert.equal(surface.action('start', null, AGENT_ENV), '/builder-start');
|
|
54
|
+
assert.equal(surface.action('start', null, TERM_ENV), 'bongos start');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('task refs stay hall links for agents and go plain for terminals', () => {
|
|
58
|
+
const url = 'https://example.com/builders#/task/';
|
|
59
|
+
assert.equal(surface.taskRef(7, url, AGENT_ENV), '[#7](https://example.com/builders#/task/7)');
|
|
60
|
+
assert.equal(surface.taskRef(7, url, TERM_ENV), '#7');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('emphasis is markdown for agents and bare text for terminals', () => {
|
|
64
|
+
assert.equal(surface.strong('hi', AGENT_ENV), '**hi**');
|
|
65
|
+
assert.equal(surface.strong('hi', TERM_ENV), 'hi');
|
|
66
|
+
assert.equal(surface.em('hi', AGENT_ENV), '_hi_');
|
|
67
|
+
assert.equal(surface.em('hi', TERM_ENV), 'hi');
|
|
68
|
+
assert.equal(surface.code('x', AGENT_ENV), '`x`');
|
|
69
|
+
assert.equal(surface.code('x', TERM_ENV), 'x');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('the agent table is still GitHub-flavoured markdown, byte for byte', () => {
|
|
73
|
+
const out = surface.table(['#', 'What'], [['a', 'b']], AGENT_ENV);
|
|
74
|
+
assert.equal(out, '| # | What |\n|---|---|\n| a | b |');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('the terminal table aligns into columns with no pipe soup', () => {
|
|
78
|
+
const out = surface.table(['#', 'What'], [['1', 'short'], ['22', 'a longer one']], TERM_ENV);
|
|
79
|
+
const lines = out.split('\n');
|
|
80
|
+
assert.ok(!out.includes('|'), `no pipes in a terminal table:\n${out}`);
|
|
81
|
+
assert.ok(!/\|---/.test(out), 'no markdown separator row');
|
|
82
|
+
assert.match(lines[1], /^─+ {2}─+$/, 'a rule under the header');
|
|
83
|
+
// Column two must start at the same offset on every row.
|
|
84
|
+
const col2 = lines.filter((_, i) => i !== 1).map((l) => l.indexOf(l.trim().split(/\s{2,}/)[1]));
|
|
85
|
+
assert.equal(new Set(col2).size, 1, `column 2 misaligned:\n${out}`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('no trailing whitespace on a terminal row', () => {
|
|
89
|
+
const out = surface.table(['a', 'bbbb'], [['x', 'y']], TERM_ENV);
|
|
90
|
+
for (const line of out.split('\n')) assert.ok(!/\s$/.test(line), `trailing space: ${JSON.stringify(line)}`);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── 3. The call sites ───────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
test('start.js routes every reader-facing idiom through the surface', () => {
|
|
96
|
+
const src = require('node:fs').readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'start.js'), 'utf8');
|
|
97
|
+
const code = src.split('\n').filter((l) => !l.trim().startsWith('//')).join('\n');
|
|
98
|
+
|
|
99
|
+
// Two exemptions, both agent-only BY CONSTRUCTION:
|
|
100
|
+
// • CARD_DIRECTIVE describes the card TO the model, so naming the slash command is correct.
|
|
101
|
+
// • the `--widget` HTML renderer only ever runs for a session with show_widget; its markup
|
|
102
|
+
// is identifiable by hesc()/class="bs-" and never reaches a terminal.
|
|
103
|
+
const scanned = code
|
|
104
|
+
.replace(/const CARD_DIRECTIVE = \{[\s\S]*?\};/, '')
|
|
105
|
+
.split('\n')
|
|
106
|
+
.filter((l) => !l.includes('hesc(') && !l.includes('class="bs-'))
|
|
107
|
+
.join('\n');
|
|
108
|
+
const slashes = [...scanned.matchAll(/\/builder-[a-z-]+/g)].map((m) => m[0]);
|
|
109
|
+
assert.deepEqual(slashes, [], `hard-coded slash commands left in start.js: ${slashes.join(', ')}`);
|
|
110
|
+
|
|
111
|
+
// A hall link built by hand would bypass the switch.
|
|
112
|
+
const rawRefs = [...code.matchAll(/\[#\$\{[^}]+\}\]\(/g)].map((m) => m[0]);
|
|
113
|
+
assert.deepEqual(rawRefs, [], 'hand-built markdown task links left in start.js');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('the card directive is suppressed for a terminal reader', () => {
|
|
117
|
+
const src = require('node:fs').readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'start.js'), 'utf8');
|
|
118
|
+
assert.match(
|
|
119
|
+
src,
|
|
120
|
+
/function emitCardDirective\(form\) \{[\s\S]{0,400}?surface\.isTerminal\(\)\) return;/,
|
|
121
|
+
'emitCardDirective must return early on a terminal surface',
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('only the dispatchers declare the terminal surface', () => {
|
|
126
|
+
const fs = require('node:fs');
|
|
127
|
+
const dispatcher = fs.readFileSync(path.join(REPO_ROOT, 'bin', 'bongos.js'), 'utf8');
|
|
128
|
+
assert.match(dispatcher, /BONGOS_SURFACE: 'terminal'/, 'bin/bongos.js must mark verb spawns as terminal');
|
|
129
|
+
|
|
130
|
+
// `exec` is the agent's escape hatch into a core script and must NOT be marked.
|
|
131
|
+
const execLine = dispatcher.split('\n').find((l) => l.includes('args.slice(2)') && l.includes('spawnSync'));
|
|
132
|
+
assert.ok(execLine, 'could not find the exec spawn');
|
|
133
|
+
assert.ok(!execLine.includes('BONGOS_SURFACE'), '`bongos exec` must stay on the agent surface');
|
|
134
|
+
|
|
135
|
+
const builder = fs.readFileSync(path.join(REPO_ROOT, 'scripts', 'gds', 'build-cli-package.js'), 'utf8');
|
|
136
|
+
assert.match(builder, /BONGOS_SURFACE: 'terminal'/, 'the generated dispatcher must mark spawns as terminal');
|
|
137
|
+
assert.match(builder, /'scripts\/gds\/surface\.js'/, 'surface.js must ship in the package');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('the skills still invoke the scripts directly, which is what keeps agents on the agent surface', () => {
|
|
141
|
+
// If a skill ever routed through `bongos <verb>`, it would silently inherit the terminal
|
|
142
|
+
// surface and the card contract would break with no test failing anywhere else.
|
|
143
|
+
const fs = require('node:fs');
|
|
144
|
+
const skill = fs.readFileSync(path.join(REPO_ROOT, '.claude', 'skills', 'builder-start', 'SKILL.md'), 'utf8');
|
|
145
|
+
assert.match(skill, /node scripts\/gds\/start\.js/, 'builder-start must call the script directly');
|
|
146
|
+
});
|