@bongos/core 1.21.129 → 1.21.130
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 +18 -18
- package/docs/file-map.md +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/modules/onboarding/howto-guide.js +6 -0
- package/modules/onboarding/howto-moments.js +28 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/scripts/gds/claude-md-template.js +1 -1
- package/scripts/gds/howto.js +82 -2
- package/src/module-api.js +1 -1
- package/tests/conductor_main_e2e.mjs +22 -8
- package/tests/howto_guide.mjs +12 -0
- package/tests/howto_moments.mjs +48 -1
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.21.
|
|
6
|
-
"core_contract": "1.21.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.21.130",
|
|
6
|
+
"core_contract": "1.21.130",
|
|
7
|
+
"source_commit": "d9a1489516436c9e34730e3c028f4465cfd62474",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-10-
|
|
9
|
+
"built_at": "2026-10-15T17:14:20.940Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 606,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 3781,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "309831afd3ba5a2aed6ebe90ff918936d2c3fd2ea36d1fb2a90e41b7187cb474",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2912,7 +2912,7 @@
|
|
|
2912
2912
|
{
|
|
2913
2913
|
"path": "docs/file-map.md",
|
|
2914
2914
|
"mode": "0000644",
|
|
2915
|
-
"sha256": "
|
|
2915
|
+
"sha256": "18a6694f80109250c168809eb753e4d4f8c7c62d5eed96b9bee217d89189decf"
|
|
2916
2916
|
},
|
|
2917
2917
|
{
|
|
2918
2918
|
"path": "docs/handoff-template.md",
|
|
@@ -2922,7 +2922,7 @@
|
|
|
2922
2922
|
{
|
|
2923
2923
|
"path": "docs/module-api-changelog.md",
|
|
2924
2924
|
"mode": "0000644",
|
|
2925
|
-
"sha256": "
|
|
2925
|
+
"sha256": "a8659f105f89cd130c2772293c07aad2e7071ef6adc4c094e65fad8ac205a2ad"
|
|
2926
2926
|
},
|
|
2927
2927
|
{
|
|
2928
2928
|
"path": "docs/modules-contract.md",
|
|
@@ -7937,12 +7937,12 @@
|
|
|
7937
7937
|
{
|
|
7938
7938
|
"path": "modules/onboarding/howto-guide.js",
|
|
7939
7939
|
"mode": "0000644",
|
|
7940
|
-
"sha256": "
|
|
7940
|
+
"sha256": "7b352dda65b04f44e48feb19fc3a959d9a122411c22f2830b9cc967d1a0f6852"
|
|
7941
7941
|
},
|
|
7942
7942
|
{
|
|
7943
7943
|
"path": "modules/onboarding/howto-moments.js",
|
|
7944
7944
|
"mode": "0000644",
|
|
7945
|
-
"sha256": "
|
|
7945
|
+
"sha256": "6950e77759f8e574187e7e77871e28492116c3c5ad17d3cd2cebd3a018c0ad6c"
|
|
7946
7946
|
},
|
|
7947
7947
|
{
|
|
7948
7948
|
"path": "modules/onboarding/howto-state.js",
|
|
@@ -10502,12 +10502,12 @@
|
|
|
10502
10502
|
{
|
|
10503
10503
|
"path": "package-lock.json",
|
|
10504
10504
|
"mode": "0000644",
|
|
10505
|
-
"sha256": "
|
|
10505
|
+
"sha256": "1556b57526d416a0156c23915ba0aaf080eef4732031261f80c7a2c891f49b9d"
|
|
10506
10506
|
},
|
|
10507
10507
|
{
|
|
10508
10508
|
"path": "package.json",
|
|
10509
10509
|
"mode": "0000644",
|
|
10510
|
-
"sha256": "
|
|
10510
|
+
"sha256": "5db6c25e29a3abec630266149723a99b09a249bcc375bca40e39bcc8eb5a0d2b"
|
|
10511
10511
|
},
|
|
10512
10512
|
{
|
|
10513
10513
|
"path": "public-docs/index.html",
|
|
@@ -10527,7 +10527,7 @@
|
|
|
10527
10527
|
{
|
|
10528
10528
|
"path": "release-notes.json",
|
|
10529
10529
|
"mode": "0000644",
|
|
10530
|
-
"sha256": "
|
|
10530
|
+
"sha256": "52d4fa7c27b8a7cfe9ead4896e254d332b1abae140b60beb8c848efa1e16fa2f"
|
|
10531
10531
|
},
|
|
10532
10532
|
{
|
|
10533
10533
|
"path": "scripts/cron/style_guide_audit.sh",
|
|
@@ -10852,7 +10852,7 @@
|
|
|
10852
10852
|
{
|
|
10853
10853
|
"path": "scripts/gds/claude-md-template.js",
|
|
10854
10854
|
"mode": "0000644",
|
|
10855
|
-
"sha256": "
|
|
10855
|
+
"sha256": "ebfbfe3e5dbd865b5699d65fd7f3fc345f992c92f84b41db716295c40553dacb"
|
|
10856
10856
|
},
|
|
10857
10857
|
{
|
|
10858
10858
|
"path": "scripts/gds/cleanup-orphans.js",
|
|
@@ -11262,7 +11262,7 @@
|
|
|
11262
11262
|
{
|
|
11263
11263
|
"path": "scripts/gds/howto.js",
|
|
11264
11264
|
"mode": "0000644",
|
|
11265
|
-
"sha256": "
|
|
11265
|
+
"sha256": "06c77dee345d93604e5d1bde431476399e858556897114bae368ed513d3a944c"
|
|
11266
11266
|
},
|
|
11267
11267
|
{
|
|
11268
11268
|
"path": "scripts/gds/http-api-client.js",
|
|
@@ -12882,7 +12882,7 @@
|
|
|
12882
12882
|
{
|
|
12883
12883
|
"path": "src/module-api.js",
|
|
12884
12884
|
"mode": "0000644",
|
|
12885
|
-
"sha256": "
|
|
12885
|
+
"sha256": "7c374bdf850a487a15f60f8ddd21aec96c49f644a50b396d1567a1e227bf4e0c"
|
|
12886
12886
|
},
|
|
12887
12887
|
{
|
|
12888
12888
|
"path": "src/module-loader/catalog.js",
|
|
@@ -13797,7 +13797,7 @@
|
|
|
13797
13797
|
{
|
|
13798
13798
|
"path": "tests/conductor_main_e2e.mjs",
|
|
13799
13799
|
"mode": "0000644",
|
|
13800
|
-
"sha256": "
|
|
13800
|
+
"sha256": "5d825f686bbe5351def658bf6e2f011e3ca351312f04009500ee65c8a195914d"
|
|
13801
13801
|
},
|
|
13802
13802
|
{
|
|
13803
13803
|
"path": "tests/confirm_attribution.mjs",
|
|
@@ -15662,12 +15662,12 @@
|
|
|
15662
15662
|
{
|
|
15663
15663
|
"path": "tests/howto_guide.mjs",
|
|
15664
15664
|
"mode": "0000644",
|
|
15665
|
-
"sha256": "
|
|
15665
|
+
"sha256": "72b439c6189e78bed945ec7e16da9cfeb32e34c9f3a4417f024c32dec07a73cf"
|
|
15666
15666
|
},
|
|
15667
15667
|
{
|
|
15668
15668
|
"path": "tests/howto_moments.mjs",
|
|
15669
15669
|
"mode": "0000644",
|
|
15670
|
-
"sha256": "
|
|
15670
|
+
"sha256": "fc668c29944ddbbc629e9ee4e6c30dc1cffbc88d12f478a1b95a4585e360a185"
|
|
15671
15671
|
},
|
|
15672
15672
|
{
|
|
15673
15673
|
"path": "tests/howto_state.mjs",
|
package/docs/file-map.md
CHANGED
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
│ ├── linkify-refs.js ← rewrite bare `#NNN` task refs across ALL docs → status-dashboard permalinks (stops GitHub's PR/issue autolinker mis-linking them); conservative classifier, token-free id list via /public/ref-ids; --apply / --check (CI gate: tests/linkify_check.mjs, in the required `unit` check) / --dry-run default ([#604](https://example.com/builders#/task/604), [#653](https://example.com/builders#/task/653)).
|
|
170
170
|
│ ├── release.js ← resolve claim as abandoned (`/builder-release`)
|
|
171
171
|
│ ├── cost.js ← log a cost entry (`/builder-cost`)
|
|
172
|
-
│ ├── howto.js ← the how-to guide for AI tools without hooks: `bongos howto moment <id>` prints + marks read the person's unread steps for a moment (task 1004635)
|
|
172
|
+
│ ├── howto.js ← the how-to guide for AI tools without hooks: `bongos howto moment <id>` prints + marks read the person's unread steps for a moment (task 1004635); `show` / `next` / `skip` / `reset` reopen the guide, continue it, turn it off or start it over (task 1004636)
|
|
173
173
|
│ ├── me.js ← current builder profile + leaderboard
|
|
174
174
|
│ ├── goal.js ← goal board CLI: `list` (rollup per goal) + `show <id>` (criteria, tasks, waits-on, next claimable)
|
|
175
175
|
│ ├── seed-v3-tasks.js ← V3 Planning Session #1: 80 net-new GDS-V3 tasks (V3.R01–V3.R89) + 10 idea_inbox promotions; idempotent on source_ref
|
|
@@ -3095,5 +3095,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
3095
3095
|
landed since 1.21.127 with no explicit bump. run 38770118067. (task 1002620)
|
|
3096
3096
|
1.21.129 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3097
3097
|
landed since 1.21.128 with no explicit bump. run 38773346540. (task 1002620)
|
|
3098
|
+
1.21.130 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
3099
|
+
landed since 1.21.129 with no explicit bump. run 38775541138. (task 1002620)
|
|
3098
3100
|
---------------------------------------------------------------------------
|
|
3099
3101
|
```
|
|
@@ -67,6 +67,12 @@ const STEPS = Object.freeze([
|
|
|
67
67
|
when: 'When you want to see everything at a glance instead of asking.',
|
|
68
68
|
try: 'Ask: "Open the hall for this project" or "What is the hall link?"',
|
|
69
69
|
},
|
|
70
|
+
{
|
|
71
|
+
id: 'reopen_the_guide', moment: 'first_open', title: 'Come back to this guide',
|
|
72
|
+
what: 'These tips show up once each, at the moment they help. You can look at the whole list, pick up the next tip, or turn the tips off.',
|
|
73
|
+
when: 'Whenever you want a refresher, or the tips get in the way.',
|
|
74
|
+
try: 'Say: "Show me the how-to", "What\'s next in the tutorial?" or "Skip the tutorial."',
|
|
75
|
+
},
|
|
70
76
|
// setup
|
|
71
77
|
{
|
|
72
78
|
id: 'sign_in', moment: 'setup', title: 'Sign in once',
|
|
@@ -33,6 +33,33 @@ const INVITE_RE = /\binvit(?:e|ing)\b|\bteammates?\b|\bco-?workers?\b|\bcolleagu
|
|
|
33
33
|
const SETTINGS_RE = /\bsettings?\b|\bship[- ]mode\b|\bshorter answers?\b|\btoo (?:long|technical)\b/i;
|
|
34
34
|
const SETUP_RE = /\bset ?up\b|\bsign(?:ed)? ?in\b|\blog ?in\b|\/builder-setup\b/i;
|
|
35
35
|
|
|
36
|
+
// A person asking for the guide itself (task 1004636): "show me the how-to", "what's
|
|
37
|
+
// next in the tutorial", "skip the tutorial", "start the tutorial over". Checked in
|
|
38
|
+
// this order, so "stop showing me the tutorial" is a skip, not a show.
|
|
39
|
+
// "guide" and "tips" only with a determiner straight before them, so "the style guide"
|
|
40
|
+
// or "tips for the header" are not read as asking for this guide.
|
|
41
|
+
const GUIDE = String.raw`(?:how-?to|tutorial|(?:the|this|your|these|those) (?:guide|tips))`;
|
|
42
|
+
const REQUESTS = [
|
|
43
|
+
['skip', new RegExp(String.raw`\b(?:skip|stop|turn off|hide|dismiss|no more)\b.{0,24}\b${GUIDE}\b|\b${GUIDE}\b.{0,12}\b(?:off|away)\b`, 'i')],
|
|
44
|
+
['reset', new RegExp(String.raw`\b(?:restart|start over|reset|bring back)\b.{0,24}\b${GUIDE}\b|\b${GUIDE}\b.{0,12}\bover\b`, 'i')],
|
|
45
|
+
['next', new RegExp(String.raw`\bnext\b.{0,24}\b${GUIDE}\b|\b(?:continue|resume)\b.{0,12}\b${GUIDE}\b`, 'i')],
|
|
46
|
+
['show', new RegExp(String.raw`\b(?:show|open|reopen|see|view)\b.{0,24}\b${GUIDE}\b|\b${GUIDE}\b.{0,12}\bprogress\b`, 'i')],
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// 'show' | 'next' | 'skip' | 'reset' when the prompt asks for the guide itself, else null.
|
|
50
|
+
function guideRequest(prompt) {
|
|
51
|
+
const p = typeof prompt === 'string' ? prompt : '';
|
|
52
|
+
for (const [kind, re] of REQUESTS) if (re.test(p)) return kind;
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// What the AI is told to do for one of those requests: run the matching command and
|
|
57
|
+
// pass its output on. The command changes the stored read state itself.
|
|
58
|
+
function renderRequestForAi(kind) {
|
|
59
|
+
const said = { show: 'to see the how-to', next: 'for the next how-to step', skip: 'to skip the how-to', reset: 'to start the how-to over' }[kind];
|
|
60
|
+
return `The person asked ${said}: run \`npx bongos howto ${kind}\` and pass on what it prints in plain words.`;
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
// The moments open right now, most urgent first. Signals:
|
|
37
64
|
// firstPrompt true on the first prompt of a session
|
|
38
65
|
// prompt the person's words
|
|
@@ -88,4 +115,4 @@ function renderForPerson({ moment, steps }) {
|
|
|
88
115
|
return [`${moment.title}:`, ...steps.map((s) => `- ${s.title}: ${s.what}\n Try: ${s.try}`)].join('\n');
|
|
89
116
|
}
|
|
90
117
|
|
|
91
|
-
module.exports = { MAX_STEPS, openMoments, unreadForMoment, pickSteps, renderForAi, renderForPerson };
|
|
118
|
+
module.exports = { MAX_STEPS, guideRequest, renderRequestForAi, openMoments, unreadForMoment, pickSteps, renderForAi, renderForPerson };
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.130",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.130",
|
|
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.
|
|
3
|
+
"version": "1.21.130",
|
|
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
|
@@ -9776,5 +9776,11 @@
|
|
|
9776
9776
|
"id": "1003815",
|
|
9777
9777
|
"text": "The module store can now record which release channel each module version is on — tester first, then everyone — with room for more, like taking a version back. Nothing changes for users yet; the next tasks use it."
|
|
9778
9778
|
}
|
|
9779
|
+
],
|
|
9780
|
+
"1.21.130": [
|
|
9781
|
+
{
|
|
9782
|
+
"id": "1004636",
|
|
9783
|
+
"text": "People can now say 'show me the how-to', 'what's next in the tutorial' or 'skip the tutorial' and the AI does it: shows the whole guide with what they have seen, gives the next tip, or turns the tips off. 'Start the tutorial o"
|
|
9784
|
+
}
|
|
9779
9785
|
]
|
|
9780
9786
|
}
|
|
@@ -79,7 +79,7 @@ ${ident}
|
|
|
79
79
|
- **\`npx bongos ship\`** runs the smoke + grade, then merges and deploys; **\`npx bongos release <id>\`** drops a claim with no credit. Ship or release before you sign off.
|
|
80
80
|
- **The project's ship mode says who starts a ship** — \`npx bongos ship-mode\` prints it. In **Auto** (the default) you run \`npx bongos ship\` yourself when the task is done; never hand the person commands to run. In **Manual** ask the person first, and ship only after they say yes.
|
|
81
81
|
- **Dependencies gate everything** — a task becomes claimable only once every task it depends on has shipped.
|
|
82
|
-
- **Walk new people through the how-to, once.** The first time the person meets one of these moments, run the matching command and pass its few lines on in plain words at the end of your reply: the first prompt of a session — \`npx bongos howto moment first_open\`; their first claim — \`npx bongos howto moment first_claim\`; finishing their first task — \`npx bongos howto moment first_ship\`. It prints nothing once a step is read, so it never repeats (\`npx bongos howto moments\` lists the rest). Claude Code is handed these steps automatically.
|
|
82
|
+
- **Walk new people through the how-to, once.** The first time the person meets one of these moments, run the matching command and pass its few lines on in plain words at the end of your reply: the first prompt of a session — \`npx bongos howto moment first_open\`; their first claim — \`npx bongos howto moment first_claim\`; finishing their first task — \`npx bongos howto moment first_ship\`. It prints nothing once a step is read, so it never repeats (\`npx bongos howto moments\` lists the rest). When they ask to see the how-to, for the next step, or to skip it, run \`npx bongos howto show\`, \`npx bongos howto next\` or \`npx bongos howto skip\`. Claude Code is handed these steps automatically.
|
|
83
83
|
- **Code you write or touch carries a doc-comment** — each module a header, each public function/class a line or two, saying *what* it does and *why* it exists (never how), in the words someone would search for. Agents find code by searching text, so meaning written into the source is what makes it findable.
|
|
84
84
|
|
|
85
85
|
## Where things live
|
package/scripts/gds/howto.js
CHANGED
|
@@ -7,7 +7,18 @@
|
|
|
7
7
|
// prints the person's unread steps for that moment — nothing at all once they are read —
|
|
8
8
|
// and marks them read in Bongos, so no tool shows the same step twice.
|
|
9
9
|
//
|
|
10
|
+
// It is also how a person reopens the guide in plain words (task 1004636): "show me the
|
|
11
|
+
// how-to" → `show` (every step with its read mark and the progress count), "what's next
|
|
12
|
+
// in the tutorial" → `next` (the first unread step, then marked read), "skip the
|
|
13
|
+
// tutorial" → `skip` (every step marked skipped, so no moment shows one again), and
|
|
14
|
+
// "start the tutorial over" → `reset`. The Conductor hook maps those words to these
|
|
15
|
+
// commands for Claude Code; the instance CLAUDE.md names them for other tools.
|
|
16
|
+
//
|
|
10
17
|
// Usage:
|
|
18
|
+
// node scripts/gds/howto.js show the whole guide with read/unread marks + progress
|
|
19
|
+
// node scripts/gds/howto.js next print + mark read the next unread step
|
|
20
|
+
// node scripts/gds/howto.js skip mark every step skipped (the guide goes quiet)
|
|
21
|
+
// node scripts/gds/howto.js reset forget every mark, so the guide starts over
|
|
11
22
|
// node scripts/gds/howto.js moment <moment-id> print + mark read the unread steps of a moment
|
|
12
23
|
// node scripts/gds/howto.js moments list the moment ids and when each applies
|
|
13
24
|
//
|
|
@@ -18,13 +29,78 @@ const { apiCall, requireSession, errCode, cliExit } = require('./cli-lib');
|
|
|
18
29
|
const guide = require('../../modules/onboarding/howto-guide');
|
|
19
30
|
const moments = require('../../modules/onboarding/howto-moments');
|
|
20
31
|
|
|
21
|
-
const USAGE = 'usage: node scripts/gds/howto.js moment <moment-id> | moments';
|
|
32
|
+
const USAGE = 'usage: node scripts/gds/howto.js show | next | skip | reset | moment <moment-id> | moments';
|
|
22
33
|
|
|
23
34
|
// The step ids this person has already read or skipped, from GET /me/howto's answer.
|
|
24
35
|
function readIds(state) {
|
|
25
36
|
return ((state && state.steps) || []).filter((s) => s.state !== 'unread').map((s) => s.id);
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
const MARK = { read: '[x]', skipped: '[-]', unread: '[ ]' };
|
|
40
|
+
|
|
41
|
+
// Pure: the whole guide as the person sees it, from GET /me/howto's answer.
|
|
42
|
+
function formatProgress(state) {
|
|
43
|
+
const byId = new Map(((state && state.steps) || []).map((s) => [s.id, s.state]));
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const m of guide.MOMENTS) {
|
|
46
|
+
out.push(`${m.title}`);
|
|
47
|
+
for (const s of guide.stepsForMoment(m.id)) out.push(` ${MARK[byId.get(s.id) || 'unread']} ${s.title}`);
|
|
48
|
+
}
|
|
49
|
+
const total = guide.STEPS.length;
|
|
50
|
+
const read = (state && state.read) || 0;
|
|
51
|
+
const skipped = (state && state.skipped) || 0;
|
|
52
|
+
const next = guide.nextUnread(readIds(state));
|
|
53
|
+
out.push('', `Progress: ${read} of ${total} read${skipped ? `, ${skipped} skipped` : ''}.`);
|
|
54
|
+
out.push(next ? `Next: ${next.title} — say "what's next in the tutorial".` : 'All done. Say "start the tutorial over" to see it again.');
|
|
55
|
+
return out.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readState() {
|
|
59
|
+
await requireSession();
|
|
60
|
+
const r = await apiCall('GET', '/api/bongos/me/howto');
|
|
61
|
+
if (!r.ok) console.error(`Could not read the how-to progress (${r.status}${errCode(r.data) ? `: ${errCode(r.data)}` : ''}).`);
|
|
62
|
+
return r.ok ? r.data : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function write(route, body, what) {
|
|
66
|
+
const w = await apiCall('POST', `/api/bongos/me/howto/${route}`, body);
|
|
67
|
+
if (!w.ok) console.error(`Could not ${what} (${w.status}${errCode(w.data) ? `: ${errCode(w.data)}` : ''}).`);
|
|
68
|
+
return w.ok;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function show() {
|
|
72
|
+
const state = await readState();
|
|
73
|
+
if (!state) return 1;
|
|
74
|
+
console.log(formatProgress(state));
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function next() {
|
|
79
|
+
const state = await readState();
|
|
80
|
+
if (!state) return 1;
|
|
81
|
+
const step = guide.nextUnread(readIds(state));
|
|
82
|
+
if (!step) { console.log('You have seen every step. Say "start the tutorial over" to see them again.'); return 0; }
|
|
83
|
+
console.log(moments.renderForPerson({ moment: guide.MOMENTS.find((m) => m.id === step.moment), steps: [step] }));
|
|
84
|
+
if (!(await write('read', { steps: [step.id] }, 'mark it read'))) return 1;
|
|
85
|
+
const left = guide.STEPS.length - readIds(state).length - 1;
|
|
86
|
+
console.log(left > 0 ? `(${left} more to go — say "what's next in the tutorial" for the next one.)` : '(That was the last one.)');
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function skip() {
|
|
91
|
+
await requireSession();
|
|
92
|
+
if (!(await write('read', { all: true, how: 'skipped' }, 'skip the guide'))) return 1;
|
|
93
|
+
console.log('Skipped. The how-to will not show any more tips. Say "show me the how-to" any time to look at it, or "start the tutorial over" to bring the tips back.');
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function reset() {
|
|
98
|
+
await requireSession();
|
|
99
|
+
if (!(await write('reset', {}, 'start the guide over'))) return 1;
|
|
100
|
+
console.log('Done. The how-to starts over: each tip will show again at its moment.');
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
28
104
|
async function showMoment(id) {
|
|
29
105
|
if (!guide.MOMENT_IDS.has(id)) {
|
|
30
106
|
console.error(`unknown moment "${id}" — one of: ${[...guide.MOMENT_IDS].join(', ')}`);
|
|
@@ -52,6 +128,10 @@ async function main(argv) {
|
|
|
52
128
|
return 0;
|
|
53
129
|
}
|
|
54
130
|
if (cmd === 'moment' && arg) return showMoment(String(arg).trim());
|
|
131
|
+
if (cmd === 'show') return show();
|
|
132
|
+
if (cmd === 'next') return next();
|
|
133
|
+
if (cmd === 'skip') return skip();
|
|
134
|
+
if (cmd === 'reset') return reset();
|
|
55
135
|
console.error(USAGE);
|
|
56
136
|
return 2;
|
|
57
137
|
}
|
|
@@ -62,4 +142,4 @@ if (require.main === module) {
|
|
|
62
142
|
.catch((err) => { console.error('fatal:', err && err.message ? err.message : err); cliExit(1); });
|
|
63
143
|
}
|
|
64
144
|
|
|
65
|
-
module.exports = { main, readIds };
|
|
145
|
+
module.exports = { main, readIds, formatProgress };
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.130'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
@@ -49,7 +49,7 @@ function mePayload({ discipline = 'engineer' } = {}) {
|
|
|
49
49
|
// Run the hook once in a throwaway HOME whose session points at `base`.
|
|
50
50
|
// `worktree` names a git repo created under the fake home for the hook to stand
|
|
51
51
|
// in — its basename is what binds a claim to "this" tree (task 1002992).
|
|
52
|
-
async function runHook(me, { worktree = null } = {}) {
|
|
52
|
+
async function runHook(me, { worktree = null, prompts = ['hello'] } = {}) {
|
|
53
53
|
const server = http.createServer((req, res) => {
|
|
54
54
|
if (req.url.startsWith('/api/bongos/me')) { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(me)); return; }
|
|
55
55
|
res.writeHead(404); res.end('{}');
|
|
@@ -70,14 +70,22 @@ async function runHook(me, { worktree = null } = {}) {
|
|
|
70
70
|
fs.mkdirSync(cwd, { recursive: true });
|
|
71
71
|
execFileSync('git', ['init', '-q'], { cwd, stdio: 'ignore' });
|
|
72
72
|
}
|
|
73
|
+
// Several prompts run one after another in ONE session, sharing the hook's cache —
|
|
74
|
+
// what the per-session repeat filter sees (task 1004636).
|
|
75
|
+
const sessionId = 'e2e-' + Date.now();
|
|
73
76
|
try {
|
|
74
|
-
const
|
|
75
|
-
let
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
const outs = [];
|
|
78
|
+
let err = ''; let code = 0;
|
|
79
|
+
for (const prompt of prompts) {
|
|
80
|
+
const child = spawn(process.execPath, [HOOK], { env, cwd, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
81
|
+
let out = '';
|
|
82
|
+
child.stdout.on('data', (d) => { out += d; });
|
|
83
|
+
child.stderr.on('data', (d) => { err += d; });
|
|
84
|
+
child.stdin.end(JSON.stringify({ session_id: sessionId, prompt, cwd }));
|
|
85
|
+
code = await new Promise((r) => child.on('close', r));
|
|
86
|
+
outs.push(out);
|
|
87
|
+
}
|
|
88
|
+
return { out: outs[outs.length - 1], outs, err, code };
|
|
81
89
|
} finally {
|
|
82
90
|
await new Promise((r) => server.close(r));
|
|
83
91
|
fs.rmSync(home, { recursive: true, force: true });
|
|
@@ -149,4 +157,10 @@ await test('a project with no laws sends no laws block', async () => {
|
|
|
149
157
|
assert.ok(r.out.includes(INTERACTION), 'and the rest of the hook still runs');
|
|
150
158
|
});
|
|
151
159
|
|
|
160
|
+
await test('asking for the next how-to step twice in one session reaches the AI both times (task 1004636)', async () => {
|
|
161
|
+
const r = await runHook(mePayload(), { prompts: ["what's next in the tutorial", "what's next in the tutorial"] });
|
|
162
|
+
assert.equal(r.outs.length, 2);
|
|
163
|
+
for (const [i, out] of r.outs.entries()) assert.ok(out.includes('npx bongos howto next'), `ask ${i + 1} was swallowed; stdout: ${JSON.stringify(out.slice(0, 200))}`);
|
|
164
|
+
});
|
|
165
|
+
|
|
152
166
|
summary();
|
package/tests/howto_guide.mjs
CHANGED
|
@@ -66,6 +66,18 @@ test('steps are short and in plain language (no jargon)', () => {
|
|
|
66
66
|
assert.deepEqual(hits, []);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
+
test('every `npx bongos <verb>` a step names is a real bongos verb', () => {
|
|
70
|
+
const { VERBS } = require('../bin/bongos.js');
|
|
71
|
+
let seen = 0;
|
|
72
|
+
for (const s of guide.STEPS) {
|
|
73
|
+
for (const m of s.try.matchAll(/npx bongos ([a-z][a-z-]*)/g)) {
|
|
74
|
+
seen++;
|
|
75
|
+
assert.ok(Object.prototype.hasOwnProperty.call(VERBS, m[1]), `${s.id}: "npx bongos ${m[1]}" is not a verb in bin/bongos.js`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
assert.ok(seen > 0, 'the guide names no bongos verbs — has the check stopped matching?');
|
|
79
|
+
});
|
|
80
|
+
|
|
69
81
|
test('every slash command a step names is a core skill an instance gets', async () => {
|
|
70
82
|
const fs = await import('node:fs');
|
|
71
83
|
const { isPublishable } = require('../scripts/gds/publish-manifest.js');
|
package/tests/howto_moments.mjs
CHANGED
|
@@ -17,7 +17,7 @@ const m = require('../modules/onboarding/howto-moments.js');
|
|
|
17
17
|
const { howtoAdvisory, evaluateTriggers } = require('../.claude/hooks/conductor.js');
|
|
18
18
|
const { VERBS } = require('../bin/bongos.js');
|
|
19
19
|
const { renderInstanceClaudeMd } = require('../scripts/gds/claude-md-template.js');
|
|
20
|
-
const { readIds } = require('../scripts/gds/howto.js');
|
|
20
|
+
const { readIds, formatProgress } = require('../scripts/gds/howto.js');
|
|
21
21
|
|
|
22
22
|
let passed = 0;
|
|
23
23
|
let failed = 0;
|
|
@@ -97,8 +97,55 @@ test('the AI is told to say it once, briefly, and that it is marked read', () =>
|
|
|
97
97
|
assert.match(text, /marked read/);
|
|
98
98
|
});
|
|
99
99
|
|
|
100
|
+
console.log('\nasking for the guide itself (task 1004636):');
|
|
101
|
+
|
|
102
|
+
test('the three plain-word requests, and starting over, are recognised', () => {
|
|
103
|
+
const cases = {
|
|
104
|
+
'show me the how-to': 'show',
|
|
105
|
+
'Can I see my tutorial progress?': 'show',
|
|
106
|
+
"what's next in the tutorial": 'next',
|
|
107
|
+
'continue the tutorial': 'next',
|
|
108
|
+
'skip the tutorial': 'skip',
|
|
109
|
+
'please stop showing me the tutorial': 'skip',
|
|
110
|
+
'turn the tips off': 'skip',
|
|
111
|
+
'start the tutorial over': 'reset',
|
|
112
|
+
};
|
|
113
|
+
for (const [prompt, kind] of Object.entries(cases)) assert.equal(m.guideRequest(prompt), kind, prompt);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('ordinary prompts are not guide requests', () => {
|
|
117
|
+
for (const p of ['show me the login page', 'what is next on the roadmap', 'skip the failing test', 'ship it', 'show me the style guide', 'add tips for the header']) {
|
|
118
|
+
assert.equal(m.guideRequest(p), null, p);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('each request tells the AI the command that changes the stored state', () => {
|
|
123
|
+
for (const kind of ['show', 'next', 'skip', 'reset']) assert.match(m.renderRequestForAi(kind), new RegExp(`npx bongos howto ${kind}`));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('the full guide shows read, skipped and unread marks, the progress, and what is next', () => {
|
|
127
|
+
const out = formatProgress({ steps: [{ id: 'welcome', state: 'read' }, { id: 'the_hall', state: 'skipped' }], read: 1, skipped: 1 });
|
|
128
|
+
assert.match(out, /\[x\] What Bongos is/);
|
|
129
|
+
assert.match(out, /\[-\] The hall/);
|
|
130
|
+
assert.match(out, /\[ \] Where to start/);
|
|
131
|
+
assert.match(out, new RegExp(`Progress: 1 of ${guide.STEPS.length} read, 1 skipped`));
|
|
132
|
+
assert.match(out, /Next: Where to start/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('with every step done, the guide says so and how to start over', () => {
|
|
136
|
+
const out = formatProgress({ steps: guide.STEPS.map((s) => ({ id: s.id, state: 'read' })), read: guide.STEPS.length });
|
|
137
|
+
assert.match(out, /All done/);
|
|
138
|
+
});
|
|
139
|
+
|
|
100
140
|
console.log('\nthe Conductor:');
|
|
101
141
|
|
|
142
|
+
test('a guide request becomes the advisory even with no read state, and replaces the moment tip', () => {
|
|
143
|
+
const a = howtoAdvisory({ howto: { read: [] }, prompt: 'skip the tutorial', firstPrompt: true, activeClaims: [] });
|
|
144
|
+
assert.equal(a.id, 'howto-request:skip');
|
|
145
|
+
assert.equal(a.howtoSteps, undefined, 'a request marks nothing itself — the command does');
|
|
146
|
+
assert.equal(howtoAdvisory({ howto: null, prompt: 'show me the how-to' }).id, 'howto-request:show');
|
|
147
|
+
});
|
|
148
|
+
|
|
102
149
|
test('no read state (offline, signed out, no onboarding module) → no how-to advisory', () => {
|
|
103
150
|
assert.equal(howtoAdvisory({ howto: null, firstPrompt: true, activeClaims: [] }), null);
|
|
104
151
|
});
|