@bongos/core 1.21.123 → 1.21.125

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 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.21.123",
6
- "core_contract": "1.21.123",
7
- "source_commit": "bfe95a4e390577d4579a3da16d81d5ada6bc8b9a",
5
+ "core_version": "1.21.125",
6
+ "core_contract": "1.21.125",
7
+ "source_commit": "6537f85e10dcb60107037c138237fe4e5d855500",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-14T19:30:01.919Z",
9
+ "built_at": "2026-10-15T15:06:45.205Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 605,
13
13
  "agent_docs_stubbed": 28,
14
- "functional_verbatim": 3122,
14
+ "functional_verbatim": 3128,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 3756,
20
- "tree_sha256": "c5becc4dfe0e822803424a46f1041b66adce70cbe65f85be941603de3db80cee",
19
+ "file_count": 3762,
20
+ "tree_sha256": "7fb72ed794b2cdaa7f0fe66ffdccb7b56f5c18a9d4d66f18b4767254072e1827",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -312,7 +312,7 @@
312
312
  {
313
313
  "path": "bin/bongos.js",
314
314
  "mode": "0000755",
315
- "sha256": "20794ff2c632668d2a65ab1ca1a0e6d5b240c19769dbabf6999c7347d41f54e6"
315
+ "sha256": "f2a64dc742bb1f13938840c9f294ea7a60f1895dc9366200e33d53891e5e0fe5"
316
316
  },
317
317
  {
318
318
  "path": "clients/bongos-client/README.md",
@@ -2922,7 +2922,7 @@
2922
2922
  {
2923
2923
  "path": "docs/module-api-changelog.md",
2924
2924
  "mode": "0000644",
2925
- "sha256": "f2b2bf656d2fa819a7bd0f10ceb966b5414c1c06f05f2e8ccaa9ed326a7227ed"
2925
+ "sha256": "a1ec4ace0bc91b34031c59bb4fd08263af542f5ddd1284204bfd821cf843b527"
2926
2926
  },
2927
2927
  {
2928
2928
  "path": "docs/modules-contract.md",
@@ -7242,7 +7242,7 @@
7242
7242
  {
7243
7243
  "path": "modules/lifecycle/github-push.js",
7244
7244
  "mode": "0000644",
7245
- "sha256": "65386fff8736c1b55c1f105c595758161e718352077603d1ba9aa7dd05d6d1d6"
7245
+ "sha256": "b15eb0c36f0acb41d11f52393ca9696de55b9326a483de02624b3a4d5e87250c"
7246
7246
  },
7247
7247
  {
7248
7248
  "path": "modules/lifecycle/goal-advisory.js",
@@ -7349,6 +7349,11 @@
7349
7349
  "mode": "0000644",
7350
7350
  "sha256": "69e9bf495dfdbc324e7f5664e09a5ad30e6eac896c12656b7d6ecb38d6be81f9"
7351
7351
  },
7352
+ {
7353
+ "path": "modules/lifecycle/local-land-proof.js",
7354
+ "mode": "0000644",
7355
+ "sha256": "7698b11de1978c29c1744032da5b28831a1f6823b905d60c8be8ac469916d46a"
7356
+ },
7352
7357
  {
7353
7358
  "path": "modules/lifecycle/matter-acts.js",
7354
7359
  "mode": "0000644",
@@ -7507,7 +7512,7 @@
7507
7512
  {
7508
7513
  "path": "modules/lifecycle/publish-reconciler.js",
7509
7514
  "mode": "0000644",
7510
- "sha256": "5d8bf02c3646f12debc411aa6291110c5bc4f1fe8917af9e17a4ecc5f6451e76"
7515
+ "sha256": "d03c36a2b3e50bb2334db66d177514542185a7277873891ee6d3a99c5ad10464"
7511
7516
  },
7512
7517
  {
7513
7518
  "path": "modules/lifecycle/removal-derivation.js",
@@ -7924,6 +7929,11 @@
7924
7929
  "mode": "0000644",
7925
7930
  "sha256": "22904d8c06daabb634b55f240b895c906e90ba4c6ed81e7a41cdbbb78635ace8"
7926
7931
  },
7932
+ {
7933
+ "path": "modules/onboarding/howto-guide.js",
7934
+ "mode": "0000644",
7935
+ "sha256": "18984c946d746da66ff7678379e9f49c9cfdcdb8f70d2acd852ab29c820baad0"
7936
+ },
7927
7937
  {
7928
7938
  "path": "modules/onboarding/module.json",
7929
7939
  "mode": "0000644",
@@ -10467,12 +10477,12 @@
10467
10477
  {
10468
10478
  "path": "package-lock.json",
10469
10479
  "mode": "0000644",
10470
- "sha256": "e92fbdef629a9283a773d94986531ae9d30412fe05dc3012dd918be8fb859030"
10480
+ "sha256": "cb1fe1a974c6063f91ae16893f1daddb028647374dc13d9df725ea4be5074d9a"
10471
10481
  },
10472
10482
  {
10473
10483
  "path": "package.json",
10474
10484
  "mode": "0000644",
10475
- "sha256": "cf96b6c94e06d4306f0b3e3c0626c051729a0498bd3e6760012ce86350286d16"
10485
+ "sha256": "e92ae06c5b33aa14e3ec516f009526f335ebec59b5e03b4e774a4a48c252d087"
10476
10486
  },
10477
10487
  {
10478
10488
  "path": "public-docs/index.html",
@@ -10492,7 +10502,7 @@
10492
10502
  {
10493
10503
  "path": "release-notes.json",
10494
10504
  "mode": "0000644",
10495
- "sha256": "86c4edcb22bf8005ac7f4e479a9b4037d2bdad36618ed1e8f739a3a786952344"
10505
+ "sha256": "415b376c73ce06c6803897093c02aa9157378d55487a0f84dd8387dc749a75a0"
10496
10506
  },
10497
10507
  {
10498
10508
  "path": "scripts/cron/style_guide_audit.sh",
@@ -10869,6 +10879,11 @@
10869
10879
  "mode": "0000644",
10870
10880
  "sha256": "9dd80d2c37602c59cb75db0be02cfadc737fc64891a81d03f5be5f0ea5e35a27"
10871
10881
  },
10882
+ {
10883
+ "path": "scripts/gds/core-pin-sync.js",
10884
+ "mode": "0000644",
10885
+ "sha256": "f8a3a0ee2c0f4dbc2583603cd6e7b547140a5f8dc286b7c5f0b6dadd6096a002"
10886
+ },
10872
10887
  {
10873
10888
  "path": "scripts/gds/coreB-carve-proof.sh",
10874
10889
  "mode": "0000644",
@@ -12812,7 +12827,7 @@
12812
12827
  {
12813
12828
  "path": "src/module-api.js",
12814
12829
  "mode": "0000644",
12815
- "sha256": "01bc6b541cf12105be05701170377582f537316fe3f70fdea2681bae07864bf7"
12830
+ "sha256": "81694f072dbbb4e64a286a4f5157d07a8e1105ae8a94d800f09eba00f30848e6"
12816
12831
  },
12817
12832
  {
12818
12833
  "path": "src/module-loader/catalog.js",
@@ -13824,6 +13839,11 @@
13824
13839
  "mode": "0000644",
13825
13840
  "sha256": "a3cf352d5dc340b453b22513c28d8c0a439526862535f51f84b7aea2161c734f"
13826
13841
  },
13842
+ {
13843
+ "path": "tests/core_pin_sync.mjs",
13844
+ "mode": "0000644",
13845
+ "sha256": "ac3714b1b089ba2450f1125c74cbe5491deb4ddec1eec77c4c83a8a635a84d93"
13846
+ },
13827
13847
  {
13828
13848
  "path": "tests/core_update_banner.mjs",
13829
13849
  "mode": "0000644",
@@ -15579,6 +15599,11 @@
15579
15599
  "mode": "0000644",
15580
15600
  "sha256": "7cd031d48b41424a654ddc66404cf112a21ecc7d558256ac27ec9357401f112c"
15581
15601
  },
15602
+ {
15603
+ "path": "tests/howto_guide.mjs",
15604
+ "mode": "0000644",
15605
+ "sha256": "8e9b206fdde738f289bc11e204852595e9128b523cfc20cd67e3e83fb60a2fac"
15606
+ },
15582
15607
  {
15583
15608
  "path": "tests/html_comment_nesting.mjs",
15584
15609
  "mode": "0000644",
@@ -16109,6 +16134,11 @@
16109
16134
  "mode": "0000644",
16110
16135
  "sha256": "64f8bd94dc461ca9b4ef82fae929d49675231afa6a24d6d65ba2dbe8721efc89"
16111
16136
  },
16137
+ {
16138
+ "path": "tests/local_land_proof.mjs",
16139
+ "mode": "0000644",
16140
+ "sha256": "0a7825f1e35c7251c66ab986555b71728575d3dff8be95f8a017dd66190d186f"
16141
+ },
16112
16142
  {
16113
16143
  "path": "tests/local_preview.mjs",
16114
16144
  "mode": "0000644",
@@ -17342,7 +17372,7 @@
17342
17372
  {
17343
17373
  "path": "tests/publish_reconciler.mjs",
17344
17374
  "mode": "0000644",
17345
- "sha256": "fac182001ce8a53bafea2d59661d2879c2ccbac55600936d151d1fe86ca59b71"
17375
+ "sha256": "882dfd49bebd89bed3c2bb20d0bf78e5f7b4da45e525492e8742b43f9c4e19a9"
17346
17376
  },
17347
17377
  {
17348
17378
  "path": "tests/publish_status_branch.mjs",
package/bin/bongos.js CHANGED
@@ -190,6 +190,15 @@ function main(argv) {
190
190
  return 0;
191
191
  }
192
192
 
193
+ // Run the core the checkout PINS (task 1004632). A task worktree has no node_modules
194
+ // of its own, so after a core update lands it resolves the main checkout's older core
195
+ // and the ship fails on a bug the pin already fixed. `ship` installs the pinned core
196
+ // into the checkout and re-runs under it; scripts/gds/core-pin-sync.js.
197
+ if (!COMPILED && verb === 'ship') {
198
+ const synced = require('../scripts/gds/core-pin-sync.js').ensurePinnedCore({ coreRoot: REPO_ROOT, argv });
199
+ if (synced != null) return synced;
200
+ }
201
+
193
202
  // `bongos exec <relpath> [args]` — run a core script by its package-relative path (see above).
194
203
  if (verb === 'exec') {
195
204
  if (COMPILED) {
@@ -3083,5 +3083,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
3083
3083
  landed since 1.21.121 with no explicit bump. run 38620147259. (task 1002620)
3084
3084
  1.21.123 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3085
3085
  landed since 1.21.122 with no explicit bump. run 38624070870. (task 1002620)
3086
+ 1.21.124 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3087
+ landed since 1.21.123 with no explicit bump. run 38626749089. (task 1002620)
3088
+ 1.21.125 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
3089
+ landed since 1.21.124 with no explicit bump. run 38757326466. (task 1002620)
3086
3090
  ---------------------------------------------------------------------------
3087
3091
  ```
@@ -1194,7 +1194,8 @@ async function landProvenForTask({ taskId, publishedSha, status }, deps = {}) {
1194
1194
  async function isAncestorOfDefaultBranch({ sha, base = 'main' } = {}, deps = {}) {
1195
1195
  if (typeof sha !== 'string' || !SAFE_SHA_RE.test(sha)) return false;
1196
1196
  const token = await resolveToken(deps);
1197
- if (!token) throw err('PUSH_UNCONFIGURED');
1197
+ // task 1004633: no credential (a laptop-mode instance) → ask the instance's own checkout.
1198
+ if (!token) return (deps.localLandProof || require('./local-land-proof')).isOnOriginMain({ sha, base }, deps.local || {});
1198
1199
  const repoInfo = deps.repoInfo || loadRepoInfo();
1199
1200
  if (!repoInfo || repoInfo.error) throw err('NO_REPO_INFO');
1200
1201
  const { owner, repo } = repoInfo;
@@ -0,0 +1,126 @@
1
+ 'use strict';
2
+
3
+ // local-land-proof — prove a land from the instance's OWN git checkout when the
4
+ // server holds no GitHub credential (task 1004633).
5
+ //
6
+ // WHY. Every land proof the lifecycle had asked GitHub: isAncestorOfDefaultBranch
7
+ // runs the compare API, and the publish reconciler returned early when
8
+ // isConfigured() was false. A laptop-mode instance (the owner's mercury project)
9
+ // merges a ship LOCALLY and pushes main itself — no PR, no server credential. So
10
+ // POST /tasks/:id/ship refused `ship_land_not_proven` (the compare threw
11
+ // PUSH_UNCONFIGURED, swallowed as "no proof"), the sweep never ran, and task 48
12
+ // sat at 'confirmed' with its merge commit on origin/main.
13
+ //
14
+ // THE PROOF is the same one the GitHub path makes, asked of a different witness:
15
+ // is the claim's recorded tip (claims.published_head_sha, written by attest-gate
16
+ // before its own credential check) an ancestor of origin/<base>? The witness is
17
+ // the remote-tracking ref after a fetch, never the local branch: a laptop ship
18
+ // merges into local main BEFORE it pushes, and a merge whose push failed must not
19
+ // count. A failed fetch (offline) falls back to the last fetched ref, which can
20
+ // only be behind the truth, so it can delay a ship but never invent one.
21
+ //
22
+ // Pure w.r.t. an injected `git` runner, so it unit-tests with no repository.
23
+
24
+ const { execFile } = require('node:child_process');
25
+ const { promisify } = require('node:util');
26
+
27
+ const execFileP = promisify(execFile);
28
+
29
+ // A real commit sha only: `merge-base` resolves refs, so "HEAD" or a branch name
30
+ // would answer about something other than the claim's tip.
31
+ const SAFE_SHA_RE = /^[0-9a-f]{7,64}$/;
32
+ const SAFE_BASE_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,100}$/;
33
+
34
+ function defaultGit(root) {
35
+ return (args) => execFileP('git', args, { cwd: root, timeout: 60_000, windowsHide: true });
36
+ }
37
+
38
+ function resolveRoot(deps) {
39
+ if (deps.root) return deps.root;
40
+ return require('../../src/module-api').resolveInstanceRoot();
41
+ }
42
+
43
+ async function fetchOrigin(git, base) {
44
+ try {
45
+ await git(['fetch', '--quiet', 'origin', base]);
46
+ } catch (_) {
47
+ /* offline or no credential: the last fetched origin ref still answers, only later */
48
+ }
49
+ }
50
+
51
+ // true = the tip is in origin/<base>; false = it is not (or git does not know the
52
+ // commit). Throws LOCAL_PROOF_UNAVAILABLE when there is no usable checkout (git
53
+ // missing, not a repository) so a caller can tell "not landed" from "cannot say".
54
+ async function isOnOriginMain({ sha, base = 'main' } = {}, deps = {}) {
55
+ if (typeof sha !== 'string' || !SAFE_SHA_RE.test(sha)) return false;
56
+ if (typeof base !== 'string' || !SAFE_BASE_RE.test(base) || base.includes('..')) return false;
57
+ const git = deps.git || defaultGit(resolveRoot(deps));
58
+ if (!deps.fetched) await fetchOrigin(git, base);
59
+ try {
60
+ await git(['merge-base', '--is-ancestor', sha, `refs/remotes/origin/${base}`]);
61
+ return true;
62
+ } catch (e) {
63
+ // 1 = not an ancestor; 128 = an unknown commit (never reached origin).
64
+ if (e && (e.code === 1 || e.code === 128)) return false;
65
+ const out = new Error('LOCAL_PROOF_UNAVAILABLE');
66
+ out.code = 'LOCAL_PROOF_UNAVAILABLE';
67
+ out.cause = e;
68
+ throw out;
69
+ }
70
+ }
71
+
72
+ // The reconciler's sweep for a host with no GitHub credential. Tip proof only: no
73
+ // PR, branch or deploy probe exists to consult, and a row with no recorded tip
74
+ // has nothing local to prove, so it waits (strand-watch reports it).
75
+ // One root and one fetch per sweep, not per row: origin/main is the same ref for
76
+ // every confirmed task the sweep reads.
77
+ async function sweepByLocalProof(rows, { db, emit, log = console.log, error = console.error, proof } = {}) {
78
+ if (!proof) {
79
+ let shared = null;
80
+ proof = async ({ sha }) => {
81
+ if (!shared) {
82
+ shared = { git: defaultGit(resolveRoot({})), fetched: true };
83
+ await fetchOrigin(shared.git, 'main');
84
+ }
85
+ return isOnOriginMain({ sha }, shared);
86
+ };
87
+ }
88
+ let checked = 0;
89
+ let reconciled = 0;
90
+ let errors = 0;
91
+ for (const r of rows || []) {
92
+ const tip = r && r.published_head_sha;
93
+ if (!tip || !SAFE_SHA_RE.test(String(tip))) continue;
94
+ checked++;
95
+ let landed = false;
96
+ try {
97
+ landed = await proof({ sha: String(tip) });
98
+ } catch (e) {
99
+ errors++;
100
+ error(`[publish-reconciler] task ${r.task_id}: local land proof unavailable: ${e && e.message}`);
101
+ continue;
102
+ }
103
+ if (!landed) continue;
104
+ const reason = `published tip ${String(tip).slice(0, 8)} in origin/main (local checkout; no GitHub credential)`;
105
+ try {
106
+ const shipped = await db.shipTask({
107
+ taskId: r.task_id,
108
+ builderId: r.builder_id,
109
+ landAssurance: { kind: 'proof', reason },
110
+ });
111
+ reconciled++;
112
+ log(`[publish-reconciler] task ${r.task_id} (${reason}) landed → shipped`);
113
+ try { await emit('task.shipped', shipped); } catch (e) {
114
+ error(`[publish-reconciler] ship broadcast for task ${r.task_id} (non-blocking): ${e && e.message}`);
115
+ }
116
+ } catch (e) {
117
+ if (!e || e.code !== 'TASK_NOT_CONFIRMED') {
118
+ errors++;
119
+ error(`[publish-reconciler] shipTask ${r.task_id} failed: ${e && e.message}`);
120
+ }
121
+ }
122
+ }
123
+ return { checked, reconciled, errors };
124
+ }
125
+
126
+ module.exports = { isOnOriginMain, sweepByLocalProof, SAFE_SHA_RE };
@@ -76,6 +76,7 @@
76
76
  const githubPush = require('./github-push');
77
77
  const seams = require('../../src/module-api');
78
78
  const conflictResolve = require('./conflict-resolve');
79
+ const localLandProof = require('./local-land-proof'); // task 1004633
79
80
  // The claim gate's grace for a strand its builder may be unable to clear — imported
80
81
  // rather than restated so the sweep's escalation and the gate agree on one number
81
82
  // (task 1003294).
@@ -195,10 +196,22 @@ async function runSweep(deps = {}) {
195
196
  if (!warn.unconfigured) {
196
197
  warn.unconfigured = true;
197
198
  errFn(
198
- `[publish-reconciler] WARN ${describeTarget({ configured: false, repo: resolveTarget(deps) })} — confirmed→shipped can NEVER land until a credential is set (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY — installation id is auto-discovered — or GITHUB_PUSH_TOKEN). Subsequent sweeps stay quiet until this changes.`
199
+ `[publish-reconciler] WARN ${describeTarget({ configured: false, repo: resolveTarget(deps) })} — no GitHub credential (GITHUB_APP_ID + GITHUB_APP_PRIVATE_KEY — installation id is auto-discovered — or GITHUB_PUSH_TOKEN): confirmed→shipped lands only on a recorded tip this instance's own checkout finds in origin/main. Subsequent sweeps stay quiet until this changes.`
199
200
  );
200
201
  }
201
- return { skipped: 'unconfigured', checked: 0, reconciled: 0, merged: 0, resolvedConflicts: 0, errors: 0, strandsFlagged: 0 };
202
+ // task 1004633: a laptop-mode instance merges and pushes main itself, with no
203
+ // server credential. Returning here stranded its merged tasks at 'confirmed'
204
+ // forever (mercury task 48), so prove the recorded tip from the local checkout.
205
+ const local = deps.localLandProof || localLandProof;
206
+ let rows = [];
207
+ try {
208
+ rows = await db.listConfirmedTasksForReconcile({ limit: deps.limit || SWEEP_LIMIT });
209
+ } catch (e) {
210
+ errFn(`[publish-reconciler] could not list confirmed tasks: ${e && e.message}`);
211
+ return { skipped: 'unconfigured', checked: 0, reconciled: 0, merged: 0, resolvedConflicts: 0, errors: 1, strandsFlagged: 0 };
212
+ }
213
+ const swept = await local.sweepByLocalProof(rows, { db, emit, log: logFn, error: errFn });
214
+ return { skipped: 'unconfigured', checked: swept.checked, reconciled: swept.reconciled, merged: 0, resolvedConflicts: 0, errors: swept.errors, strandsFlagged: 0 };
202
215
  }
203
216
  warn.unconfigured = false; // re-arm: a later credential LOSS must warn again
204
217
 
@@ -0,0 +1,259 @@
1
+ // modules/onboarding/howto-guide.js — the in-session how-to guide: ~25 short steps an AI walks a person through (task 1004627)
2
+ //
3
+ // WHY. The owner opened Bongos on their own project and did not know where to begin:
4
+ // nothing told them `/builder-start` exists, what a goal is for, or what happens on a
5
+ // first ship. This guide is the CONTENT an AI assistant walks a person through inside
6
+ // their own session, one short step at a time, at the moment it is useful — so someone
7
+ // who has never heard of a Bongos command is never lost.
8
+ //
9
+ // It is the single source of truth for the three tasks that build on it:
10
+ // • task 1004634 stores each person's read/unread state per step id;
11
+ // • task 1004635 shows the right unread step when its moment arrives, then marks it read;
12
+ // • task 1004636 lets a person reopen the guide, see progress, or skip it.
13
+ // So the step ids are STABLE: a stored "read" mark names one. Never rename or reuse an
14
+ // id — retire a step by deleting it and add a new id for new content
15
+ // (tests/howto_guide.mjs pins the shape and the plain-language rule).
16
+ //
17
+ // Each step is: what the feature is FOR (`what`), WHEN to reach for it (`when`), and the
18
+ // exact words or command to TRY (`try`). Plain language throughout — the reader may not
19
+ // be technical; the test bans the accessibility scorer's jargon list. Commands are the
20
+ // ones an instance actually gets (core skills + `npx bongos` verbs); a step names a
21
+ // slash command AND the plain words that reach it, because typed-only commands do not
22
+ // answer to plain words.
23
+ //
24
+ // Read it in a terminal: `node modules/onboarding/howto-guide.js` (add `--moment <id>`).
25
+
26
+ 'use strict';
27
+
28
+ // The moments, in the order a new project meets them. `trigger` says, for task 1004635,
29
+ // what event opens the moment; it is descriptive here, not wiring.
30
+ const MOMENTS = Object.freeze([
31
+ { id: 'first_open', title: 'Opening the project for the first time', trigger: 'the first prompt of the first session in a project folder' },
32
+ { id: 'setup', title: 'Setting up the project', trigger: 'the project has no goal yet, or setup is unfinished' },
33
+ { id: 'goal', title: 'Creating a goal', trigger: 'the person asks to plan work, or the project has no open goal' },
34
+ { id: 'task', title: 'Creating a task', trigger: 'the person describes a change they want, or a goal has no tasks' },
35
+ { id: 'find_work', title: 'Finding work', trigger: 'the person asks what to do next, or starts a session with nothing claimed' },
36
+ { id: 'first_claim', title: 'Claiming your first task', trigger: 'the person claims a task for the first time' },
37
+ { id: 'first_ship', title: 'Your first ship', trigger: 'the person finishes the work on their first claimed task' },
38
+ { id: 'after_ship', title: 'After a ship', trigger: 'a task reaches shipped for the first time' },
39
+ { id: 'invite', title: 'Inviting people', trigger: 'the person mentions a teammate, or asks how others can help' },
40
+ { id: 'settings', title: 'Settings', trigger: 'the person asks to change how Bongos behaves for them or the project' },
41
+ { id: 'stuck', title: 'When something goes wrong', trigger: 'a command fails, or the person says they are stuck or confused' },
42
+ ]);
43
+
44
+ const STEPS = Object.freeze([
45
+ // first_open
46
+ {
47
+ id: 'welcome', moment: 'first_open', title: 'What Bongos is',
48
+ what: 'Bongos keeps the list of everything your project needs, and the AI does the building. You decide what gets made; the AI does the work and records it.',
49
+ when: 'The first time you open this project with an AI assistant.',
50
+ try: 'Ask: "What is Bongos and what can it do for me?"',
51
+ },
52
+ {
53
+ id: 'where_to_start', moment: 'first_open', title: 'Where to start',
54
+ what: 'There are three ways in: finish setting up the project, make a goal (a bigger outcome you want), or make a task (one piece of work).',
55
+ when: 'Any time you are not sure what to do first.',
56
+ try: 'Say: "Where should I start?" The AI will look at the project and suggest one of the three.',
57
+ },
58
+ {
59
+ id: 'ask_in_plain_words', moment: 'first_open', title: 'Just ask in plain words',
60
+ what: 'You never need to memorise commands. Describe what you want and the AI picks the right one, then tells you which it used.',
61
+ when: 'Whenever you want something done and do not know the command.',
62
+ try: 'Say: "I want to add a contact form to the website."',
63
+ },
64
+ {
65
+ id: 'the_hall', moment: 'first_open', title: 'The hall: your project in a browser',
66
+ what: 'The hall is your project\'s web page. It shows goals, tasks, who is working on what, and what has shipped.',
67
+ when: 'When you want to see everything at a glance instead of asking.',
68
+ try: 'Ask: "Open the hall for this project" or "What is the hall link?"',
69
+ },
70
+ // setup
71
+ {
72
+ id: 'sign_in', moment: 'setup', title: 'Sign in once',
73
+ what: 'Signing in links this computer to your project, so the AI can claim and ship work in your name.',
74
+ when: 'The first time on a new computer, or when the AI says your sign-in has expired.',
75
+ try: 'Type /builder-setup, or in a terminal run `npx bongos login`.',
76
+ },
77
+ {
78
+ id: 'check_setup', moment: 'setup', title: 'Check everything is ready',
79
+ what: 'A quick health check of this computer and your sign-in, with a plain fix for anything missing.',
80
+ when: 'Before your first task, or when something that used to work stops working.',
81
+ try: 'Say: "Check my setup", or run `npx bongos doctor`.',
82
+ },
83
+ {
84
+ id: 'project_status', moment: 'setup', title: 'See where the project stands',
85
+ what: 'A summary of the current version: what is done, what is left, and what is blocking.',
86
+ when: 'At the start of a working session, or before planning.',
87
+ try: 'Type /status, or say "How far along is the project?"',
88
+ },
89
+ // goal
90
+ {
91
+ id: 'what_is_a_goal', moment: 'goal', title: 'What a goal is',
92
+ what: 'A goal is a bigger outcome, like "people can sign up" — a home for the tasks that get you there, with a clear "done when".',
93
+ when: 'When what you want is too big to finish in one sitting.',
94
+ try: 'Ask: "Should this be a goal or a single task?"',
95
+ },
96
+ {
97
+ id: 'create_goal', moment: 'goal', title: 'Create a goal',
98
+ what: 'The AI asks what the goal is for and what "done" looks like, then writes it up with its first few tasks.',
99
+ when: 'When you have decided on a new outcome to work towards.',
100
+ try: 'Type /goal-create, or say "Make a goal for letting people sign up."',
101
+ },
102
+ // task
103
+ {
104
+ id: 'what_is_a_task', moment: 'task', title: 'What a task is',
105
+ what: 'A task is one piece of work small enough to finish and check in one go. Every change to the project belongs to a task, so there is always a record of it.',
106
+ when: 'Whenever you want something changed.',
107
+ try: 'Ask: "What makes a good task?"',
108
+ },
109
+ {
110
+ id: 'create_task', moment: 'task', title: 'Create a task',
111
+ what: 'Describe the change and how you will know it is done. The AI writes it up and files it under the right goal.',
112
+ when: 'When you know a specific change you want.',
113
+ try: 'Say: "Make a task: the contact form should email me each message."',
114
+ },
115
+ {
116
+ id: 'capture_an_idea', moment: 'task', title: 'Save an idea for later',
117
+ what: 'Not ready to commit to it? Save it as an idea. Ideas wait in a list until you decide to turn them into tasks or drop them.',
118
+ when: 'When a thought comes up in the middle of other work.',
119
+ try: 'Say: "Save an idea: a dark mode for the website."',
120
+ },
121
+ // find_work
122
+ {
123
+ id: 'whats_claimable', moment: 'find_work', title: 'See what you can work on',
124
+ what: 'A list of the tasks that are ready right now and safe to start without clashing with anyone else\'s work.',
125
+ when: 'At the start of a session, or when you finish a task.',
126
+ try: 'Type /builder-start, or say "What can I work on?"',
127
+ },
128
+ {
129
+ id: 'work_a_goal', moment: 'find_work', title: 'Work through a whole goal',
130
+ what: 'The AI takes the goal\'s tasks one at a time — start, build, ship, next — and stops to ask you only when it needs a decision.',
131
+ when: 'When a goal has several tasks that follow on from each other.',
132
+ try: 'Say: "Work through goal 12 end to end", or type /builder-sequence.',
133
+ },
134
+ // first_claim
135
+ {
136
+ id: 'claim_a_task', moment: 'first_claim', title: 'Claiming a task',
137
+ what: 'Claiming says "I am on this one", so nobody else starts the same work. The AI then works on it in its own separate copy of the project, so your main folder stays tidy.',
138
+ when: 'When you pick a task to work on.',
139
+ try: 'Type /builder-claim followed by the task number, or say "Claim task 42."',
140
+ },
141
+ {
142
+ id: 'give_a_task_back', moment: 'first_claim', title: 'Give a task back',
143
+ what: 'Changed your mind? Giving it back makes the task available again. Nothing is lost and nothing is charged.',
144
+ when: 'When you will not finish a task you claimed.',
145
+ try: 'Type /builder-release, or say "Give this task back."',
146
+ },
147
+ {
148
+ id: 'preview_before_ship', moment: 'first_claim', title: 'See a change before it ships',
149
+ what: 'For changes you can see, the AI can show you the page as it will look, before anything goes live.',
150
+ when: 'When the task changes something on screen.',
151
+ try: 'Say: "Let me see it before you ship", or type /builder-stage.',
152
+ },
153
+ // first_ship
154
+ {
155
+ id: 'what_ship_does', moment: 'first_ship', title: 'What shipping does',
156
+ what: 'Shipping finishes a task: automatic reviewers check the work, then it is added to the project. Going live for your users can wait for a person to say yes.',
157
+ when: 'When the work on your task is finished.',
158
+ try: 'Type /builder-ship, or say "Ship it."',
159
+ },
160
+ {
161
+ id: 'review_findings', moment: 'first_ship', title: 'When the reviewers ask for fixes',
162
+ what: 'If the reviewers find a problem, the task is held, not lost. The AI fixes what they listed and asks for another review.',
163
+ when: 'When a ship says it needs fixes.',
164
+ try: 'Say: "Fix what the reviewers found and try again."',
165
+ },
166
+ // after_ship
167
+ {
168
+ id: 'what_happens_after', moment: 'after_ship', title: 'What happens after a ship',
169
+ what: 'The task is marked shipped, anything that was waiting on it becomes ready, and the record of the work is kept for the next person.',
170
+ when: 'Right after your first task ships.',
171
+ try: 'Ask: "What did that ship unlock?"',
172
+ },
173
+ {
174
+ id: 'fresh_session', moment: 'after_ship', title: 'Start fresh after a few tasks',
175
+ what: 'A long conversation gets slower and costlier. After a few tasks, the AI can write a short note so a fresh conversation picks up exactly where you left off.',
176
+ when: 'After three or so tasks in one conversation.',
177
+ try: 'Say: "Hand this off to a fresh session", or type /session-handoff.',
178
+ },
179
+ // invite
180
+ {
181
+ id: 'invite_people', moment: 'invite', title: 'Invite people',
182
+ what: 'Bring someone onto the project by their GitHub name. You choose whether anyone can join, people must ask first, or only invited people can sign in.',
183
+ when: 'When you want someone else to help build.',
184
+ try: 'In the hall, use "Invite someone" and enter their GitHub name, or ask "How do I invite someone?"',
185
+ },
186
+ {
187
+ id: 'ask_for_help', moment: 'invite', title: 'Ask a teammate for help',
188
+ what: 'Send a named person, or anyone with the right skills, a request for help on a task. It shows up in their own list.',
189
+ when: 'When you are stuck on a task and someone else knows the area.',
190
+ try: 'Say: "Ask Sam to help with task 42", or type /ask-for-help.',
191
+ },
192
+ // settings
193
+ {
194
+ id: 'ship_mode', moment: 'settings', title: 'Ship mode: who starts a ship',
195
+ what: 'In Auto, the AI ships a finished task by itself. In Manual, it stops and waits for you to say "ship it".',
196
+ when: 'When you want more, or less, control over when work is added to the project.',
197
+ try: 'In the hall\'s project settings pick Auto or Manual, or run `npx bongos ship-mode` to see the current one.',
198
+ },
199
+ {
200
+ id: 'how_the_ai_talks', moment: 'settings', title: 'How the AI talks to you',
201
+ what: 'Tell Bongos how technical you are and how long you like answers. Every AI session on this project follows it.',
202
+ when: 'When answers feel too technical, too long, or too short.',
203
+ try: 'Open your settings in the hall and look for how the AI talks to you, or say "Keep your answers shorter."',
204
+ },
205
+ // stuck
206
+ {
207
+ id: 'when_stuck', moment: 'stuck', title: 'When something goes wrong',
208
+ what: 'Every refusal comes with a plain explanation: what happened, what the AI will do, and whether it needs you. Most fix themselves.',
209
+ when: 'When a command fails or you do not understand what happened.',
210
+ try: 'Say: "Explain that in plain words and fix it."',
211
+ },
212
+ {
213
+ id: 'tell_the_bongos_team', moment: 'stuck', title: 'Tell the Bongos team',
214
+ what: 'If Bongos itself gets in your way, say so. Your words go to the people who build Bongos, and each complaint becomes a fix.',
215
+ when: 'Whenever Bongos is confusing, slow, or broken.',
216
+ try: 'Say: "Bongos feedback: …" followed by what went wrong.',
217
+ },
218
+ ]);
219
+
220
+ const MOMENT_IDS = new Set(MOMENTS.map((m) => m.id));
221
+
222
+ function stepById(id) { return STEPS.find((s) => s.id === id) || null; }
223
+
224
+ // The steps for one moment, in guide order. Unknown moment → [].
225
+ function stepsForMoment(momentId) { return STEPS.filter((s) => s.moment === momentId); }
226
+
227
+ // The first step not in `readIds` (an iterable of step ids), in guide order — what
228
+ // "continue the tutorial" means. null when everything is read.
229
+ function nextUnread(readIds) {
230
+ const read = new Set(readIds || []);
231
+ return STEPS.find((s) => !read.has(s.id)) || null;
232
+ }
233
+
234
+ function formatStep(step) {
235
+ return [`${step.title} [${step.id}]`, ` What it is for: ${step.what}`, ` When: ${step.when}`, ` Try: ${step.try}`].join('\n');
236
+ }
237
+
238
+ function formatGuide({ moment = null } = {}) {
239
+ const out = [];
240
+ for (const m of MOMENTS) {
241
+ if (moment && m.id !== moment) continue;
242
+ out.push(`## ${m.title}`);
243
+ for (const s of stepsForMoment(m.id)) out.push(formatStep(s), '');
244
+ }
245
+ return out.join('\n').trimEnd() + '\n';
246
+ }
247
+
248
+ if (require.main === module) {
249
+ const i = process.argv.indexOf('--moment');
250
+ const moment = i > -1 ? process.argv[i + 1] : null;
251
+ if (moment && !MOMENT_IDS.has(moment)) {
252
+ process.stderr.write(`unknown moment "${moment}" — one of: ${[...MOMENT_IDS].join(', ')}\n`);
253
+ process.exitCode = 2;
254
+ } else {
255
+ process.stdout.write(formatGuide({ moment }));
256
+ }
257
+ }
258
+
259
+ module.exports = { MOMENTS, STEPS, MOMENT_IDS, stepById, stepsForMoment, nextUnread, formatStep, formatGuide };
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.123",
3
+ "version": "1.21.125",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.123",
9
+ "version": "1.21.125",
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.21.123",
3
+ "version": "1.21.125",
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",