@bongos/core 1.19.549 → 1.19.551

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.19.549",
6
- "core_contract": "1.19.549",
7
- "source_commit": "a554c51b4ea9134b53e0f99617a051c65cf1fb34",
5
+ "core_version": "1.19.551",
6
+ "core_contract": "1.19.551",
7
+ "source_commit": "de0b6c99753fd502f2356bcfb6bf338227445cc7",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-04T18:37:41.417Z",
9
+ "built_at": "2026-09-04T20:39:18.276Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
- "docs_redacted": 441,
12
+ "docs_redacted": 442,
13
13
  "agent_docs_stubbed": 24,
14
- "functional_verbatim": 2013,
14
+ "functional_verbatim": 2018,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 2478,
20
- "tree_sha256": "9cee93593f3443ba78ae1c00bb5cd092008ed33ee8c024f4e61a7d17d67bffb5",
19
+ "file_count": 2484,
20
+ "tree_sha256": "38fea0008aa898d10d6e7388c30a7d422be81dcde8d201172848e551d72d361b",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -29,6 +29,11 @@
29
29
  "mode": "0000644",
30
30
  "sha256": "90de8b32fb4dac69d6e5c781d78489c36c40f00c4b07f062c0650cff08052203"
31
31
  },
32
+ {
33
+ "path": ".claude/skills/bug-triage/SKILL.md",
34
+ "mode": "0000644",
35
+ "sha256": "85481aea23304149520f1adcb7e2e8ab05b655c269dd2e38a6c04922013e9202"
36
+ },
32
37
  {
33
38
  "path": ".claude/skills/builder-backup/SKILL.md",
34
39
  "mode": "0000644",
@@ -177,7 +182,7 @@
177
182
  {
178
183
  "path": ".claude/skills/idea-triage/SKILL.md",
179
184
  "mode": "0000644",
180
- "sha256": "b64fe42bb2a542304802b48bea348ae817608ac47f026d98c4de9bea5aa90174"
185
+ "sha256": "2519755269ff2d55e3c060569f7d16c440428ba005d206a3bf12fe2275830285"
181
186
  },
182
187
  {
183
188
  "path": ".claude/skills/ideate/SKILL.md",
@@ -367,12 +372,12 @@
367
372
  {
368
373
  "path": "clients/bongos-client/README.md",
369
374
  "mode": "0000644",
370
- "sha256": "b1abf8c13f89ba8fe2ebca540d1a8b22fac0ad293595e51f464e189dfaf1a1ad"
375
+ "sha256": "262491824b5d7b6005c69d8190d50f450320f02dc1333dc7ae029616aff9f018"
371
376
  },
372
377
  {
373
378
  "path": "clients/bongos-client/bongos-client.global.js",
374
379
  "mode": "0000644",
375
- "sha256": "1178634ee16c7b1a83defe0c6ece5754a92901370ce18f114a84d3922f7065ca"
380
+ "sha256": "fd5d716ec3ba7fbdb9fdaee076ef89048424248139ef7d4ea51c3983866dea90"
376
381
  },
377
382
  {
378
383
  "path": "clients/bongos-client/examples/hello-world.mjs",
@@ -382,17 +387,17 @@
382
387
  {
383
388
  "path": "clients/bongos-client/index.cjs",
384
389
  "mode": "0000644",
385
- "sha256": "be9e267735c41c695157c01fb82efb9219c2f6769febfa46495395d6dcc887ec"
390
+ "sha256": "17753bb744e592f7d7871d33399536d9938b1b7d6c6249959bcbb4355688b557"
386
391
  },
387
392
  {
388
393
  "path": "clients/bongos-client/index.d.ts",
389
394
  "mode": "0000644",
390
- "sha256": "73e1e22f18262eb0cb73edfe58aef773f802bae33b68b9ca2e4430403d03d7c9"
395
+ "sha256": "1b28c414b039d21ab8105d1687cf6d75cde078238ac84b920885772cd5e9bdbf"
391
396
  },
392
397
  {
393
398
  "path": "clients/bongos-client/index.mjs",
394
399
  "mode": "0000644",
395
- "sha256": "f9c26f336f8b7808ed1c07e844191abd0de7bc39657dda5d0363f5d472b6c2c4"
400
+ "sha256": "779dc3d8970036513a0f8da8e4846bd6c0d2d4714de38390b14ede249e75a3f6"
396
401
  },
397
402
  {
398
403
  "path": "clients/bongos-client/package.json",
@@ -1767,12 +1772,12 @@
1767
1772
  {
1768
1773
  "path": "docs/api-reference.md",
1769
1774
  "mode": "0000644",
1770
- "sha256": "aef38c3ecd8942f0a38c56cbb390fd3590e785634d24e95660900a5140636b5f"
1775
+ "sha256": "5ec1ca7cb551994a31d644eeebd77795955bac39e6389feaf6e6a5ec0217c911"
1771
1776
  },
1772
1777
  {
1773
1778
  "path": "docs/api/openapi.json",
1774
1779
  "mode": "0000644",
1775
- "sha256": "923202b89890cc048769c8b05087181fa06ff126aa80aeaa4d9f669f4d96c27e"
1780
+ "sha256": "40d863b7b46cc3691298f9e48c254cda3b7f4ab23bb008712a55e75764a69dd1"
1776
1781
  },
1777
1782
  {
1778
1783
  "path": "docs/architecture.md",
@@ -2637,7 +2642,7 @@
2637
2642
  {
2638
2643
  "path": "docs/file-map.md",
2639
2644
  "mode": "0000644",
2640
- "sha256": "6085fdda80f0c961067a044b890358fa148415638854029e63f764e262594aeb"
2645
+ "sha256": "e7c193b1cca2fc3f59742e95f921fc65096a32fbbcfba16e4a3950448293a7ba"
2641
2646
  },
2642
2647
  {
2643
2648
  "path": "docs/handoff-template.md",
@@ -2647,7 +2652,7 @@
2647
2652
  {
2648
2653
  "path": "docs/module-api-changelog.md",
2649
2654
  "mode": "0000644",
2650
- "sha256": "c4f07821d5cae7bacdc51e2d7a0f0c5672beb7ba623069c9b4f88e565d8f77b6"
2655
+ "sha256": "1389ff20919a19aef2fbff28102f596f0ca168c34bc4c4114cf557bbf52627a5"
2651
2656
  },
2652
2657
  {
2653
2658
  "path": "docs/modules-contract.md",
@@ -3644,6 +3649,11 @@
3644
3649
  "mode": "0000644",
3645
3650
  "sha256": "8ae32be783a4ca41637f56626bd83375aa4ae0755e701a0c0170eada8d8303b8"
3646
3651
  },
3652
+ {
3653
+ "path": "migrations/core_232_task_merge_provenance.sql",
3654
+ "mode": "0000644",
3655
+ "sha256": "5ed2ff8117e6650de621a1576062b27f2d6e84faf8860f9cb2aed45323a85aa6"
3656
+ },
3647
3657
  {
3648
3658
  "path": "modules/agents/lib/validate.js",
3649
3659
  "mode": "0000644",
@@ -4042,7 +4052,7 @@
4042
4052
  {
4043
4053
  "path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
4044
4054
  "mode": "0000644",
4045
- "sha256": "be9e267735c41c695157c01fb82efb9219c2f6769febfa46495395d6dcc887ec"
4055
+ "sha256": "17753bb744e592f7d7871d33399536d9938b1b7d6c6249959bcbb4355688b557"
4046
4056
  },
4047
4057
  {
4048
4058
  "path": "modules/dev-box/app/test/unit.mjs",
@@ -4282,7 +4292,7 @@
4282
4292
  {
4283
4293
  "path": "modules/government/catalog.js",
4284
4294
  "mode": "0000644",
4285
- "sha256": "913b7ab33c6785903376b0bf7804d7d8d2db620112b983ae4c0368f33a6b8c0f"
4295
+ "sha256": "57b29298fa5707d3a1d59e78612d2b039b1559d26b8a9116812d5c76e9c3af87"
4286
4296
  },
4287
4297
  {
4288
4298
  "path": "modules/government/config.js",
@@ -4384,6 +4394,11 @@
4384
4394
  "mode": "0000644",
4385
4395
  "sha256": "2b729acbc94c933ff2558d59e3cb811e7725450f0d646f9d9bc89209c1340d0f"
4386
4396
  },
4397
+ {
4398
+ "path": "modules/government/migrations/government_011_task_merge_metic.sql",
4399
+ "mode": "0000644",
4400
+ "sha256": "ccd2cd14103bae00957227d037d41f76adaaddc639bd09808f244b0bc514e702"
4401
+ },
4387
4402
  {
4388
4403
  "path": "modules/government/module.json",
4389
4404
  "mode": "0000644",
@@ -5437,7 +5452,7 @@
5437
5452
  {
5438
5453
  "path": "modules/lifecycle/db-ship.js",
5439
5454
  "mode": "0000644",
5440
- "sha256": "d300392b7bf802d00ae79c55ad3b0d9413dc9bb56e7e1105f49656b39abb874a"
5455
+ "sha256": "e48d877a5951f02153c7384217421b2b04a3a1fed215b61ac41fc5b0b8355ded"
5441
5456
  },
5442
5457
  {
5443
5458
  "path": "modules/lifecycle/db-tasks.js",
@@ -5452,7 +5467,7 @@
5452
5467
  {
5453
5468
  "path": "modules/lifecycle/db.js",
5454
5469
  "mode": "0000644",
5455
- "sha256": "09d40dd2eaae47c4620993ad3db6a6db2d880cc499724d31ceff29604960fd64"
5470
+ "sha256": "df5de732d9484e1bcf801c657c78b618d1a0353a912cdb1ee4fdd2c9f121749c"
5456
5471
  },
5457
5472
  {
5458
5473
  "path": "modules/lifecycle/dead-deps.js",
@@ -5682,7 +5697,7 @@
5682
5697
  {
5683
5698
  "path": "modules/lifecycle/routes/tasks.js",
5684
5699
  "mode": "0000644",
5685
- "sha256": "0ba020342e32eb552532a3a34a57b19aa4bbe87f0cb89ace0c45f4c301d7a015"
5700
+ "sha256": "366cb9c60952539ab8a946f8cf757754163b6cd6764a6430777b4529c86c6a75"
5686
5701
  },
5687
5702
  {
5688
5703
  "path": "modules/lifecycle/routes/versions.js",
@@ -7452,12 +7467,12 @@
7452
7467
  {
7453
7468
  "path": "package-lock.json",
7454
7469
  "mode": "0000644",
7455
- "sha256": "8a3f8bc746c2704c4859a36a3f365d6c24305019ba6270aa2c25abbd6bba1bef"
7470
+ "sha256": "c71d59c13b87d9ffbd6c792eb56b26eb434704e855aed3afdfc58914dba02e29"
7456
7471
  },
7457
7472
  {
7458
7473
  "path": "package.json",
7459
7474
  "mode": "0000644",
7460
- "sha256": "2a7d837ba22c70e21b083f3189108a90a974312b07bccf634bca4175e7213697"
7475
+ "sha256": "6a2c55e98a36d257177254a100569a14bec9b3720931caf9ef265971b81ba319"
7461
7476
  },
7462
7477
  {
7463
7478
  "path": "public-docs/index.html",
@@ -7644,6 +7659,11 @@
7644
7659
  "mode": "0000644",
7645
7660
  "sha256": "afcebfb8e8e8f7041ee9cbfce1e9b8a46a299feb5e6e89ce4e00273135b268db"
7646
7661
  },
7662
+ {
7663
+ "path": "scripts/gds/bug-triage.js",
7664
+ "mode": "0000644",
7665
+ "sha256": "70b8f34cd401395e828bcd7d2b1c3bf508d2f1e0918f998df034efb15b6783b8"
7666
+ },
7647
7667
  {
7648
7668
  "path": "scripts/gds/build-bongos.js",
7649
7669
  "mode": "0000644",
@@ -8702,7 +8722,7 @@
8702
8722
  {
8703
8723
  "path": "scripts/gds/triage.js",
8704
8724
  "mode": "0000644",
8705
- "sha256": "831de1914642a7dcf7c591c9cc69b37c917122e04428cb1e28fc00b07170b603"
8725
+ "sha256": "dc494030979bf60de1d1423c68a07e36667e816d7e2a1652960cfbd17be4bfcc"
8706
8726
  },
8707
8727
  {
8708
8728
  "path": "scripts/gds/turn-end-sound.js",
@@ -9147,7 +9167,7 @@
9147
9167
  {
9148
9168
  "path": "src/module-api.js",
9149
9169
  "mode": "0000644",
9150
- "sha256": "9808388b3f47f916911b59d0434a3e67f466869be9a3c7fa9adde9fb1274770c"
9170
+ "sha256": "95e7b476152291d84e955bfd585e62c48c48d85dcab0ca8d73c4519af2997e8b"
9151
9171
  },
9152
9172
  {
9153
9173
  "path": "src/module-loader/catalog.js",
@@ -9197,7 +9217,7 @@
9197
9217
  {
9198
9218
  "path": "tests/abandon_task.mjs",
9199
9219
  "mode": "0000644",
9200
- "sha256": "031b75aeb83322055c4d69ab5481b220a7a41ccbe950129b84f8bc80d7c75a40"
9220
+ "sha256": "ccfb5d3e85fd6bd34acc78d510edf20d037d344eca58706f3e3130fa6dce611c"
9201
9221
  },
9202
9222
  {
9203
9223
  "path": "tests/access_requests_invite.mjs",
@@ -9599,6 +9619,11 @@
9599
9619
  "mode": "0000644",
9600
9620
  "sha256": "b9303554061efaea510ef42d774ab9017ac7258cc0bc08f684abca7408b3fa86"
9601
9621
  },
9622
+ {
9623
+ "path": "tests/bug_triage.mjs",
9624
+ "mode": "0000644",
9625
+ "sha256": "2bc6312ff72fffc04478d40adc60e57e031b52ed208adf1d88f78986e536623f"
9626
+ },
9602
9627
  {
9603
9628
  "path": "tests/build_info.mjs",
9604
9629
  "mode": "0000644",
@@ -12214,6 +12239,11 @@
12214
12239
  "mode": "0000644",
12215
12240
  "sha256": "bc89502f571659477178d68047098cf6098030491ac5c5a82b46802474a48387"
12216
12241
  },
12242
+ {
12243
+ "path": "tests/task_merge.mjs",
12244
+ "mode": "0000644",
12245
+ "sha256": "2a16f120729c641de65cccf79f1583e2d2e68b72bf10277310991c1787dde2bf"
12246
+ },
12217
12247
  {
12218
12248
  "path": "tests/task_ownership_gate.mjs",
12219
12249
  "mode": "0000644",
@@ -12302,7 +12332,7 @@
12302
12332
  {
12303
12333
  "path": "tests/triage_development.mjs",
12304
12334
  "mode": "0000644",
12305
- "sha256": "74798ce81280d4fcfa5844b575c3c64db582308f7a0726fc420317fe83eadd96"
12335
+ "sha256": "56ff1fab014462291349ac200aab9d12e11a73c028aebeb564866b7a1b309fbd"
12306
12336
  },
12307
12337
  {
12308
12338
  "path": "tests/ui_design_kit.mjs",
@@ -0,0 +1,191 @@
1
+ ---
2
+ name: bug-triage
3
+ description: >-
4
+ Daily walk through open kind=bug tasks, which land with no triage gate and
5
+ accumulate near-duplicates. Groups likely dups for one batch verdict, then
6
+ walks what's left: merge into a canonical task or won't-fix. Mirrors
7
+ /idea-triage. Triggers on "/bug-triage", "triage bugs", "walk the bug
8
+ queue", "dedupe bugs". Metic+ rank only (not Xenos/Thetes).
9
+ ---
10
+
11
+ You are running the bug-triage session for Example. Open `kind=bug` tasks get
12
+ walked and verdicted, so near-duplicate reports don't sit forever as separate
13
+ claimable rows.
14
+
15
+ ## Why this exists (task 1001478, idea 244)
16
+
17
+ Since task 1003071, a `#bugs` report routes straight to a `kind=bug` task at
18
+ filing time — no inbox stop, no gate. That is correct for getting a report
19
+ into the queue fast, but it means nothing ever folds two reports of the same
20
+ underlying bug together the way `/idea-triage` folds duplicate ideas. This
21
+ skill is that missing step, built on a **task-merge primitive** (`tasks`
22
+ gained `merged_into_task_id` + `triage_note` in `core_232`, mirroring
23
+ `idea_inbox`'s `merged_into_idea_id` from `core_195`) that did not exist
24
+ before this task.
25
+
26
+ **A merge is `abandoned` + a target, not a new status.** `tasks.status` has no
27
+ `'merged'` value — adding one would touch every place that already enumerates
28
+ task statuses (the claimable feed, dependency satisfaction, the diagrams
29
+ state machine) to answer a question `abandoned` already answers: this task is
30
+ retired, and by owner ruling its dependents are stranded, not waiting (see
31
+ `modules/lifecycle/CLAUDE.md`). `merged_into_task_id` carries the one thing
32
+ bare abandon can't: which task absorbed it. Read `modules/lifecycle/db-ship.js`
33
+ `mergeTask`'s header comment for the full guard list before touching this
34
+ surface.
35
+
36
+ ## Rank gate — Metic+ only
37
+
38
+ **Gated to Metic+ (Metic, Archon); not Xenos or Thetes.** Merging or
39
+ won't-fixing a bug task reshapes the team's backlog the same way idea triage
40
+ does — `task.merge` and `task.abandon` are both floor-`metic` permissions
41
+ (`modules/government/catalog.js`). A sub-Metic can still **file** a bug (via
42
+ Discord `#bugs`, or `POST /tasks` if `task.create` allows it); they can't
43
+ verdict the queue.
44
+
45
+ **Enforce the gate at the very top of the session, before reading anything else:**
46
+
47
+ 1. Call `GET /api/gds/me` and read `builder.rank`. Compare case-insensitively.
48
+ 2. **If `rank` lowercases to `xenos` or `thetes`** → stop immediately. Tell the
49
+ builder: *"Bug triage is a Metic+ operation — merging or dismissing a bug
50
+ report reshapes the team's backlog. File new bugs via Discord `#bugs`;
51
+ ask an Archon to promote you if you need to triage them."* Do not list
52
+ open bugs, do not propose verdicts.
53
+ 3. **If `rank` lowercases to `metic` or `archon`** → proceed below.
54
+ 4. **If `rank` is absent** (pre-rank deployment) → treat the session as
55
+ available, the same pre-rank passthrough `/idea-triage` uses.
56
+
57
+ > Belt-and-braces: the server already rejects `POST /tasks/:id/merge` and
58
+ > `POST /tasks/:id/abandon` with `403 permission_forbidden` for a sub-Metic
59
+ > caller — this check just stops one early instead of after a wall of 403s.
60
+
61
+ ## What this skill does
62
+
63
+ 1. Lists open `kind=bug` tasks via `GET /api/gds/tasks?kind=bug&limit=1000`,
64
+ filtered to the still-open statuses (`backlog`, `ready`, `active`,
65
+ `blocked`, `completed`, `confirmed` — never `shipped` or `abandoned`,
66
+ the latter of which already includes merged-away duplicates).
67
+ 2. **Batches near-duplicate bug tasks** into one verdict per cluster
68
+ (`node scripts/gds/bug-triage.js`'s reuse of `triage.js`'s `clusterRecords`
69
+ — the same union-find/Jaccard core `/idea-triage`'s `clusterIdeas` uses,
70
+ generalized in task 1001478 so the algorithm is written once). Partitioned
71
+ by `goal_id`, not `kind` — every row here is already `kind='bug'`, so a
72
+ kind gate would be a no-op; two bug reports in unrelated goals describing
73
+ similar-sounding symptoms are not the same triage decision.
74
+ 3. For each remaining bug, prompts the user with: **merge** (this is the same
75
+ report as task N) or **won't-fix** (not a bug / already fixed / can't
76
+ reproduce — closes the report itself, folds into nothing).
77
+ 4. Applies each verdict via `POST /api/gds/tasks/:id/merge` or
78
+ `POST /api/gds/tasks/:id/abandon`.
79
+
80
+ **No "promote" step, unlike `/idea-triage`.** A bug report is already a task —
81
+ there is no shape question (goal vs criterion vs task) to work out, because
82
+ `task-classifier.js`'s auto-routing already decided `kind=bug` at capture
83
+ time. The only two questions triage adds are "is this the same as an existing
84
+ report" and "should this report be closed at all".
85
+
86
+ ## How to use
87
+
88
+ **Step 1 — fetch open bug tasks.**
89
+
90
+ ```
91
+ node scripts/gds/api.js GET "/api/gds/tasks?kind=bug&limit=1000"
92
+ ```
93
+
94
+ Filter client-side to the open statuses above. If nothing remains, print "bug
95
+ queue empty — nothing to triage today" and stop.
96
+
97
+ For a human at a terminal, `node scripts/gds/bug-triage.js` runs the whole
98
+ walk interactively instead — batching, per-task prompts, and the summary all
99
+ in one command, same shape as `node scripts/gds/triage.js`.
100
+
101
+ **Step 1.5 — batch near-duplicate bug reports BEFORE walking individually.**
102
+
103
+ Run `clusterRecords` (`scripts/gds/triage.js`, pure, no I/O) with
104
+ `textOf: t => t.title + ' ' + t.description`, `kindOf: t => t.goal_id`,
105
+ `timeOf: t => new Date(t.created_at)`. For each cluster it returns, offer the
106
+ user ONE decision covering the whole group:
107
+
108
+ > Cluster of 3 likely duplicates: #501 "Login button does nothing on
109
+ > Safari", #517 "Safari: login unresponsive", #530 "Login broken in Safari".
110
+ > **Keep #501 and merge the rest, dismiss all as won't-fix, walk them
111
+ > individually, or skip?**
112
+
113
+ - **Keep #N** → merge every other member into #N (`POST /tasks/:id/merge
114
+ {"into_task_id": N}`), leaving #N itself open — it still gets the normal
115
+ Step 2 walk below, since "which report is the real one" and "should it stay
116
+ open" are different decisions.
117
+ - **Won't-fix all** → every member abandoned with one shared reason.
118
+ - **Individually** → ignore the grouping for this cluster; each member
119
+ returns to the normal walk.
120
+ - **Skip** → leave the whole cluster open; it clusters again next triage.
121
+
122
+ `node scripts/gds/bug-triage.js` already runs this pass automatically before
123
+ its interactive walk — a human running the deterministic walker gets it for
124
+ free with no flag.
125
+
126
+ **Step 2 — walk the list, task by task.**
127
+
128
+ Present each remaining bug with:
129
+ - id, status, title
130
+ - description (full)
131
+ - goal_id, created_at
132
+
133
+ Then ask the user (concise prompt — the owner may not be a software engineer,
134
+ CLAUDE.md §2's portable default; frame in product/outcome terms, not
135
+ implementation):
136
+
137
+ > Bug #N: "{title}". {one-sentence summary of the report}. **Is this a
138
+ > duplicate of an existing bug, should it be closed as won't-fix, or should
139
+ > it stay open?**
140
+
141
+ **Step 3 — apply the verdict.**
142
+
143
+ **Merge** → ask which existing task N is the canonical one, then fold this
144
+ report into it:
145
+
146
+ ```
147
+ node scripts/gds/api.js POST /api/gds/tasks/<id>/merge --body '{"into_task_id":<N>,"reason":"same report as #N"}'
148
+ ```
149
+
150
+ The server refuses a self-merge (`merge_self`), a shipped source
151
+ (`cannot_merge_shipped`), a task with an active claim
152
+ (`task_has_active_claim`), a target that doesn't exist
153
+ (`merge_target_not_found`), and a target that is itself merged elsewhere
154
+ (`merge_target_merged` — it names where the chain ends; retarget there
155
+ instead of re-merging).
156
+
157
+ **Won't-fix** → dismiss with a reason (not a bug / already fixed / cannot
158
+ reproduce):
159
+
160
+ ```
161
+ node scripts/gds/api.js POST /api/gds/tasks/<id>/abandon --body '{"reason":"cannot reproduce on current build"}'
162
+ ```
163
+
164
+ **Keep open** is implicit — leave the task un-patched by not calling either
165
+ route. It will surface again next triage.
166
+
167
+ **Step 4 — surface a summary.**
168
+
169
+ Report to the user:
170
+ - Total walked
171
+ - Merged (count + which task each folded into)
172
+ - Won't-fixed (count + ids)
173
+ - Skipped / left open (count — these surface again next session)
174
+ - Remaining open bug count
175
+
176
+ ## Why we run this
177
+
178
+ The same reason `/idea-triage` runs daily: a queue nobody walks quietly fills
179
+ with near-duplicates that each cost a separate builder's attention to
180
+ discover independently. A fast pass with mostly "skip" verdicts beats
181
+ skipping the session entirely — the cadence is the discipline.
182
+
183
+ ## Tone for the user
184
+
185
+ The owner is the prompter (CLAUDE.md §2's portable default). Frame merge/
186
+ won't-fix decisions in terms of what's actually broken for the end user, not
187
+ implementation: "these
188
+ are the same login failure reported twice" not "these share 71% word
189
+ overlap." Decide the mechanical merge target yourself; ask the human only
190
+ when two reports are genuinely ambiguous (different symptoms that might
191
+ share one root cause, say) rather than obviously the same.
@@ -53,9 +53,10 @@ capture-time suggester (R08/R09/R11 surfaces) is where the fix belongs.
53
53
  ## What this skill does
54
54
 
55
55
  1. Lists open ideas via `GET /api/gds/inbox`.
56
- 2. **Develops the open ideas in parallel** — spawns one subagent per idea (the Agent tool) that fleshes the one-liner out into a structured *open-questions / challenges / risks* block, so the Archon verdicts on a developed idea instead of a bare title (task 930).
57
- 3. For each, prompts the user with: **promote** (create a task and link it), **discard** (closed, no action), or **merge** (this duplicates idea N, mark merged into N).
58
- 4. Updates each idea via `PATCH /api/gds/inbox/:id` with the verdict.
56
+ 2. **Batches near-duplicate ideas** into one verdict per cluster (`node scripts/gds/triage.js`'s `clusterIdeas`), so a backlog of similar asks doesn't cost one decision each (task 1001465).
57
+ 3. **Develops what's left with a LEAN inline pass by default** — reasoning from context already in the session, no subagent spend — and escalates to a FEW targeted subagents only where repo grounding would actually flip the verdict (task 1001465; the old unconditional 12-subagent fan-out is now opt-in, task 930's original mechanism).
58
+ 4. For each, prompts the user with: **promote** (create a task and link it), **discard** (closed, no action), or **merge** (this duplicates idea N, mark merged into N).
59
+ 5. Updates each idea via `PATCH /api/gds/inbox/:id` with the verdict.
59
60
 
60
61
  ## How to use
61
62
 
@@ -91,33 +92,46 @@ this order and prints the grade on each card, and `render-ideas.js` renders the
91
92
  same two sections — so the interactive walk, the generated file and this skill all
92
93
  show the same idea the same way.
93
94
 
94
- **Step 1.5 — develop the open ideas in parallel (task 930).**
95
+ **Step 1.4 — a cheap staleness pass, before anything else costs a token.** Quick-skim each open idea's title/body against the current codebase/CLAUDE.md to catch ones already implemented, already superseded, or otherwise obviously stale. A recent triage found 47 of 170 open ideas already stale — filtering those first means neither the batching pass nor the development pass below is spent on an idea that just needs a discard.
95
96
 
96
- **Before spawning any subagents, run a cheap staleness pass over the list first** — a quick skim of each idea's title/body against the current codebase/CLAUDE.md to catch ideas that are already implemented, already superseded, or otherwise obviously stale. A recent triage found 47 of 170 open ideas already stale; filtering those out first means the expensive multi-agent development pass below is only spent on ideas that still need it.
97
+ **Step 1.5a — batch near-duplicate ideas BEFORE developing anything (task 1001465).**
97
98
 
98
- Then, before walking the list, **flesh out each remaining open idea with a subagent** so the verdict lands on a developed idea, not a one-liner. Spawn the subagents **in parallel** (multiple `Agent` tool calls in a single message) — they run while you prepare the walk, so there's no serial wait.
99
+ 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:
99
100
 
100
- - **Model:** `opus` for every development subagent. This is research / design analysis against the live repo, which per CLAUDE.md §2's routing table is Opus work — never inherit silently, never downgrade to Haiku.
101
- - **Fan-out cap:** develop up to **12** ideas per session (oldest-first — `captured_at` ascending). If there are more than 12 open, note "developed the 12 oldest; the rest are walked undeveloped" so nothing is silently dropped.
102
- - **One idea per subagent.** Give each subagent the idea's `id`, `title`, `body_md`, `kind`, and `captured_by`, and this instruction:
101
+ > 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?**
103
102
 
104
- > Flesh out this Example idea for an Archon triage verdict. Read the relevant code/docs to ground yourself (start from CLAUDE.md and the surfaces the idea touches). Return ONLY a JSON object with these keys: `summary` (one sentence, what integrating it actually means), `integration_surfaces` (array of files/areas it would touch), `open_questions` (array — the decisions a human must make before this can be a task), `challenges` (array — what's hard or unclear), `risks` (array — what could break or regress), `rough_effort` (one of `S`/`M`/`L`), `recommendation` (one of `promote`, `discard`, `merge:#<id>`, `needs-info`). Keep each array item to one line. Do not invent surfaces you didn't verify exist.
103
+ - **Keep #N** → merge every other member into #N (`merged_into_idea_id`), leaving #N itself open — it still gets the normal Step 2 walk below, since "which of these duplicates is real" and "should the real one become a task" are different decisions.
104
+ - **Discard all** → every member closed, one shared `triage_note`.
105
+ - **Individually** → ignore the grouping for this cluster; each member returns to the normal walk.
106
+ - **Skip** → leave the whole cluster open; it clusters again next triage.
105
107
 
106
- - **Collect the results into a development cache** keyed by idea id and write it to a temp file so the deterministic walker can also surface it:
108
+ `node scripts/gds/triage.js` already runs this pass automatically before its interactive walk — if a human is running the deterministic walker instead of you, they get it for free with no flag.
109
+
110
+ **Step 1.5b — develop what's LEFT with a lean inline pass by default (task 1001465).**
111
+
112
+ **The subagent fan-out below is now OPT-IN, not the default.** The prior design ran a 12-Opus-subagent fan-out unconditionally on every session — the main cost driver idea 377 was filed about. Default instead to a **lean inline pass**: for each remaining open idea (after batching), reason about it yourself using context you already have — CLAUDE.md, `/recall`, a quick grep if genuinely needed — and write a short inline note (one or two lines: what it touches, the one open question that matters) directly in the session. No `Agent` tool call, no JSON cache file, no per-idea cost.
113
+
114
+ **Escalate to a targeted subagent only when the inline pass can't tell whether repo grounding would flip the verdict** — a technical/GDS idea where "is this already implemented?", "does this surface still exist?", or "would this conflict with X" needs an actual code read to answer, not a guess. Cap this at a **FEW** ideas per session (3, not 12) — if more than 3 are genuinely ambiguous, develop the 3 oldest and note the rest are walked on the inline pass alone.
115
+
116
+ - **Model:** `opus` for a targeted subagent — this is still research/design analysis against the live repo (CLAUDE.md §2's routing table), never downgrade to Haiku just because there are fewer of them now.
117
+ - **One idea per subagent**, same instruction as before: give it the idea's `id`, `title`, `body_md`, `kind`, `captured_by`, and ask for the same JSON shape (`summary`, `integration_surfaces`, `open_questions`, `challenges`, `risks`, `rough_effort`, `recommendation`) — only the FAN-OUT WIDTH changed, not the shape a developed idea returns.
118
+ - **Collect results into the same development cache**, written to `${TMPDIR:-/tmp}/otb-idea-development.json` (Windows: `%TEMP%\otb-idea-development.json`):
107
119
 
108
120
  ```json
109
121
  { "generated_at": "<ISO>", "ideas": { "228": { "summary": "...", "integration_surfaces": ["..."], "open_questions": ["..."], "challenges": ["..."], "risks": ["..."], "rough_effort": "M", "recommendation": "needs-info" } } }
110
122
  ```
111
123
 
112
- Write it to `${TMPDIR:-/tmp}/otb-idea-development.json` (Windows: `%TEMP%\otb-idea-development.json`). A subagent that fails or returns unparseable JSON is simply omitted from the cache — that idea is walked undeveloped, not blocked.
124
+ A subagent that fails or returns unparseable JSON is simply omitted — that idea is walked on the inline pass alone, not blocked.
125
+
126
+ **The full 12-idea fan-out is still available, explicitly, when the queue genuinely needs it** — a big backlog day, or the user asks for "the deep pass" / "develop all of these with subagents". When asked for it: spawn one Opus subagent per open idea (not just the ambiguous few), up to the same 12-idea cap (oldest-first), same instruction and cache shape as above. This is the OLD default, now a deliberate choice rather than something that runs unasked every time.
113
127
 
114
- If a human will run the interactive walker instead of the Claude-driven walk below, hand it the cache:
128
+ If a human will run the interactive walker instead of the Claude-driven walk below, hand it the cache (works the same whether it came from the lean pass's few subagents or the full deep pass):
115
129
 
116
130
  ```
117
131
  node scripts/gds/triage.js --development "${TMPDIR:-/tmp}/otb-idea-development.json"
118
132
  ```
119
133
 
120
- `triage.js` prints each idea's developed block above its verdict prompt. It never *generates* the analysis (that's this skill's subagent fan-out) — it only displays what the cache holds, and runs exactly as before when no cache is present.
134
+ `triage.js` prints each idea's developed block above its verdict prompt, and runs its own batch-cluster pass (Step 1.5a) automatically first. It never *generates* the analysis (that's this skill's job); it only displays what the cache holds, and runs exactly as before when no cache is present.
121
135
 
122
136
  > **Not in this cut (follow-up leg).** The next step is to let the Archon push the developed `open_questions` *back to the submitter* — a round-trip in the Discord `#ideas` thread / DM before promotion — so the submitter says how they'd want it integrated. That needs the Discord bot interaction + idea↔thread linking (idea `#216` / [ADR 0033](../../../docs/adr/<redacted>.md)) and is tracked separately. For now the open questions are answered by the Archon in-session.
123
137
 
@@ -5,7 +5,7 @@ A **generated**, zero-dependency typed client for the Bongos API — produced fr
5
5
  by hand; it regenerates when the spec changes, so it can never drift from the routes.
6
6
 
7
7
  - API version: **v1** (served at `/api/bongos/v1`)
8
- - 355 operations across 55 resource groups
8
+ - 356 operations across 55 resource groups
9
9
 
10
10
  ## Install
11
11
 
@@ -849,6 +849,8 @@ function createClient(opts = {}) {
849
849
  deleteTasksIdDependenciesDepId: (args) => request("DELETE", "/tasks/{id}/dependencies/{depId}", { hasBody: false }, args),
850
850
  // POST /tasks/{id}/grade — rank: any-builder — POST /tasks/:id/grade
851
851
  postTasksIdGrade: (args) => request("POST", "/tasks/{id}/grade", { hasBody: true }, args),
852
+ // POST /tasks/{id}/merge — rank: metic+archon — POST /tasks/:id/merge
853
+ postTasksIdMerge: (args) => request("POST", "/tasks/{id}/merge", { hasBody: true }, args),
852
854
  // POST /tasks/{id}/override-request — rank: any-builder — POST /tasks/:id/override-request
853
855
  postTasksIdOverrideRequest: (args) => request("POST", "/tasks/{id}/override-request", { hasBody: true }, args),
854
856
  // POST /tasks/{id}/promote — rank: metic+archon — POST /tasks/:id/promote
@@ -848,6 +848,8 @@ function createClient(opts = {}) {
848
848
  deleteTasksIdDependenciesDepId: (args) => request("DELETE", "/tasks/{id}/dependencies/{depId}", { hasBody: false }, args),
849
849
  // POST /tasks/{id}/grade — rank: any-builder — POST /tasks/:id/grade
850
850
  postTasksIdGrade: (args) => request("POST", "/tasks/{id}/grade", { hasBody: true }, args),
851
+ // POST /tasks/{id}/merge — rank: metic+archon — POST /tasks/:id/merge
852
+ postTasksIdMerge: (args) => request("POST", "/tasks/{id}/merge", { hasBody: true }, args),
851
853
  // POST /tasks/{id}/override-request — rank: any-builder — POST /tasks/:id/override-request
852
854
  postTasksIdOverrideRequest: (args) => request("POST", "/tasks/{id}/override-request", { hasBody: true }, args),
853
855
  // POST /tasks/{id}/promote — rank: metic+archon — POST /tasks/:id/promote
@@ -371,6 +371,7 @@ export interface PostTasksIdDemoteResponse { ok: boolean }
371
371
  export interface PostTasksIdDependenciesRequest { depends_on_task_ids?: unknown[]; depends_on_task_id?: StringifiableId }
372
372
  export interface PostTasksIdDependenciesResponse { ok: boolean; added: unknown; dependencies: unknown }
373
373
  export interface PostTasksIdGradeRequest { subagent_output?: unknown; signals?: Record<string, unknown>; committed_files?: string[]; diff_hash?: string }
374
+ export interface PostTasksIdMergeRequest { into_task_id: number; reason?: string }
374
375
  export interface PostTasksIdOverrideRequestRequest { rationale: string }
375
376
  export interface PostTasksIdOverrideRequestResponse { ok: boolean; request: unknown }
376
377
  export interface PostTasksIdPublishBranchRequest { branch: string; head_sha: string; bundle_b64: string; value_summary?: string }
@@ -1172,6 +1173,8 @@ export interface BongosClient {
1172
1173
  deleteTasksIdDependenciesDepId(args?: RequestArgs): Promise<DeleteTasksIdDependenciesDepIdResponse>;
1173
1174
  /** POST /tasks/{id}/grade — rank: any-builder */
1174
1175
  postTasksIdGrade(args?: RequestArgs & { body?: PostTasksIdGradeRequest }): Promise<ApiResponse>;
1176
+ /** POST /tasks/{id}/merge — rank: metic+archon */
1177
+ postTasksIdMerge(args: RequestArgs & { body: PostTasksIdMergeRequest }): Promise<ApiResponse>;
1175
1178
  /** POST /tasks/{id}/override-request — rank: any-builder */
1176
1179
  postTasksIdOverrideRequest(args: RequestArgs & { body: PostTasksIdOverrideRequestRequest }): Promise<PostTasksIdOverrideRequestResponse>;
1177
1180
  /** POST /tasks/{id}/promote — rank: metic+archon */
@@ -845,6 +845,8 @@ export function createClient(opts = {}) {
845
845
  deleteTasksIdDependenciesDepId: (args) => request("DELETE", "/tasks/{id}/dependencies/{depId}", { hasBody: false }, args),
846
846
  // POST /tasks/{id}/grade — rank: any-builder — POST /tasks/:id/grade
847
847
  postTasksIdGrade: (args) => request("POST", "/tasks/{id}/grade", { hasBody: true }, args),
848
+ // POST /tasks/{id}/merge — rank: metic+archon — POST /tasks/:id/merge
849
+ postTasksIdMerge: (args) => request("POST", "/tasks/{id}/merge", { hasBody: true }, args),
848
850
  // POST /tasks/{id}/override-request — rank: any-builder — POST /tasks/:id/override-request
849
851
  postTasksIdOverrideRequest: (args) => request("POST", "/tasks/{id}/override-request", { hasBody: true }, args),
850
852
  // POST /tasks/{id}/promote — rank: metic+archon — POST /tasks/:id/promote