@bongos/core 1.21.24 → 1.21.26

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,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.24",
6
- "core_contract": "1.21.24",
7
- "source_commit": "9ed14f470c9244d95d36c136ddb7ab5353ce8578",
5
+ "core_version": "1.21.26",
6
+ "core_contract": "1.21.26",
7
+ "source_commit": "a0fe60e1a8c588d4e9913bcdffda9edae3267a4a",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-10-03T00:06:14.383Z",
9
+ "built_at": "2026-10-03T02:56:29.128Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 580,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 3506,
20
- "tree_sha256": "7a3a980adf2820c753d32bc3dcba9eb7b3b6471b6bf0a46db4a45b583fe2b888",
20
+ "tree_sha256": "bf858332458c31c371fdb0605eb745807f1f5ae5b4c42f85c6b015f7a7a36a4d",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -192,7 +192,7 @@
192
192
  {
193
193
  "path": ".claude/skills/idea-triage/SKILL.md",
194
194
  "mode": "0000644",
195
- "sha256": "d475e4bf8cb1b3a21f7778e5b036277a665cf497f604630160a2ec3eb5924b65"
195
+ "sha256": "628a9880309ba370151becbebd7497972006716ec955d06bb51a79d335dbd54f"
196
196
  },
197
197
  {
198
198
  "path": ".claude/skills/merge-mode/SKILL.md",
@@ -2832,7 +2832,7 @@
2832
2832
  {
2833
2833
  "path": "docs/module-api-changelog.md",
2834
2834
  "mode": "0000644",
2835
- "sha256": "92dde1422bdcca1dce6e857c712af4a8474a9140424681225445428783035bf8"
2835
+ "sha256": "8b98f13ef9c22f6d73ab2065a1563410301cb24edcc3d83b81a34f453a814c6c"
2836
2836
  },
2837
2837
  {
2838
2838
  "path": "docs/modules-contract.md",
@@ -9787,12 +9787,12 @@
9787
9787
  {
9788
9788
  "path": "package-lock.json",
9789
9789
  "mode": "0000644",
9790
- "sha256": "0d93e33c8e66223f3566f9f89fc3b2f22d81942b74bdae8e8ab5db7c057c8f66"
9790
+ "sha256": "b89ac1d6344bc2814145aee2e1a10552fb7bc4b24da346f4dcca305f47a355b7"
9791
9791
  },
9792
9792
  {
9793
9793
  "path": "package.json",
9794
9794
  "mode": "0000644",
9795
- "sha256": "ee3839fcdf18e5fd982b6a2d1f3bc55459125e13f27bfe19aef411556ef7006b"
9795
+ "sha256": "13606c58a4cce9dbc00d018a3f3067e43d0b9d1ee553fb6069ed9b7493166659"
9796
9796
  },
9797
9797
  {
9798
9798
  "path": "public-docs/index.html",
@@ -9812,7 +9812,7 @@
9812
9812
  {
9813
9813
  "path": "release-notes.json",
9814
9814
  "mode": "0000644",
9815
- "sha256": "83458aeed8dc40040956f48a6670b30cea746cfbfcc62f6baf3db3201f537bf8"
9815
+ "sha256": "270e78b9cc46481cf3aa2fb599bcbdc4bca8e4c0f5876371861e68610353f537"
9816
9816
  },
9817
9817
  {
9818
9818
  "path": "scripts/bongos-mcp.js",
@@ -11462,7 +11462,7 @@
11462
11462
  {
11463
11463
  "path": "scripts/gds/triage.js",
11464
11464
  "mode": "0000644",
11465
- "sha256": "a4af12591b829f9b89a5768858e8a2ba02a44d7e7ebd848e945e77f95fc55da7"
11465
+ "sha256": "0e03060897a7d708ad54936d9942e130be4c2f22be01821ab98e7cf30ee4ef11"
11466
11466
  },
11467
11467
  {
11468
11468
  "path": "scripts/gds/turn-end-sound.js",
@@ -12037,7 +12037,7 @@
12037
12037
  {
12038
12038
  "path": "src/module-api.js",
12039
12039
  "mode": "0000644",
12040
- "sha256": "278f3e5cee7b2d4b784aa79cdef9fe7570cc5bede2208dbe8c54f16095dba7f9"
12040
+ "sha256": "91b122a9d3facf064489524a4ea1fa610466b29b855c98841de21c0e4c05e042"
12041
12041
  },
12042
12042
  {
12043
12043
  "path": "src/module-loader/catalog.js",
@@ -14717,7 +14717,7 @@
14717
14717
  {
14718
14718
  "path": "tests/idea_triage.mjs",
14719
14719
  "mode": "0000644",
14720
- "sha256": "836ee90dd24736c84fede53394d0d724915f73540c9b99c41762da1d37c447b2"
14720
+ "sha256": "6960c9628f6b41ef78db6d09bcbb00ca4afb6b93c4bf57a6c39e86f6e09c4c66"
14721
14721
  },
14722
14722
  {
14723
14723
  "path": "tests/idea_triage_grade.mjs",
@@ -58,7 +58,7 @@ Bug reports from Discord have their own channel, `#bugs`, which does route.
58
58
  rather than just promoting past: it usually means the filer named no goal, and the
59
59
  capture-time suggester (R08/R09/R11 surfaces) is where the fix belongs.
60
60
 
61
- > **This skill is no longer the only way to triage.** Since task 1002447 a Metic+ builder can promote, discard and merge from the **hall** — the Home "Needs your attention" card's idea rows each carry a *Triage* verb that opens the record overlay, where all three forms live. No Claude session involved. What this skill adds over the hall is the **parallel development pass** (Step 1.5) — an idea fleshed out into open questions/risks before the verdict — so reach for it when the queue needs judgment, and use the hall when it just needs clearing.
61
+ > **This skill is no longer the only way to triage.** Since task 1002447 a Metic+ builder can promote, discard and merge from the **hall** — the Home "Needs your attention" card's idea rows each carry a *Triage* verb that opens the record overlay, where all three forms live. No Claude session involved. What this skill adds over the hall is the **parallel development pass** (Step 1.5b) — an idea fleshed out into open questions/risks before the verdict — so reach for it when the queue needs judgment, and use the hall when it just needs clearing.
62
62
 
63
63
  ## Rank gate — Metic+ only
64
64
 
@@ -119,7 +119,7 @@ show the same idea the same way.
119
119
 
120
120
  **Step 1.5a — batch near-duplicate ideas BEFORE developing anything (task 1001465).**
121
121
 
122
- Run `clusterIdeas` (`scripts/gds/triage.js` — pure, no I/O): it groups open quick/spark ideas that share a `kind` and a high title/body word overlap. For each cluster it returns, offer the user ONE decision covering the whole group instead of walking each member — this is the actual fix for "triage costs a ton of credits" (idea 377): a backlog of near-identical asks used to cost one full development + verdict PER idea, and now costs one:
122
+ Run `clusterIdeas` (`scripts/gds/triage.js` — pure, no I/O): it groups open quick ideas (not Full Ideas, not sparks) that share a `kind` and a high title/body word overlap. For each cluster it returns, offer the user ONE decision covering the whole group instead of walking each member — this is the actual fix for "triage costs a ton of credits" (idea 377): a backlog of near-identical asks used to cost one full development + verdict PER idea, and now costs one:
123
123
 
124
124
  > Cluster of 3 likely duplicates: #101 "Add dark mode toggle", #145 "Dark mode support", #203 "Support a dark theme". **Keep #101 and merge the rest, discard all, walk them individually, or skip?**
125
125
 
@@ -165,7 +165,7 @@ Present each idea with:
165
165
  - title
166
166
  - body_md (full)
167
167
  - captured_at + captured_by
168
- - **its developed block** (from Step 1.5): summary, integration surfaces, open questions, challenges, risks, rough effort, and the subagent's recommendation
168
+ - **its developed block** (from Step 1.5b): summary, integration surfaces, open questions, challenges, risks, rough effort, and the subagent's recommendation
169
169
 
170
170
  Then ask the user (concise prompt — Lars is an EE, not a software engineer; frame in product/outcome terms). Lead with the developed open questions, since those are what the Archon actually needs to decide:
171
171
 
@@ -2875,5 +2875,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
2875
2875
  landed since 1.21.22 with no explicit bump. run 37078725783. (task 1002620)
2876
2876
  1.21.24 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2877
2877
  landed since 1.21.23 with no explicit bump. run 37080562697. (task 1002620)
2878
+ 1.21.25 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2879
+ landed since 1.21.24 with no explicit bump. run 37089254828. (task 1002620)
2880
+ 1.21.26 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2881
+ landed since 1.21.25 with no explicit bump. run 37091481670. (task 1002620)
2878
2882
  ---------------------------------------------------------------------------
2879
2883
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.21.24",
3
+ "version": "1.21.26",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.21.24",
9
+ "version": "1.21.26",
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.24",
3
+ "version": "1.21.26",
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",
@@ -8900,5 +8900,17 @@
8900
8900
  "id": "1002830",
8901
8901
  "text": "Sessions now has a Read button on each row that opens the full conversation for that session. Only the session's owner or an administrator can read it, and administrator reads are logged."
8902
8902
  }
8903
+ ],
8904
+ "1.21.25": [
8905
+ {
8906
+ "id": "1004533",
8907
+ "text": "Twelve goal 1000045 tasks that were counted toward other goals' criteria now count toward their own, so that goal's progress reads true."
8908
+ }
8909
+ ],
8910
+ "1.21.26": [
8911
+ {
8912
+ "id": "1003623",
8913
+ "text": "Fixed idea-triage cluster handling: 'keep #N' and 'merge #N' now parse; skipped clusters count toward the summary; spark ideas excluded from batch clustering; stale Step 1.5 refs now point at 1.5b. Tests added in tests/idea_tr"
8914
+ }
8903
8915
  ]
8904
8916
  }
@@ -400,10 +400,12 @@ function clusterRecords(records, { textOf, kindOf, timeOf, threshold = 0.34, min
400
400
 
401
401
  // The idea-shaped call: Full Ideas are never eligible (they get their own
402
402
  // shape recommendation, SKILL.md Step 2.5, and batching one away would skip
403
- // that judgment), clustered on title+body_md / kind / captured_at. Same
404
- // signature and behavior as before the clusterRecords extraction.
403
+ // that judgment), nor are sparks (not triage's to convert, SKILL.md); the rest
404
+ // cluster on title+body_md / kind / captured_at. Same signature as before the
405
+ // clusterRecords extraction.
406
+ const NON_CLUSTER_GRADES = new Set(['full', 'spark']);
405
407
  function clusterIdeas(ideas, { threshold = 0.34, minShared = 2 } = {}) {
406
- const eligible = ideas.filter((i) => (i.grade || 'quick') !== 'full');
408
+ const eligible = ideas.filter((i) => !NON_CLUSTER_GRADES.has(i.grade || 'quick'));
407
409
  return clusterRecords(eligible, {
408
410
  textOf: (i) => `${i.title || ''} ${i.body_md || ''}`,
409
411
  kindOf: (i) => i.kind || null,
@@ -431,7 +433,7 @@ async function handleCluster(rl, cluster, clusteredIds, state) {
431
433
  while (!handled) {
432
434
  const ans = (await prompt(rl, ` [k]eep #${cluster[0].id} & merge the rest / [d]iscard all / [i]ndividually / [s]kip cluster > `)).trim().toLowerCase();
433
435
 
434
- if (ans === 's' || ans === 'skip' || ans === '') { handled = true; break; }
436
+ if (ans === 's' || ans === 'skip' || ans === '') { state.summary.skipped += cluster.length; handled = true; break; }
435
437
 
436
438
  if (ans === 'i' || ans === 'individually') {
437
439
  for (const m of cluster) clusteredIds.delete(m.id);
@@ -458,7 +460,7 @@ async function handleCluster(rl, cluster, clusteredIds, state) {
458
460
  handled = true; break;
459
461
  }
460
462
 
461
- if (ans === 'k' || ans === 'keep' || ans.startsWith('k ') || ans.startsWith('k#')) {
463
+ if (/^k(eep)?(\s|#|$)/.test(ans)) {
462
464
  let canonicalId = cluster[0].id;
463
465
  const inline = ans.replace(/^k(eep)?(\s|#)*/, '').trim();
464
466
  if (inline) {
@@ -535,6 +537,9 @@ async function main() {
535
537
  await handleCluster(rl, cluster, clusteredIds, state);
536
538
  }
537
539
  }
540
+ // Cluster verdicts bump the summary too, but those ideas are already out of
541
+ // `ideas`; remember the tally so "unseen" only counts the individual walk.
542
+ const clusterHandled = summary.promoted + summary.discarded + summary.merged + summary.skipped;
538
543
  ideas = ideas.filter((i) => !clusteredIds.has(i.id));
539
544
  if (ideas.length === 0) {
540
545
  console.log('\nnothing left to walk individually — every open idea was resolved in a batch cluster verdict.');
@@ -593,10 +598,10 @@ async function main() {
593
598
  break;
594
599
  }
595
600
 
596
- if (ans === 'm' || ans === 'merge' || ans.startsWith('m ') || ans.startsWith('m#')) {
601
+ if (/^m(erge)?(\s|#|$)/.test(ans)) {
597
602
  // Accept "m 12" or "m #12" inline, otherwise prompt.
598
603
  let target = null;
599
- const inline = ans.replace(/^m(\s|#)*/, '').trim();
604
+ const inline = ans.replace(/^m(erge)?(\s|#)*/, '').trim();
600
605
  if (inline) target = Number(inline.replace(/^#/, ''));
601
606
  if (!Number.isFinite(target) || target <= 0) {
602
607
  const a = (await prompt(rl, ' merge into idea #: ')).trim().replace(/^#/, '');
@@ -700,7 +705,7 @@ async function main() {
700
705
  rl.close();
701
706
  }
702
707
 
703
- const walked = summary.promoted + summary.discarded + summary.merged + summary.skipped;
708
+ const walked = summary.promoted + summary.discarded + summary.merged + summary.skipped - clusterHandled;
704
709
  const remaining = ideas.length - walked;
705
710
 
706
711
  console.log('\n' + '═'.repeat(72));
@@ -730,6 +735,7 @@ module.exports = {
730
735
  loadDevelopment,
731
736
  formatDevelopment,
732
737
  clusterIdeas,
738
+ handleCluster,
733
739
  clusterRecords,
734
740
  significantWords,
735
741
  jaccard,
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.24'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.21.26'; // 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');
@@ -18,6 +18,7 @@
18
18
  // Run: node tests/idea_triage.mjs
19
19
 
20
20
  import { strict as assert } from 'node:assert';
21
+ import fs from 'node:fs';
21
22
  import { createRequire } from 'node:module';
22
23
 
23
24
  const require = createRequire(import.meta.url);
@@ -162,5 +163,44 @@ await test('listOpenIdeas selects the triage-provenance columns', async () => {
162
163
  assert.match(select.sql, /triage_note/);
163
164
  });
164
165
 
166
+ // ── task 1003623: batch-cluster follow-ups ─────────────────────────────────
167
+ const triage = require('../scripts/gds/triage.js');
168
+ const fakeRl = (answers) => {
169
+ const asked = [];
170
+ return { asked, question: (q, cb) => { asked.push(q); cb(answers.shift() ?? 's'); } };
171
+ };
172
+ const pair = [
173
+ { id: 1, title: 'a', kind: 'bug' },
174
+ { id: 2, title: 'b', kind: 'bug' },
175
+ ];
176
+ const freshState = () => ({ summary: { promoted: 0, discarded: 0, merged: 0, skipped: 0 }, discardedIds: [], mergedIds: [] });
177
+
178
+ await test('cluster verdict accepts the natural "keep #N" phrasing', async () => {
179
+ // A one-member cluster keeps the verdict free of API writes: nothing to merge.
180
+ const rl = fakeRl(['keep #1', 's']);
181
+ const state = freshState();
182
+ await triage.handleCluster(rl, [pair[0]], new Set([1]), state);
183
+ assert.equal(rl.asked.length, 1, 'keep #1 was understood, not re-prompted as unknown');
184
+ assert.equal(state.summary.skipped, 0);
185
+ });
186
+
187
+ await test('skipping a cluster counts every member as skipped', async () => {
188
+ const rl = fakeRl(['s']);
189
+ const state = freshState();
190
+ await triage.handleCluster(rl, pair, new Set([1, 2]), state);
191
+ assert.equal(state.summary.skipped, 2);
192
+ });
193
+
194
+ await test('spark-grade ideas are not eligible for batch clustering', async () => {
195
+ const mk = (id, grade) => ({ id, grade, kind: 'idea', title: 'dashboard filter export widget', body_md: 'dashboard filter export widget', captured_at: '2026-01-01' });
196
+ assert.equal(triage.clusterIdeas([mk(1, 'spark'), mk(2, 'spark')]).clusters.length, 0);
197
+ assert.equal(triage.clusterIdeas([mk(1, 'quick'), mk(2, 'quick')]).clusters.length, 1);
198
+ });
199
+
200
+ await test('SKILL.md has no stale bare "Step 1.5" cross-references', async () => {
201
+ const md = fs.readFileSync(new URL('../.claude/skills/idea-triage/SKILL.md', import.meta.url), 'utf8');
202
+ assert.ok(!/Step 1\.5(?![ab])/.test(md));
203
+ });
204
+
165
205
  console.log(`\nIdea triage tests: ${passed} passed, ${failed} failed`);
166
206
  process.exit(failed === 0 ? 0 : 1);