@bongos/core 1.19.302 → 1.19.303

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.302",
6
- "core_contract": "1.19.302",
7
- "source_commit": "d2bc099b7707a0e3e29c2b48b796bc95785d7c74",
5
+ "core_version": "1.19.303",
6
+ "core_contract": "1.19.303",
7
+ "source_commit": "fd08cc750b621521b54d357645136366c89f0316",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-22T23:12:06.332Z",
9
+ "built_at": "2026-08-22T23:32:40.774Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 322,
13
13
  "agent_docs_stubbed": 22,
14
- "functional_verbatim": 1411,
14
+ "functional_verbatim": 1412,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1755,
20
- "tree_sha256": "f1bb517fc8d0b3ac176f07c721aaa32bec280b6937905b78fdde9966e084d99e",
19
+ "file_count": 1756,
20
+ "tree_sha256": "42829db3492386930a68c9936d55f1e7d7895fc1e6194a65e6d659e81be98c05",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -172,7 +172,7 @@
172
172
  {
173
173
  "path": ".claude/skills/idea-triage/SKILL.md",
174
174
  "mode": "0000644",
175
- "sha256": "cbecde1c3fe60e02e6b008b80232e8866d7924aaefa9614377044dce5973d1d0"
175
+ "sha256": "b64fe42bb2a542304802b48bea348ae817608ac47f026d98c4de9bea5aa90174"
176
176
  },
177
177
  {
178
178
  "path": ".claude/skills/ideate/SKILL.md",
@@ -4577,12 +4577,12 @@
4577
4577
  {
4578
4578
  "path": "package-lock.json",
4579
4579
  "mode": "0000644",
4580
- "sha256": "06de1a2274d6eafe7a544b7e0c1f157b42191eb0ad151d6e7f821180604d74bb"
4580
+ "sha256": "5721cb7973d1dc844692348aeb2462d3adfcba9929f7146f6a872c6de15dd710"
4581
4581
  },
4582
4582
  {
4583
4583
  "path": "package.json",
4584
4584
  "mode": "0000644",
4585
- "sha256": "45ca3b2302b14730ce004fdc64f37ab82cc0dc85518c55c4c3119b85851ddf20"
4585
+ "sha256": "441e6e4bea0f538d4c0e63eb17cee0f65077775be47c930b8876cb022bc0966b"
4586
4586
  },
4587
4587
  {
4588
4588
  "path": "public-docs/index.html",
@@ -6237,7 +6237,7 @@
6237
6237
  {
6238
6238
  "path": "src/module-api.js",
6239
6239
  "mode": "0000644",
6240
- "sha256": "938f69ad6350189382990706f63b41c1781f9c7b19610d7275144152c65412d7"
6240
+ "sha256": "dcb27806a84ffee473f8e69ca78530535b7d7c51e30780e09982ba8489768f9c"
6241
6241
  },
6242
6242
  {
6243
6243
  "path": "src/module-loader/catalog.js",
@@ -7614,6 +7614,11 @@
7614
7614
  "mode": "0000644",
7615
7615
  "sha256": "4e75030cf2320c2715c5e764394e9d56f66861712176261c878bc99183622b69"
7616
7616
  },
7617
+ {
7618
+ "path": "tests/idea_triage_recommends.mjs",
7619
+ "mode": "0000644",
7620
+ "sha256": "c7ba20839a7b33df0eb7e8bfc2f463217e7d3a538d888e89774107d55d34556d"
7621
+ },
7617
7622
  {
7618
7623
  "path": "tests/idp_device_exchange.mjs",
7619
7624
  "mode": "0000644",
@@ -134,6 +134,54 @@ Then ask the user (concise prompt — Lars is an EE, not a software engineer; fr
134
134
 
135
135
  > Idea #N: "{title}". {one-sentence developed summary}. **Open questions:** {the subagent's open_questions}. **Promote, discard, or merge with another idea?**
136
136
 
137
+ **Step 2.5 — for a FULL IDEA, work out its SHAPE and recommend one (task 1002925 / BV1.R149).**
138
+
139
+ Only for `grade='full'`. A quick idea promotes straight to a task — that is what a
140
+ quick idea is *for* — and a spark is not yours to convert at all (its author
141
+ offered it up for development, and promoting it discards that invitation).
142
+
143
+ **Why this step exists, in the owner's words:** *for ideators it is hard to say
144
+ what is a task or a goal, because it is often hard to have the full picture.* So a
145
+ Full Idea gets **thought about** at triage instead of being forced down a fixed
146
+ path. The person who wrote it was not required to know whether it was one task or
147
+ a quarter of work; that is the judgement triage adds.
148
+
149
+ **Three shapes it can take.** Read the five answers — especially *how does it fit*
150
+ — and decide which one it actually is:
151
+
152
+ | Shape | It looks like this | What you would create |
153
+ |---|---|---|
154
+ | **Goal** | several distinct pieces of work, or an outcome you would measure rather than finish; the "big picture" answer names more than one surface | a goal, plus **draft done-when criteria** taken from what the idea says "done" means |
155
+ | **Task** | one concrete change with an obvious end | a single task in whatever goal already owns that area |
156
+ | **Merge** | work that already exists — an open goal, a live task, or another idea says the same thing | nothing; point it at the existing record |
157
+
158
+ **Do the legwork before recommending.** A recommendation with nothing behind it is
159
+ worse than no recommendation, because it looks like an answer. Run
160
+ `/recall <the idea's subject>`, check the goal board
161
+ (`node scripts/gds/goal.js list`), and look for an existing task before proposing
162
+ anything new — "merge" is the recommendation people skip, and it is often the right
163
+ one.
164
+
165
+ **Then RECOMMEND, and show the reasoning.** Say which shape and *why*, name the
166
+ alternative you rejected and why, and — for a goal — sketch the criteria you would
167
+ draft. One short paragraph, not a report:
168
+
169
+ > Idea #N (Full, 84/100) — **I'd make this a goal.** Its "big picture" answer names
170
+ > three separate surfaces (the CLI, the hall, and the Discord mirror), so a single
171
+ > task would either be a month long or quietly deliver only one of them. Draft
172
+ > criteria: *(1) the CLI can do X; (2) the same is reachable in the hall; (3) the
173
+ > Discord path stops needing the old flag.* I considered a task in goal 1000071
174
+ > since the routing work overlaps, but this outlives that goal's scope.
175
+ > **Goal, task, or merge?**
176
+
177
+ **NOTHING CONVERTS UNTIL THEY ANSWER.** The recommendation is a recommendation:
178
+ do not create the goal, do not create the task, do not mark the idea promoted
179
+ until the human has chosen. If they pick a different shape than you recommended,
180
+ that is the step working — build what they chose, not what you argued for. (The
181
+ server backs this up at the capture end: a Full Idea never auto-routes,
182
+ `full_idea_never_routes` in `modules/ideas/routing.js`. This step is the same rule
183
+ at the triage end, where the judgement is.)
184
+
137
185
  **Step 3 — apply the verdict.**
138
186
 
139
187
  **Promote → pick the tier (BV1.R64 / ADR 0086).** An idea no longer only becomes a *task* — it can promote into any tier of the work hierarchy: a **goal** (a whole module-scoped workspace), a **criterion** (a new done-when test), or a **task** (a single unit of work). Choose by the idea's size: a broad objective → goal; a "we should also require X to be done" → criterion; a concrete piece of work → task. Frame the choice to the user in those terms, then create the right record and mark the idea promoted.
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.302",
3
+ "version": "1.19.303",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.302",
9
+ "version": "1.19.303",
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.19.302",
3
+ "version": "1.19.303",
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/src/module-api.js CHANGED
@@ -1031,8 +1031,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
1031
1031
  // landed since 1.19.300 with no explicit bump. run 32602146266. (task 1002620)
1032
1032
  // 1.19.302 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1033
1033
  // landed since 1.19.301 with no explicit bump. run 32604538465. (task 1002620)
1034
+ // 1.19.303 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
1035
+ // landed since 1.19.302 with no explicit bump. run 32605477232. (task 1002620)
1034
1036
  // ---------------------------------------------------------------------------
1035
- const CORE_VERSION = '1.19.302'; // CI auto-patch carrier (ADR 0161); see changelog above
1037
+ const CORE_VERSION = '1.19.303'; // CI auto-patch carrier (ADR 0161); see changelog above
1036
1038
 
1037
1039
  // A namespaced logger so a module's log lines are attributable + consistent.
1038
1040
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -0,0 +1,155 @@
1
+ // tests/idea_triage_recommends.mjs
2
+ //
3
+ // task 1002925 (BV1.R149, goal 1000062) — a Full Idea's SHAPE is recommended, not
4
+ // decided for the builder.
5
+ //
6
+ // The done-when: "triage presents a recommendation plus its reasoning; the human
7
+ // decides; nothing converts without that decision."
8
+ //
9
+ // Two halves, and they live in different places:
10
+ // • the SERVER half already exists and is enforced — a Full Idea never
11
+ // auto-routes at capture (routing.js). This file re-asserts it, because R149's
12
+ // rule is meaningless if the row can convert itself before triage ever sees
13
+ // it, and an invariant relied on by a prompt should be pinned next to that
14
+ // prompt's test too.
15
+ // • the TRIAGE half is a prompt, so what is checkable is the CONTRACT: a
16
+ // recommendation is required to carry reasoning, the three shapes are named,
17
+ // the legwork is required before recommending, and nothing converts until the
18
+ // human answers.
19
+ //
20
+ // Prose assertions run against a whitespace-normalised copy — markdown is
21
+ // hard-wrapped, and a phrase worth asserting will straddle a newline.
22
+ //
23
+ // Run: node tests/idea_triage_recommends.mjs
24
+
25
+ import { strict as assert } from 'node:assert';
26
+ import { createRequire } from 'node:module';
27
+ import fs from 'node:fs';
28
+ import path from 'node:path';
29
+ import { fileURLToPath } from 'node:url';
30
+
31
+ const require = createRequire(import.meta.url);
32
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
33
+ const SKILL = fs.readFileSync(path.join(ROOT, '.claude/skills/idea-triage/SKILL.md'), 'utf8');
34
+ const NORM = SKILL.replace(/\s+/g, ' ');
35
+ const { decideLanding } = require('../modules/ideas/routing.js');
36
+
37
+ let passed = 0;
38
+ let failed = 0;
39
+ function test(name, fn) {
40
+ try { fn(); passed++; console.log(` ok ${name}`); }
41
+ catch (err) { failed++; console.error(` FAIL ${name}\n ${err.message}`); }
42
+ }
43
+
44
+ // --- the server half: it cannot convert itself before triage looks -------
45
+
46
+ test('a Full Idea never auto-routes, whatever else is true of it', () => {
47
+ // R149's whole premise is that a human judges the shape. If capture-time
48
+ // routing could turn a Full Idea into a task, that judgement would arrive too
49
+ // late to matter.
50
+ for (const grade of ['full', 'FULL', 'Full']) {
51
+ const d = decideLanding({ goalId: 1000062, kind: 'feature', grade, filerRank: 'archon' });
52
+ assert.equal(d.route, false, `grade ${grade}`);
53
+ assert.equal(d.reason, 'full_idea_never_routes');
54
+ }
55
+ });
56
+
57
+ test('and a quick idea still routes — R149 is not a brake on everything', () => {
58
+ // The contrast is the point: quick ideas promoting straight through is what a
59
+ // quick idea is FOR, and slowing that down would be a regression dressed as care.
60
+ const d = decideLanding({ goalId: 1000062, kind: 'bug', grade: 'quick', filerRank: 'archon' });
61
+ assert.equal(d.route, true);
62
+ });
63
+
64
+ // --- the triage half: recommend, with reasoning --------------------------
65
+
66
+ test('the step is scoped to a Full Idea, and says what the other grades do', () => {
67
+ assert.match(NORM, /Only for `grade='full'`/);
68
+ assert.match(NORM, /quick idea promotes straight to a task/);
69
+ // A spark is not triage's to convert: promoting it discards its author's offer.
70
+ assert.match(NORM, /discards that invitation/);
71
+ });
72
+
73
+ test('it carries the owner\'s REASON, not just the instruction', () => {
74
+ // Without the why, "recommend instead of convert" reads as ceremony and gets
75
+ // optimised away by the next person trying to make triage faster.
76
+ assert.match(NORM, /hard to say what is a task or a goal/);
77
+ assert.match(NORM, /full picture/);
78
+ });
79
+
80
+ test('all three shapes are named — including MERGE', () => {
81
+ assert.match(NORM, /\*\*Goal\*\*/);
82
+ assert.match(NORM, /\*\*Task\*\*/);
83
+ assert.match(NORM, /\*\*Merge\*\*/);
84
+ // Merge is the one people skip, and it is often right.
85
+ assert.match(NORM, /"merge" is the recommendation people skip/);
86
+ });
87
+
88
+ test('a goal recommendation drafts criteria rather than just naming the tier', () => {
89
+ assert.match(NORM, /draft.{0,4}done-when criteria/i);
90
+ });
91
+
92
+ test('the legwork is REQUIRED before recommending', () => {
93
+ // A recommendation with nothing behind it is worse than none, because it looks
94
+ // like an answer.
95
+ assert.match(NORM, /worse than no recommendation/);
96
+ assert.match(NORM, /\/recall/);
97
+ assert.match(NORM, /goal\.js list/);
98
+ });
99
+
100
+ test('the reasoning must include the REJECTED alternative', () => {
101
+ // "I recommend a goal" is an assertion; "a task would deliver only one of the
102
+ // three surfaces" is a reason someone can disagree with.
103
+ assert.match(NORM, /name the alternative you rejected/i);
104
+ });
105
+
106
+ // --- nothing converts without the human ---------------------------------
107
+
108
+ test('the no-convert rule is stated absolutely, and enumerated', () => {
109
+ assert.match(NORM, /NOTHING CONVERTS UNTIL THEY ANSWER/);
110
+ // Enumerated because "do not convert" is vague about the three separate writes
111
+ // an eager agent might make.
112
+ assert.match(NORM, /do not create the goal, do not create the task, do not mark the idea promoted/);
113
+ });
114
+
115
+ test('a human choosing differently is framed as the step WORKING', () => {
116
+ // Otherwise the recommendation becomes something to defend, and the next turn
117
+ // is an argument instead of a build.
118
+ assert.match(NORM, /that is the step working/);
119
+ assert.match(NORM, /build what they chose, not what you argued for/);
120
+ });
121
+
122
+ test('it names the server invariant it leans on', () => {
123
+ // So the next reader knows the two halves are one rule in two places, and does
124
+ // not "simplify" either half away believing the other covers it.
125
+ assert.match(NORM, /full_idea_never_routes/);
126
+ });
127
+
128
+ // --- it does not restate the template ----------------------------------
129
+
130
+ test('the skill restates no field label, and no key as a code identifier', () => {
131
+ const { BASE_TEMPLATE } = require('../modules/ideas/templates.js');
132
+ // LABELS are checked over the whole file: they are distinctive canonical
133
+ // question text, so any occurrence is a restatement.
134
+ for (const f of BASE_TEMPLATE.fields) {
135
+ assert.ok(!SKILL.includes(f.label), `restates the label "${f.label}"`);
136
+ }
137
+ // KEYS are checked only inside CODE — backticks and fenced blocks. One of them
138
+ // is `fit`, an ordinary English word, and this skill legitimately says "how does
139
+ // it fit" in prose. What would actually be a forked template is a key written as
140
+ // an identifier: a hard-coded field list, a JSON example, a payload sketch. So
141
+ // the assertion follows the meaning rather than the substring.
142
+ const code = [
143
+ ...[...SKILL.matchAll(/```[\s\S]*?```/g)].map((m) => m[0]),
144
+ ...[...SKILL.matchAll(/`([^`\n]+)`/g)].map((m) => m[1]),
145
+ ].join('\n');
146
+ for (const f of BASE_TEMPLATE.fields) {
147
+ assert.ok(
148
+ !new RegExp(`\\b${f.key}\\b`).test(code),
149
+ `the key "${f.key}" appears as a code identifier — the template is being restated`,
150
+ );
151
+ }
152
+ });
153
+
154
+ console.log(`\nidea_triage_recommends: ${passed} passed, ${failed} failed`);
155
+ if (failed > 0) process.exit(1);