@bongos/core 1.25.16 → 1.25.18
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 +200 -75
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +24 -0
- package/clients/bongos-client/index.cjs +24 -0
- package/clients/bongos-client/index.d.ts +36 -2
- package/clients/bongos-client/index.mjs +24 -0
- package/docs/adr/0406-every-project-gets-a-sandbox-that-follows-main-and-live-moves-only-when-a-person-promotes.md +8 -0
- package/docs/adr/0408-a-genesis-stage-is-three-doors-onto-one-record.md +1 -0
- package/docs/api/openapi.json +761 -112
- package/docs/api-reference.md +23 -3
- package/docs/copy-inventory.md +118 -108
- package/docs/copy-registry.json +315 -172
- package/docs/file-map.md +2 -1
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +3 -0
- package/docs/page-readings.json +62 -62
- package/modules/app-host/CLAUDE.md +15 -0
- package/modules/app-host/module.json +20 -0
- package/modules/app-host/public/settings-widget.js +143 -0
- package/modules/app-host/routes/app-host.js +88 -0
- package/modules/government/charter-advice.js +103 -0
- package/modules/government/decision-rules.js +1 -0
- package/modules/government/presets.js +42 -0
- package/modules/government/recommendations.js +102 -7
- package/modules/government/routes/government.js +1 -0
- package/modules/lifecycle/first-read-catalog.js +62 -0
- package/modules/lifecycle/first-read-db.js +190 -0
- package/modules/lifecycle/first-read.js +330 -0
- package/modules/lifecycle/kickoff-checklist.js +12 -3
- package/modules/lifecycle/migrations/lifecycle_022_repo_maps.sql +44 -0
- package/modules/lifecycle/migrations/lifecycle_023_repo_map_dismissed.sql +19 -0
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/routes/first-read.js +101 -0
- package/modules/onboarding/genesis-work.js +101 -4
- package/modules/onboarding/routes/genesis-work.js +2 -2
- package/modules/platform-identity/module.json +1 -1
- package/modules/platform-identity/routes/sso-app-status.js +65 -0
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/pollers/app-liveness.js +23 -1
- package/modules/provisioning/render-sandbox.js +113 -0
- package/modules/provisioning/render-standup.js +3 -1
- package/modules/provisioning/routes/provisioning.js +4 -1
- package/modules/provisioning/routes/render-standup.js +49 -1
- package/modules/provisioning/seams.js +32 -1
- package/modules/provisioning/starter-bundles.js +1 -0
- package/modules/public-landing/public/assets/guide-core.js +475 -0
- package/modules/public-landing/public/assets/guide-hall.css +121 -0
- package/modules/public-landing/public/assets/guide-hall.js +164 -0
- package/modules/public-landing/public/assets/guide-scene.css +9 -0
- package/modules/public-landing/public/assets/guide-scene.js +58 -381
- package/modules/public-landing/public/index.html +46 -1
- package/modules/public-landing/public/projects.html +112 -6
- package/modules/ui-design/kit/serve.js +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/scripts/bongos/first-read.js +150 -0
- package/scripts/bongos/genesis.js +21 -1
- package/scripts/bongos/government-walk.js +4 -4
- package/scripts/bongos/provision-render-sandbox.js +171 -0
- package/scripts/bongos/provision-render.js +37 -2
- package/scripts/bongos/provision-teardown.js +1 -1
- package/scripts/bongos/publish-manifest.js +1 -0
- package/scripts/bongos/render-api.js +33 -3
- package/scripts/bongos/render-payload.js +39 -1
- package/scripts/bongos/seam-wiring-guard.js +3 -2
- package/scripts/gds/first-read.js +7 -0
- package/scripts/gds/provision-render-sandbox.js +6 -0
- package/scripts/hall-preview/server.js +2 -0
- package/src/bongos/auth-admission.js +64 -0
- package/src/bongos/auth.js +2 -1
- package/src/bongos/module-scope-map.js +4 -0
- package/src/bongos/routes/core-update.js +3 -16
- package/src/bongos/routes/modules.js +11 -0
- package/src/bongos/serve-internal.js +20 -6
- package/src/module-api.js +6 -1
- package/src/module-loader/loader.js +2 -2
- package/tests/app_host.mjs +413 -0
- package/tests/first_read.mjs +479 -0
- package/tests/genesis_work.mjs +74 -3
- package/tests/government_act_alone_buildings.mjs +2 -0
- package/tests/government_charter_advice.mjs +150 -0
- package/tests/landing_guide_hall.mjs +364 -0
- package/tests/landing_guide_scene.mjs +8 -5
- package/tests/module_api.mjs +1 -0
- package/tests/module_loader.mjs +1 -1
- package/tests/npm_release_where.mjs +13 -3
- package/tests/provisioning_app_status.mjs +3 -2
- package/tests/provisioning_render_route.mjs +1 -1
- package/tests/provisioning_teardown_intent.mjs +50 -0
- package/tests/render_api.mjs +5 -2
- package/tests/render_sandbox.mjs +447 -0
|
@@ -2250,7 +2250,13 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2250
2250
|
wires. null when there is nothing to show (no platform chosen). */
|
|
2251
2251
|
function appStatusView(data) {
|
|
2252
2252
|
var app = data && data.app;
|
|
2253
|
-
if (!app)
|
|
2253
|
+
if (!app) {
|
|
2254
|
+
/* no app on Render yet: the sandbox says plainly why it has none (task 1005289) */
|
|
2255
|
+
if (!data || !data.sandbox || data.sandbox.state !== 'none') return null;
|
|
2256
|
+
var nv = { root: appEl('div', 'appStat') };
|
|
2257
|
+
sandboxView(data.sandbox, nv);
|
|
2258
|
+
return nv;
|
|
2259
|
+
}
|
|
2254
2260
|
var v = { root: appEl('div', 'appStat') };
|
|
2255
2261
|
if (app.state === 'disconnected') {
|
|
2256
2262
|
v.root.appendChild(appPara(['This project is no longer connected to Render. Its services there are still yours; remove them in Render if you want to stop paying.']));
|
|
@@ -2304,6 +2310,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2304
2310
|
}
|
|
2305
2311
|
}
|
|
2306
2312
|
if (app.links) v.root.appendChild(appPara([appLink(app.links.service, 'Open it on Render')]));
|
|
2313
|
+
sandboxView(data.sandbox, v);
|
|
2307
2314
|
var vars = (data.env_vars || []).filter(function (x) { return !x.set_for_you; });
|
|
2308
2315
|
if (vars.length) {
|
|
2309
2316
|
v.root.appendChild(appEl('h3', null, 'Still to set on Render'));
|
|
@@ -2320,7 +2327,59 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2320
2327
|
}
|
|
2321
2328
|
return v;
|
|
2322
2329
|
}
|
|
2323
|
-
|
|
2330
|
+
/* ── the project's sandbox (task 1005289, ADR 0406) ──
|
|
2331
|
+
A free copy of the app on the owner's Render account that takes every finished change
|
|
2332
|
+
first; the live app moves only when a person promotes. Same server-shaped answer as
|
|
2333
|
+
the app's (`data.sandbox`), drawn beside it, node by node. "Add a sandbox" asks for the
|
|
2334
|
+
key once, like every Render action, and never keeps it. */
|
|
2335
|
+
var mSandboxOn = {}; /* project id → has a sandbox to remove, so the teardown asks for the key only then */
|
|
2336
|
+
/* the stage word is the app's own (APP_STAGES), so one page never names one stage two ways */
|
|
2337
|
+
function stageWord(stage) {
|
|
2338
|
+
var s = stage === 'quiet' ? 'building' : stage;
|
|
2339
|
+
for (var i = 0; i < APP_STAGES.length; i++) if (APP_STAGES[i][0] === s) return APP_STAGES[i][1];
|
|
2340
|
+
return APP_STAGES[0][1];
|
|
2341
|
+
}
|
|
2342
|
+
function sandboxView(sb, v) {
|
|
2343
|
+
if (!sb) return;
|
|
2344
|
+
var box = appEl('div', 'appSandbox');
|
|
2345
|
+
box.appendChild(appEl('h3', null, 'Sandbox'));
|
|
2346
|
+
box.appendChild(appPara(['A free test copy of your app. Every finished change reaches it first; your live app only changes when you promote it.'], 'finePrint'));
|
|
2347
|
+
if (sb.state === 'none' || sb.state === 'requested' || sb.state === 'left') {
|
|
2348
|
+
box.appendChild(appPara([String(sb.error || sb.message || '')], sb.state === 'left' ? 'err' : null));
|
|
2349
|
+
} else if (sb.error) {
|
|
2350
|
+
box.appendChild(appPara([String(sb.error)], 'err'));
|
|
2351
|
+
} else {
|
|
2352
|
+
var line = [stageWord(sb.stage)];
|
|
2353
|
+
if (sb.url) line.push(' at ', appLink(sb.url, String(sb.url).replace(/^https:\/\//, '')));
|
|
2354
|
+
line.push('. It sleeps when nobody visits and takes about a minute to wake.');
|
|
2355
|
+
box.appendChild(appPara(line));
|
|
2356
|
+
if (sb.links) box.appendChild(appPara([appLink(sb.links.service, 'Open the sandbox on Render')]));
|
|
2357
|
+
}
|
|
2358
|
+
if (sb.database && sb.database.note) box.appendChild(appPara([String(sb.database.note)], 'finePrint'));
|
|
2359
|
+
if (sb.can_add) {
|
|
2360
|
+
v.sbBtn = appEl('button', 'btn ghost small appSbBtn', sb.state === 'none' ? 'Add a sandbox' : 'Try the sandbox again');
|
|
2361
|
+
v.sbBtn.type = 'button';
|
|
2362
|
+
box.appendChild(v.sbBtn);
|
|
2363
|
+
v.sbForm = appEl('div', 'appRetry appSbForm');
|
|
2364
|
+
v.sbForm.hidden = true;
|
|
2365
|
+
var lab = appEl('label', 'wizField');
|
|
2366
|
+
lab.appendChild(appEl('span', null, 'Render API key'));
|
|
2367
|
+
v.sbKey = appEl('input', 'appRetryKey');
|
|
2368
|
+
v.sbKey.type = 'password'; v.sbKey.autocomplete = 'off'; v.sbKey.spellcheck = false; v.sbKey.maxLength = 200; v.sbKey.placeholder = 'rnd_…';
|
|
2369
|
+
lab.appendChild(v.sbKey);
|
|
2370
|
+
v.sbForm.appendChild(lab);
|
|
2371
|
+
v.sbSend = appEl('button', 'btn small', 'Add the sandbox');
|
|
2372
|
+
v.sbSend.type = 'button';
|
|
2373
|
+
v.sbForm.appendChild(v.sbSend);
|
|
2374
|
+
v.sbHint = appEl('div', 'hint appRetryHint', 'Free on Render. Your key is used once and thrown away.');
|
|
2375
|
+
v.sbHint.setAttribute('role', 'status');
|
|
2376
|
+
v.sbForm.appendChild(v.sbHint);
|
|
2377
|
+
box.appendChild(v.sbForm);
|
|
2378
|
+
}
|
|
2379
|
+
v.root.appendChild(box);
|
|
2380
|
+
}
|
|
2381
|
+
function sandboxSettled(sb) { return !sb || ['none', 'left', 'removed'].indexOf(sb.state) >= 0 || !!sb.error || sb.stage === 'live' || sb.stage === 'quiet'; }
|
|
2382
|
+
function appTerminal(data) { var a = data && data.app; return (!a || a.state === 'disconnected' || !!a.error || a.stage === 'live') && sandboxSettled(data && data.sandbox); }
|
|
2324
2383
|
function fetchAppStatus(id) {
|
|
2325
2384
|
return fetch(API + '/provisioning/instances/' + encodeURIComponent(id) + '/render', { credentials: 'same-origin' })
|
|
2326
2385
|
.then(function (res) { return res.ok ? res.json() : null; })
|
|
@@ -2337,6 +2396,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2337
2396
|
function paint(data) {
|
|
2338
2397
|
if (stopped || !alive()) return;
|
|
2339
2398
|
last = data;
|
|
2399
|
+
var sbNow = data && data.sandbox;
|
|
2400
|
+
mSandboxOn[String(id)] = !!(sbNow && (sbNow.links || sbNow.url));
|
|
2340
2401
|
/* never repaint over a Retry the owner has opened — it holds a key they are typing */
|
|
2341
2402
|
/* …whether this watch opened it or an earlier one on the same box did (the project
|
|
2342
2403
|
page starts a fresh watch on every reload of the project) */
|
|
@@ -2352,6 +2413,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2352
2413
|
}
|
|
2353
2414
|
function tick() { fetchAppStatus(id).then(paint); }
|
|
2354
2415
|
function wire(v) {
|
|
2416
|
+
if (v.sbBtn) wireSandbox(v);
|
|
2355
2417
|
if (!v.retryBtn) return;
|
|
2356
2418
|
v.retryBtn.addEventListener('click', function () {
|
|
2357
2419
|
v.retryForm.hidden = false; v.retryBtn.hidden = true; v.keyEl.focus();
|
|
@@ -2381,6 +2443,30 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2381
2443
|
});
|
|
2382
2444
|
});
|
|
2383
2445
|
}
|
|
2446
|
+
function wireSandbox(v) {
|
|
2447
|
+
v.sbBtn.addEventListener('click', function () { v.sbForm.hidden = false; v.sbBtn.hidden = true; v.sbKey.focus(); });
|
|
2448
|
+
v.sbSend.addEventListener('click', function () {
|
|
2449
|
+
var key = String(v.sbKey.value || '').trim();
|
|
2450
|
+
if (!/^rnd_[A-Za-z0-9]{16,128}$/.test(key)) {
|
|
2451
|
+
v.sbHint.textContent = 'That doesn’t look like a Render API key. It starts with “rnd_”.'; v.sbHint.className = 'hint appRetryHint err'; return;
|
|
2452
|
+
}
|
|
2453
|
+
var body = JSON.stringify({ key: key });
|
|
2454
|
+
v.sbKey.value = ''; key = '';
|
|
2455
|
+
v.sbSend.disabled = true; v.sbHint.textContent = 'Handing it to Render…'; v.sbHint.className = 'hint appRetryHint';
|
|
2456
|
+
fetch(API + '/provisioning/instances/' + encodeURIComponent(id) + '/render/sandbox', {
|
|
2457
|
+
method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: body
|
|
2458
|
+
}).then(function (res) {
|
|
2459
|
+
body = '';
|
|
2460
|
+
return res.json().catch(function () { return {}; }).then(function (d) {
|
|
2461
|
+
if (!res.ok) throw new Error((d.error && d.error.message) || 'The sandbox could not be queued.');
|
|
2462
|
+
v.sbForm.hidden = true; view = null; clearTimeout(t); ticks = 0; tick();
|
|
2463
|
+
});
|
|
2464
|
+
}).catch(function (err) {
|
|
2465
|
+
body = '';
|
|
2466
|
+
v.sbSend.disabled = false; v.sbHint.textContent = err.message; v.sbHint.className = 'hint appRetryHint err';
|
|
2467
|
+
});
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2384
2470
|
tick();
|
|
2385
2471
|
return function stop() { stopped = true; clearTimeout(t); };
|
|
2386
2472
|
}
|
|
@@ -2680,7 +2766,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
2680
2766
|
try { id = new URLSearchParams(location.search).get('id'); } catch (e) {}
|
|
2681
2767
|
if (id) { manageId = String(id); return 'manage'; }
|
|
2682
2768
|
try { handedProject = new URLSearchParams(location.search).get('project') || null; } catch (e) {}
|
|
2683
|
-
if (handedProject && /^#m(Delete|Disconnect)$/.test(location.hash || '')) handedSection = location.hash.slice(1);
|
|
2769
|
+
if (handedProject && /^#m(Delete|Disconnect|AppCard)$/.test(location.hash || '')) handedSection = location.hash.slice(1);
|
|
2684
2770
|
return 'mine';
|
|
2685
2771
|
}
|
|
2686
2772
|
return VIEWS[v] ? v : 'map';
|
|
@@ -9607,7 +9693,16 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
9607
9693
|
if (handedSection) {
|
|
9608
9694
|
var target = $(handedSection);
|
|
9609
9695
|
handedSection = null;
|
|
9610
|
-
|
|
9696
|
+
/* the app card (#mAppCard, the hall's Connect Render link — task 1004571) is
|
|
9697
|
+
drawn by its own read below, so it is still hidden here: land on it once shown */
|
|
9698
|
+
if (target && target.hidden && typeof MutationObserver === 'function') {
|
|
9699
|
+
var seen = new MutationObserver(function () {
|
|
9700
|
+
if (target.hidden) return;
|
|
9701
|
+
seen.disconnect();
|
|
9702
|
+
if (target.scrollIntoView) target.scrollIntoView({ block: 'start' });
|
|
9703
|
+
});
|
|
9704
|
+
seen.observe(target, { attributes: true, attributeFilter: ['hidden'] });
|
|
9705
|
+
} else if (target && target.scrollIntoView) target.scrollIntoView({ block: 'start' });
|
|
9611
9706
|
}
|
|
9612
9707
|
/* the SECOND, GitHub-backed read — fired only here (entry + tab
|
|
9613
9708
|
return), never from renderManage, which the fetch-free tick calls */
|
|
@@ -11095,7 +11190,12 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
11095
11190
|
'<input type="text" id="mDelName" autocomplete="off" spellcheck="false" maxlength="40" ' +
|
|
11096
11191
|
'aria-label="Type the project name to confirm" placeholder="' + esc(inst.slug) + '">' +
|
|
11097
11192
|
'<button type="button" class="btn small danger" data-del-yes disabled>Yes, tear it down</button>' +
|
|
11098
|
-
'<button type="button" class="linklike" data-del-no>Keep it</button></div>'
|
|
11193
|
+
'<button type="button" class="linklike" data-del-no>Keep it</button></div>' +
|
|
11194
|
+
(mSandboxOn[String(inst.id)]
|
|
11195
|
+
? '<label class="wizField"><span>Render API key, to remove its sandbox too (optional)</span>' +
|
|
11196
|
+
'<input type="password" id="mDelRenderKey" autocomplete="off" spellcheck="false" maxlength="200" placeholder="rnd_…"></label>' +
|
|
11197
|
+
'<p class="finePrint">Only the free sandbox is removed. Your live app on Render is left as it is. The key is used once and thrown away; without it the sandbox stays on your Render account for you to remove.</p>'
|
|
11198
|
+
: ''));
|
|
11099
11199
|
var field = $('mDelName');
|
|
11100
11200
|
if (field) field.focus();
|
|
11101
11201
|
}
|
|
@@ -11126,11 +11226,17 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
11126
11226
|
/* the typed name is this page's own gate, never the server's — so a click
|
|
11127
11227
|
on a button that only LOOKS enabled cannot act */
|
|
11128
11228
|
if (!inst || !delTypedMatches()) return;
|
|
11229
|
+
var rkField = $('mDelRenderKey');
|
|
11230
|
+
var rk = rkField ? String(rkField.value || '').trim() : '';
|
|
11231
|
+
var headers = { 'Content-Type': 'application/json' };
|
|
11232
|
+
if (/^rnd_[A-Za-z0-9]{16,128}$/.test(rk)) headers['x-render-key'] = rk;
|
|
11233
|
+
rk = '';
|
|
11129
11234
|
mDelAsking = null;
|
|
11130
11235
|
delMsg('<p class="finePrint">Asking…</p>');
|
|
11131
11236
|
fetch(API + '/provisioning/instances/' + encodeURIComponent(inst.id) + '/teardown', {
|
|
11132
|
-
method: 'POST', credentials: 'same-origin', headers:
|
|
11237
|
+
method: 'POST', credentials: 'same-origin', headers: headers, body: '{}'
|
|
11133
11238
|
}).then(function (res) {
|
|
11239
|
+
delete headers['x-render-key'];
|
|
11134
11240
|
return jsonOrEmpty(res).then(function (data) {
|
|
11135
11241
|
if (res.ok) {
|
|
11136
11242
|
var note = esc(data.message || 'Teardown requested.');
|
|
@@ -132,7 +132,7 @@ const UNAUTH = { error: { code: 'unauthenticated', message: 'unauthenticated' }
|
|
|
132
132
|
// only its length matters here; `optional` and `bundles` are byte-for-byte.
|
|
133
133
|
const STARTER_BUNDLES_FIXTURE = {
|
|
134
134
|
core: [
|
|
135
|
-
'autonomy', 'builder-settings', 'copy-desk', 'economy', 'government', 'grading',
|
|
135
|
+
'app-host', 'autonomy', 'builder-settings', 'copy-desk', 'economy', 'government', 'grading',
|
|
136
136
|
'hall-ui', 'ideas', 'lifecycle', 'memory', 'onboarding', 'page-notes', 'public-landing',
|
|
137
137
|
'security', 'sessions', 'specialities', 'status-ui', 'ui-design',
|
|
138
138
|
],
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.18",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.25.
|
|
9
|
+
"version": "1.25.18",
|
|
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.25.
|
|
3
|
+
"version": "1.25.18",
|
|
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/release-notes.json
CHANGED
|
@@ -11824,5 +11824,33 @@
|
|
|
11824
11824
|
"id": "1005116",
|
|
11825
11825
|
"text": "Founders now write their own strategy. Each setup stage can be done three ways: answer guided questions, write it freely, or get help from your own AI — and you can switch at any time without losing anything. Your answers are"
|
|
11826
11826
|
}
|
|
11827
|
+
],
|
|
11828
|
+
"1.25.17": [
|
|
11829
|
+
{
|
|
11830
|
+
"id": "1005289",
|
|
11831
|
+
"text": "Every project on Render now gets a free test copy of its app that takes each finished change first, shown beside the live site, added in one click for existing projects and removed when the project is taken down."
|
|
11832
|
+
},
|
|
11833
|
+
{
|
|
11834
|
+
"id": "1005280",
|
|
11835
|
+
"text": "Owners bringing in an existing project get a prompt for their own AI to read the code and map goals, tasks and what's broken; they then choose what Bongos files as drafts."
|
|
11836
|
+
}
|
|
11837
|
+
],
|
|
11838
|
+
"1.25.18": [
|
|
11839
|
+
{
|
|
11840
|
+
"id": "1005281",
|
|
11841
|
+
"text": "When an owner's own AI reads their existing project, it can now suggest modules from the project's own list that would do a weak job better; the owner accepts each one as a to-do or dismisses it."
|
|
11842
|
+
},
|
|
11843
|
+
{
|
|
11844
|
+
"id": "1005276",
|
|
11845
|
+
"text": "People who start a project from the front page can now talk with the guide in a plain, simple version instead of the game, and switch between the two mid-chat."
|
|
11846
|
+
},
|
|
11847
|
+
{
|
|
11848
|
+
"id": "1005119",
|
|
11849
|
+
"text": "When founding a project, the charter step now asks how decisions should go and what the founder believes, then recommends a government, a level of strictness, and a first draft of the rules, each with the reason in the founder"
|
|
11850
|
+
},
|
|
11851
|
+
{
|
|
11852
|
+
"id": "1004571",
|
|
11853
|
+
"text": "Inside a project's hall, the options menu now shows where the project's app runs (no platform yet, or Render and its progress and address), and gives the owner a one-click way to connect Render."
|
|
11854
|
+
}
|
|
11827
11855
|
]
|
|
11828
11856
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/bongos/first-read.js — `bongos first-read`: the command the owner's own AI
|
|
3
|
+
// session runs during the first read-through of a project that already exists (task
|
|
4
|
+
// 1005280, goal 1000106).
|
|
5
|
+
//
|
|
6
|
+
// The session reads the repository on the owner's computer and subscription, writes a
|
|
7
|
+
// map, and uses this to hand it to Bongos. Nothing here sends repository content: only
|
|
8
|
+
// the map file the session wrote, which `check` validates locally first with the same
|
|
9
|
+
// rules the server applies (modules/lifecycle/first-read.js).
|
|
10
|
+
//
|
|
11
|
+
// bongos first-read prompt print the prompt, with this project's module list
|
|
12
|
+
// bongos first-read catalog the modules a map may recommend (task 1005281)
|
|
13
|
+
// bongos first-read check <map.json> validate (asks for the module list only when
|
|
14
|
+
// the map recommends modules)
|
|
15
|
+
// bongos first-read submit <map.json> send it for review — files nothing
|
|
16
|
+
// bongos first-read show [<map id>] the newest map (or one), with what is filed
|
|
17
|
+
// bongos first-read accept <map id> --all | --items <ref>,<ref> ONLY with the owner's yes
|
|
18
|
+
// bongos first-read dismiss <map id> --items <ref>,<ref> the owner's no to some items
|
|
19
|
+
// bongos first-read reject <map id> the owner's no to all of it — files nothing
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const fs = require('node:fs');
|
|
24
|
+
const fr = require('../../modules/lifecycle/first-read');
|
|
25
|
+
|
|
26
|
+
function readMap(file) {
|
|
27
|
+
if (!file) { console.error(`usage: bongos first-read check|submit <${fr.MAP_FILE}>`); process.exit(2); }
|
|
28
|
+
let text;
|
|
29
|
+
try { text = fs.readFileSync(file, 'utf8').replace(/^/, ''); } catch (e) { console.error(`Cannot read ${file}: ${e.message}`); process.exit(2); }
|
|
30
|
+
try { return JSON.parse(text); } catch (e) { console.error(`${file} is not valid JSON: ${e.message}`); process.exit(1); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printErrors(errors) {
|
|
34
|
+
console.error('The map needs fixing:');
|
|
35
|
+
for (const e of errors) console.error(` - ${e}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function printItems(items) {
|
|
39
|
+
for (const i of items) {
|
|
40
|
+
const mark = !i.filed ? '' : i.filed.kind === 'dismissed' ? ' [dismissed]' : ' [already in Bongos]';
|
|
41
|
+
const indent = i.type === 'goal' || i.type === 'module' ? '' : ' ';
|
|
42
|
+
console.log(`${indent}${i.type.padEnd(6)} ${i.title}${mark}\n${indent} ref: ${i.ref}`);
|
|
43
|
+
if (i.type === 'module' && i.found) console.log(`${indent} found: ${i.found}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A refusal prints and returns null; the caller then ends with cliExit — never a hard
|
|
48
|
+
// process.exit after a request (the Windows abort in scripts/bongos/CLAUDE.md).
|
|
49
|
+
async function call(method, path, body) {
|
|
50
|
+
const { apiCall } = require('./cli-lib');
|
|
51
|
+
const res = await apiCall(method, path, body);
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const d = res.data || {};
|
|
54
|
+
console.error(`Bongos refused (${res.status}): ${d.error || d.code || 'error'}`);
|
|
55
|
+
if (Array.isArray(d.errors)) printErrors(d.errors);
|
|
56
|
+
if (d.unknown) console.error(` unknown refs: ${d.unknown.join(', ')}`);
|
|
57
|
+
if (d.hint || d.message) console.error(` ${d.hint || d.message}`);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
return res.data;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fail = () => require('./cli-lib').cliExit(1);
|
|
64
|
+
|
|
65
|
+
// The live module list, or null when it cannot be read (no session, or the server says so).
|
|
66
|
+
async function liveCatalog() {
|
|
67
|
+
const out = await call('GET', '/api/bongos/first-read/catalog');
|
|
68
|
+
return out && out.available ? out.modules : null;
|
|
69
|
+
}
|
|
70
|
+
const flag = (name) => { const i = process.argv.indexOf(name); return i === -1 ? null : (process.argv[i + 1] || ''); };
|
|
71
|
+
|
|
72
|
+
(async () => {
|
|
73
|
+
const [verb, arg] = process.argv.slice(2);
|
|
74
|
+
switch (verb) {
|
|
75
|
+
case 'prompt': {
|
|
76
|
+
// The server's prompt carries the live module list; offline, the fixed text says
|
|
77
|
+
// how to fetch it later.
|
|
78
|
+
const out = await call('GET', '/api/bongos/first-read/prompt');
|
|
79
|
+
console.log(out ? out.prompt : fr.firstReadPrompt());
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
case 'catalog': {
|
|
83
|
+
const out = await call('GET', '/api/bongos/first-read/catalog');
|
|
84
|
+
if (!out) return fail();
|
|
85
|
+
if (!out.available) { console.log('The module list cannot be read on this project. Leave "recommended_modules" out of the map.'); return; }
|
|
86
|
+
if (!out.modules.length) { console.log('This project has no modules left to switch on. Leave "recommended_modules" out of the map.'); return; }
|
|
87
|
+
console.log('Modules this project can switch on (recommend only these, by key):');
|
|
88
|
+
for (const m of out.modules) console.log(` ${m.key}: ${m.title}. ${m.summary}`.trimEnd());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
case 'check': {
|
|
92
|
+
const raw = readMap(arg);
|
|
93
|
+
const recommends = Array.isArray(raw.recommended_modules) && raw.recommended_modules.length > 0;
|
|
94
|
+
const v = fr.validateRepoMap(raw, recommends ? { catalog: await liveCatalog() } : {});
|
|
95
|
+
if (!v.ok) { printErrors(v.errors); process.exit(1); }
|
|
96
|
+
console.log(`The map is in the right shape: ${v.items.length} items.`);
|
|
97
|
+
printItems(v.items);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
case 'submit': {
|
|
101
|
+
const raw = readMap(arg);
|
|
102
|
+
const v = fr.validateRepoMap(raw);
|
|
103
|
+
if (!v.ok) { printErrors(v.errors); process.exit(1); }
|
|
104
|
+
const out = await call('POST', '/api/bongos/first-read/maps', { map: raw });
|
|
105
|
+
if (!out) return fail();
|
|
106
|
+
console.log(`Sent as map ${out.map_id}. Nothing is filed yet.`);
|
|
107
|
+
printItems(out.items);
|
|
108
|
+
console.log(`\nWalk the owner through it. Only with their yes:\n bongos first-read accept ${out.map_id} --all\n bongos first-read accept ${out.map_id} --items <ref>,<ref>\nwhat they turn down:\n bongos first-read dismiss ${out.map_id} --items <ref>,<ref>\nor, if they want none of it:\n bongos first-read reject ${out.map_id}`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
case 'show': {
|
|
112
|
+
const out = await call('GET', `/api/bongos/first-read/maps/${arg || 'latest'}`);
|
|
113
|
+
if (!out) return fail();
|
|
114
|
+
console.log(`Map ${out.map_id} (${out.status})\n${out.summary}\n`);
|
|
115
|
+
printItems(out.items);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
case 'accept': {
|
|
119
|
+
if (!arg) { console.error('usage: bongos first-read accept <map id> --all | --items <ref>,<ref>'); process.exit(2); }
|
|
120
|
+
const list = flag('--items');
|
|
121
|
+
const items = process.argv.includes('--all') ? 'all' : (list ? list.split(',').map((s) => s.trim()).filter(Boolean) : null);
|
|
122
|
+
if (!items || (Array.isArray(items) && !items.length)) { console.error('Say what the owner accepted: --all, or --items <ref>,<ref>.'); process.exit(2); }
|
|
123
|
+
const out = await call('POST', `/api/bongos/first-read/maps/${arg}/accept`, items === 'all' ? { all: true } : { items });
|
|
124
|
+
if (!out) return fail();
|
|
125
|
+
console.log(`Filed as drafts: ${out.filed.goals.length} goals, ${out.filed.tasks.length} tasks (tasks wait in backlog until promoted).`);
|
|
126
|
+
for (const g of out.filed.goals) console.log(` goal ${g.id} ${g.title}`);
|
|
127
|
+
for (const t of out.filed.tasks) console.log(` task ${t.id} ${t.title}`);
|
|
128
|
+
for (const s of out.skipped) console.log(` skipped ${s.ref} (${s.reason.replace('_', ' ')})`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
case 'dismiss': {
|
|
132
|
+
const list = (flag('--items') || '').split(',').map((x) => x.trim()).filter(Boolean);
|
|
133
|
+
if (!arg || !list.length) { console.error('usage: bongos first-read dismiss <map id> --items <ref>,<ref>'); process.exit(2); }
|
|
134
|
+
const out = await call('POST', `/api/bongos/first-read/maps/${arg}/dismiss`, { items: list });
|
|
135
|
+
if (!out) return fail();
|
|
136
|
+
console.log(`Dismissed: ${out.dismissed.length ? out.dismissed.join(', ') : 'nothing'}. A later read-through will not raise them again.`);
|
|
137
|
+
for (const x of out.skipped) console.log(` skipped ${x.ref} (${x.reason.replace('_', ' ')})`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
case 'reject': {
|
|
141
|
+
if (!arg) { console.error('usage: bongos first-read reject <map id>'); process.exit(2); }
|
|
142
|
+
if (!(await call('POST', `/api/bongos/first-read/maps/${arg}/reject`, {}))) return fail();
|
|
143
|
+
console.log(`Map ${arg} rejected. Nothing was filed.`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
console.log('usage: bongos first-read prompt | catalog | check <file> | submit <file> | show [id] | accept <id> --all|--items a,b | dismiss <id> --items a,b | reject <id>');
|
|
148
|
+
process.exit(verb ? 2 : 0);
|
|
149
|
+
}
|
|
150
|
+
})().catch((e) => { console.error(e.message || e); process.exitCode = 1; });
|
|
@@ -41,16 +41,36 @@ function formatStage(s) {
|
|
|
41
41
|
for (const q of s.questions) {
|
|
42
42
|
if (q.group && q.group !== group) { group = q.group; out.push(` — ${group} —`); }
|
|
43
43
|
out.push(` ${q.id}: ${q.ask}`);
|
|
44
|
+
if (q.choices) out.push(` choose: ${q.choices.map((c) => `${c.key} (${c.label})`).join(' · ')}`);
|
|
44
45
|
if (!q.answer) { out.push(' (empty)'); continue; }
|
|
45
46
|
const a = q.answer;
|
|
46
|
-
const
|
|
47
|
+
const said = a.choice_label || a.text;
|
|
48
|
+
const words = said ? said.replace(/\n/g, '\n ') : '';
|
|
47
49
|
out.push(` ${STATE_LABEL[a.state] || ''}${words}${ORIGIN_LABEL[a.origin] || ''}`.trimEnd());
|
|
48
50
|
}
|
|
49
51
|
if (s.page) out.push(' Page:', ` ${s.page.replace(/\n/g, '\n ')}`);
|
|
50
52
|
if (s.link) out.push(` Link: ${s.link}`);
|
|
53
|
+
if (s.advice) out.push('', ...formatAdvice(s.advice));
|
|
51
54
|
return out.join('\n');
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
// Pure: the charter stage's recommendation from the founder's answers (task 1005119).
|
|
58
|
+
// A recommendation only: choosing is still the founder's.
|
|
59
|
+
function formatAdvice(a) {
|
|
60
|
+
const out = [` Recommended government: ${a.charter.name}`, ` Why: ${a.charter.why}`];
|
|
61
|
+
if (a.charter.council_rank) out.push(` It is governed by a council rank you create: ${a.charter.council_rank}.`);
|
|
62
|
+
out.push(' It is a recommendation. Any form, or your own settings, is still yours to choose.');
|
|
63
|
+
if (a.preset) {
|
|
64
|
+
out.push(` Recommended strictness: ${a.preset.name}. ${a.preset.line}`, ` Why: ${a.preset.why}`, ` ${a.preset.is}`);
|
|
65
|
+
for (const l of a.preset.laws) out.push(` - ${l.text}: ${l.effect_on_ai}`);
|
|
66
|
+
} else {
|
|
67
|
+
out.push(' Strictness: no preset recommended. Written principles with no laws is a fine way to start.');
|
|
68
|
+
}
|
|
69
|
+
out.push(' Draft bylaws and code of conduct, from your answers (edit it, then save it with',
|
|
70
|
+
' npx bongos genesis page charter --file <path>):', '', ...String(a.draft_md || '').split('\n').map((l) => ` ${l}`));
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
54
74
|
// Pure: parse `answer` arguments into the PUT body, or { error }.
|
|
55
75
|
function parseAnswer(rest) {
|
|
56
76
|
const args = [...rest];
|
|
@@ -73,10 +73,8 @@ const ACTIVE_IDS = BUILDERS.filter((b) => b.status === 'active').map((b) => b.id
|
|
|
73
73
|
const FOUNDER = '42';
|
|
74
74
|
const COUNCIL = ['43', '44'];
|
|
75
75
|
|
|
76
|
-
// Each bound route as a request that
|
|
77
|
-
//
|
|
78
|
-
// tests/government_act_alone_buildings.mjs, with People's two carrying a real
|
|
79
|
-
// custom rank and builder of this project.
|
|
76
|
+
// Each bound route as a request that stops in its handler's first check (bad id or body),
|
|
77
|
+
// before any DB write — as tests/government_act_alone_buildings.mjs; People's two carry a real rank + builder.
|
|
80
78
|
const BAD = 'x';
|
|
81
79
|
const REQUESTS = Object.freeze({
|
|
82
80
|
'PATCH /project-settings/rot-days': ['PATCH', '/project-settings/rot-days', { days: 'soon' }],
|
|
@@ -98,6 +96,7 @@ const REQUESTS = Object.freeze({
|
|
|
98
96
|
'POST /goals/reorder': ['POST', '/goals/reorder', {}],
|
|
99
97
|
'POST /work-categories': ['POST', '/work-categories', {}],
|
|
100
98
|
'PUT /versions/focus': ['PUT', '/versions/focus', { version_id: ' ' }],
|
|
99
|
+
'POST /first-read/maps/:id/accept': ['POST', `/first-read/maps/${BAD}/accept`, { all: true }],
|
|
101
100
|
'POST /autonomy/fence': ['POST', '/autonomy/fence', { enabled: 'yes' }],
|
|
102
101
|
'POST /autonomy/fence/goals': ['POST', '/autonomy/fence/goals', { goal_id: 0 }],
|
|
103
102
|
'DELETE /autonomy/fence/goals/:goalId': ['DELETE', `/autonomy/fence/goals/${BAD}`],
|
|
@@ -141,6 +140,7 @@ const ROUTERS = Object.freeze([
|
|
|
141
140
|
'modules/lifecycle/routes/goals.js',
|
|
142
141
|
'modules/lifecycle/routes/versions.js',
|
|
143
142
|
'modules/lifecycle/routes/project-settings.js',
|
|
143
|
+
'modules/lifecycle/routes/first-read.js',
|
|
144
144
|
'modules/render-deploy/routes/act.js',
|
|
145
145
|
'modules/npm-release/routes/release.js',
|
|
146
146
|
'modules/provisioning/routes/core-upgrade.js',
|