@bongos/core 1.19.934 → 1.19.935

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.19.934",
6
- "core_contract": "1.19.934",
7
- "source_commit": "6054b55eb2141143f4d11a42b1bda4268b8b5cd8",
5
+ "core_version": "1.19.935",
6
+ "core_contract": "1.19.935",
7
+ "source_commit": "b3b2e4e86881e90300f7b23d75f5e1508eb2bf81",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-21T03:13:03.324Z",
9
+ "built_at": "2026-09-21T03:42:26.193Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 511,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 2864,
20
- "tree_sha256": "d88bd8ff77498c91b75ca7ae50c9789681f652af911d6af41c1534ee4099b67c",
20
+ "tree_sha256": "e3c4fa4ce99958763ab765d6846942c8fad965add8df64e1f54a7e375806c926",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2677,7 +2677,7 @@
2677
2677
  {
2678
2678
  "path": "docs/module-api-changelog.md",
2679
2679
  "mode": "0000644",
2680
- "sha256": "58d9600e5e86ec9ff6ac1f3e585b1a8935d26372c16909d96b9129de68b91554"
2680
+ "sha256": "4c9bccb55d99dc3c2588b73ae95d365511eb433e27265822725aeed38420992b"
2681
2681
  },
2682
2682
  {
2683
2683
  "path": "docs/modules-contract.md",
@@ -5557,7 +5557,7 @@
5557
5557
  {
5558
5558
  "path": "modules/ideas/templates.js",
5559
5559
  "mode": "0000644",
5560
- "sha256": "3bbec1061e010f8e35e04db89b5cf0f08d1c1512a6ef2c7a1a0ce8c4828c7ec7"
5560
+ "sha256": "deef66d514a2ea090274acc05e85ffefdfd6c0bf0279b64f7bee16af7b2e1471"
5561
5561
  },
5562
5562
  {
5563
5563
  "path": "modules/ideas/trail-descent.js",
@@ -7977,12 +7977,12 @@
7977
7977
  {
7978
7978
  "path": "package-lock.json",
7979
7979
  "mode": "0000644",
7980
- "sha256": "c7cfc9418097ef5d95196a983c18f530596de47a261f713a224fd44a14a0c735"
7980
+ "sha256": "cc78b4e84d90eba27a329d88ae83b507df318962b057d9281c3cc1b858063e05"
7981
7981
  },
7982
7982
  {
7983
7983
  "path": "package.json",
7984
7984
  "mode": "0000644",
7985
- "sha256": "0cb7773b2dc025a6341b3461a6abb85dbff149e246f509b725bd362ea11d61dd"
7985
+ "sha256": "12f2fd99b6ac30b3bd8a443f576cf083c97eee82623b4392b714b107ab0c666b"
7986
7986
  },
7987
7987
  {
7988
7988
  "path": "public-docs/index.html",
@@ -9982,7 +9982,7 @@
9982
9982
  {
9983
9983
  "path": "src/module-api.js",
9984
9984
  "mode": "0000644",
9985
- "sha256": "0cf2ed963899d5343c619126aac9193ce576dcb47c334f6124c85400a36eb0d1"
9985
+ "sha256": "fe49958cdbfc0121ca262cd95b1fef21fc3028aa5da19250fb4bab2e68e42ccd"
9986
9986
  },
9987
9987
  {
9988
9988
  "path": "src/module-loader/catalog.js",
@@ -12017,7 +12017,7 @@
12017
12017
  {
12018
12018
  "path": "tests/idea_completeness.mjs",
12019
12019
  "mode": "0000644",
12020
- "sha256": "a7d6e79e240f7397109afc2522a4d3a255154149b3499581de7b22cfe7761f47"
12020
+ "sha256": "cbbe8fa2dbf3c593b322641caa1550bd1805089669ef2658c4c92df3c9cdc6f6"
12021
12021
  },
12022
12022
  {
12023
12023
  "path": "tests/idea_credit_split.mjs",
@@ -12052,7 +12052,7 @@
12052
12052
  {
12053
12053
  "path": "tests/idea_full_capture.mjs",
12054
12054
  "mode": "0000644",
12055
- "sha256": "95b53f1acfa61f723741b4d33b5e882525903ccc549ae01f3094a788251a60ec"
12055
+ "sha256": "a5d507f3fac77d70904ae0c9c9c49feb4c5eed26889e69f931fdf15fb9959960"
12056
12056
  },
12057
12057
  {
12058
12058
  "path": "tests/idea_full_pass_credit.mjs",
@@ -12147,7 +12147,7 @@
12147
12147
  {
12148
12148
  "path": "tests/idea_templates.mjs",
12149
12149
  "mode": "0000644",
12150
- "sha256": "8b21073cc2a766c749ca5687767c8c8fc6da6bf1149160f57d239d9163d0a1a6"
12150
+ "sha256": "7e1dd5417391cacd137967c388ba2f086b75ec5aca3fbb2703b3a5eb2ec2313d"
12151
12151
  },
12152
12152
  {
12153
12153
  "path": "tests/idea_trail_descent.mjs",
@@ -2342,5 +2342,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2342
2342
  landed since 1.19.932 with no explicit bump. run 35555853466. (task 1002620)
2343
2343
  1.19.934 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2344
2344
  landed since 1.19.933 with no explicit bump. run 35556739619. (task 1002620)
2345
+ 1.19.935 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2346
+ landed since 1.19.934 with no explicit bump. run 35558269194. (task 1002620)
2345
2347
  ---------------------------------------------------------------------------
2346
2348
  ```
@@ -82,20 +82,48 @@ const BASE_V1_TEMPLATE = Object.freeze({
82
82
  // the v1 label. `purpose`, `big_picture` and `why_matters` are NOT carried over —
83
83
  // they belong to v1 and stay there.
84
84
  //
85
- // THE FLOORS ARE FLAT 40, MATCHING v1, AND THAT IS TEMPORARY. The new questions
86
- // are not interchangeable ("scope it as best as you can" deserves more room than
87
- // "what made you have the idea"), but the score feeds credits, so choosing the
88
- // numbers is its own deliberate act — task 1004091 (BV2.R12) owns it. Shipping a
89
- // guess here would have been an unreviewed change to what a thought pays.
85
+ // THE FLOORS, CHOSEN PER FIELD (task 1004091 / BV2.R12, spec R21). v2 shipped at
86
+ // a placeholder flat 40 because picking the numbers is its own deliberate act:
87
+ // `completeness_score` is the mean of per-field ratios, and that number gates
88
+ // both the ideator credit (economy's IDEA_FULL_PASS_BAR = 60) and whether a Full
89
+ // Idea reaches the board at all (government's FULL_IDEA_PASS_BAR). A floor is not
90
+ // a style note; it is a price.
91
+ //
92
+ // THE TOTAL IS HELD AT 200, EXACTLY v1's FIVE × 40. What a thought costs to file
93
+ // is an economic decision the owner has already made once; this task was asked to
94
+ // distribute that cost across questions that are not interchangeable, not to
95
+ // re-price filing. Redistributing inside a fixed total is the change that needs no
96
+ // owner ruling. (It also keeps the template clear of ADR 0186's interim karma-
97
+ // eligibility guard — ≥3 required fields and ≥120 total `substanceMin`.)
98
+ //
99
+ // WHY EACH NUMBER, IN ONE LINE EACH. The rule is how much text the question
100
+ // HONESTLY takes, never how much we would like to receive:
101
+ // why_had 25 — the anti-ghost-writing field, and the shortest honest
102
+ // answer is a sentence. Nobody but the filer can say what
103
+ // made them have the idea, so this is precisely the floor
104
+ // that must NOT push an agent to pad; the never-invent-a-
105
+ // field rule (the Ideator pack) is strongest here.
106
+ // scope 65 — "as best as you can" is an invitation to the longest
107
+ // answer in the set, and this field is the one that becomes
108
+ // the work. The highest floor in the template.
109
+ // build_context 45 — a prior-knowledge dump: real but bounded, and legitimately
110
+ // short when there genuinely is little to know.
111
+ // fit 30 — an argument, and a good one is compact. Deliberately low:
112
+ // a long justification is usually a weak one.
113
+ // success 35 — a concrete picture of the finished thing; a few sentences.
114
+ //
115
+ // EACH RATIO IS CAPPED AT 1, SO THE TOTAL IS NOT A BUDGET. A 600-character scope
116
+ // answer cannot buy a blank `why_had`; the floors bind field by field, which is
117
+ // the whole reason they differ.
90
118
  const BASE_V2_TEMPLATE = Object.freeze({
91
119
  key: 'base-v2',
92
120
  label: 'Full Idea',
93
121
  fields: Object.freeze([
94
- Object.freeze({ key: 'why_had', label: 'What made you have the idea', required: true, substanceMin: 40 }),
95
- Object.freeze({ key: 'scope', label: 'Scope it as best as you can here', required: true, substanceMin: 40 }),
96
- Object.freeze({ key: 'build_context', label: 'What should an engineer or artist know prior to building', required: true, substanceMin: 40 }),
97
- Object.freeze({ key: 'fit', label: 'How is this long term justified in the project', required: true, substanceMin: 40 }),
98
- Object.freeze({ key: 'success', label: 'What does a successful implementation of the idea look like', required: true, substanceMin: 40 }),
122
+ Object.freeze({ key: 'why_had', label: 'What made you have the idea', required: true, substanceMin: 25 }),
123
+ Object.freeze({ key: 'scope', label: 'Scope it as best as you can here', required: true, substanceMin: 65 }),
124
+ Object.freeze({ key: 'build_context', label: 'What should an engineer or artist know prior to building', required: true, substanceMin: 45 }),
125
+ Object.freeze({ key: 'fit', label: 'How is this long term justified in the project', required: true, substanceMin: 30 }),
126
+ Object.freeze({ key: 'success', label: 'What does a successful implementation of the idea look like', required: true, substanceMin: 35 }),
99
127
  ]),
100
128
  });
101
129
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.934",
3
+ "version": "1.19.935",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.934",
9
+ "version": "1.19.935",
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.934",
3
+ "version": "1.19.935",
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
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
71
71
  // there. scripts/gds/bump-version.js still rewrites the literal below; it appends
72
72
  // the entry to that file. Look for a version's history there, not here.
73
73
  // ---------------------------------------------------------------------------
74
- const CORE_VERSION = '1.19.934'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
74
+ const CORE_VERSION = '1.19.935'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
75
75
 
76
76
  // A namespaced logger so a module's log lines are attributable + consistent.
77
77
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
@@ -40,6 +40,11 @@ const COMPLETE = Object.fromEntries(
40
40
  // the current template's keys is quietly asserting the template never changes.
41
41
  const [FIELD_1, FIELD_2, , FIELD_4] = BASE_TEMPLATE.fields.map((f) => f.key);
42
42
 
43
+ // Since task 1004091 (BV2.R12) the floors DIFFER per field, so a fixture may no
44
+ // longer assume "40" — or, worse, assume that equal text in two sections is equal
45
+ // credit. Every expectation below is derived from the declaration.
46
+ const floorOf = (key) => BASE_TEMPLATE.fields.find((f) => f.key === key).substanceMin;
47
+
43
48
  test('a complete idea scores 100, an empty one 0', () => {
44
49
  assert.equal(C.completenessScore(COMPLETE), 100);
45
50
  assert.equal(C.completenessScore({}), 0);
@@ -51,13 +56,25 @@ test('same input, same score — byte-for-byte determinism', () => {
51
56
  assert.deepEqual(C.scoreFieldValues(COMPLETE), C.scoreFieldValues(COMPLETE));
52
57
  });
53
58
 
54
- test('a thin answer earns its proportion, not the full share', () => {
55
- const oneThin = { ...COMPLETE, [FIELD_1]: 'ten chars.' }; // 10 of the 40-char floor
59
+ test('a thin answer earns its proportion of ITS OWN floor, not a flat share', () => {
60
+ const TEN = 'ten chars.'; // exactly 10 characters of substance
61
+ const oneThin = { ...COMPLETE, [FIELD_1]: TEN };
56
62
  const s = C.completenessScore(oneThin);
57
- assert.ok(s > 80 && s < 100, `four full + one quarter-credit field ≈ 85, got ${s}`);
63
+ assert.ok(s > 80 && s < 100, `four full fields + one part-credit field, got ${s}`);
58
64
  const breakdown = C.scoreFieldValues(oneThin).fields.find((f) => f.key === FIELD_1);
59
65
  assert.equal(breakdown.substance, 10);
60
- assert.equal(breakdown.ratio, 0.25);
66
+ assert.equal(breakdown.floor, floorOf(FIELD_1), 'the breakdown publishes the field\'s own floor');
67
+ assert.equal(breakdown.ratio, 10 / floorOf(FIELD_1));
68
+
69
+ // BV2.R12's actual deliverable: the same ten characters are worth DIFFERENT
70
+ // credit in different sections, because the questions are not interchangeable.
71
+ // A test that only ever looked at one field would pass just as happily against
72
+ // the flat-40 placeholder this task replaced.
73
+ const other = C.scoreFieldValues({ ...COMPLETE, [FIELD_2]: TEN })
74
+ .fields.find((f) => f.key === FIELD_2);
75
+ assert.equal(other.ratio, 10 / floorOf(FIELD_2));
76
+ assert.notEqual(other.ratio, breakdown.ratio,
77
+ 'identical text scores lower where the question asks for more');
61
78
  });
62
79
 
63
80
  test('padding earns exactly nothing: whitespace and blank lines are not substance', () => {
@@ -111,7 +128,16 @@ test('the scorer walks the declaration — a different template scores by ITS fi
111
128
  // ── the sub-bar rejection payload (BV1.R09 of goal 1000069, criterion C2) ───
112
129
 
113
130
  test('the weakest section is the LOWEST ratio, with ties going to template order', () => {
114
- const values = { ...COMPLETE, [FIELD_2]: 'thin', [FIELD_4]: 'thin' }; // two equally thin
131
+ // A GENUINE tie takes different amounts of text in each field now that the
132
+ // floors differ (BV2.R12): two-fifths of each field's OWN floor is the same
133
+ // ratio, where two identical strings would not be. The fixture asserts it is
134
+ // really a tie before asserting how the tie breaks — otherwise this case
135
+ // quietly stops testing tie-breaking at all.
136
+ const twoFifths = (key) => 'x'.repeat(Math.round(floorOf(key) * 0.4));
137
+ const values = { ...COMPLETE, [FIELD_2]: twoFifths(FIELD_2), [FIELD_4]: twoFifths(FIELD_4) };
138
+ const broken = C.scoreFieldValues(values).fields;
139
+ assert.equal(broken.find((f) => f.key === FIELD_2).ratio, broken.find((f) => f.key === FIELD_4).ratio,
140
+ 'the fixture really is a tie');
115
141
  const worst = C.weakestField(values);
116
142
  assert.equal(worst.key, FIELD_2, 'the earlier of two equal-thinnest fields, by template order');
117
143
  // strictly-thinnest wins regardless of position
@@ -61,8 +61,14 @@ function call(bodyIn) {
61
61
  });
62
62
  }
63
63
 
64
+ // Since task 1004091 (BV2.R12) the floors differ per field, so one fixed
65
+ // sentence no longer clears every one of them — this suite wants a payload that
66
+ // scores 100, so each answer is padded to the field's OWN declared floor.
64
67
  const FULL = Object.fromEntries(
65
- BASE_TEMPLATE.fields.map((f) => [f.key, `A substantive answer for ${f.key}, long enough to clear the floor.`]),
68
+ BASE_TEMPLATE.fields.map((f) => [
69
+ f.key,
70
+ `A substantive answer for ${f.key}, long enough to clear the floor.`.padEnd(f.substanceMin, '.'),
71
+ ]),
66
72
  );
67
73
 
68
74
  let passed = 0;
@@ -74,6 +74,40 @@ test('base-v2 declares the owner\'s new five, in order, and IS the store default
74
74
  }
75
75
  });
76
76
 
77
+ // ── the substance floors (task 1004091, BV2.R12, spec R21) ──────────────────
78
+ //
79
+ // The floors are a PRICE, not a style note: `completeness_score` is the mean of
80
+ // per-field ratios, and that number gates the ideator credit (economy's
81
+ // IDEA_FULL_PASS_BAR) and whether a Full Idea reaches the board at all. So the
82
+ // two claims worth pinning are the two that would be silently reverted: that the
83
+ // floors actually differ per question, and that redistributing them did not
84
+ // change what filing a Full Idea costs in total.
85
+
86
+ test('BV2.R12: v2\'s floors differ per question, and total exactly what v1 cost', () => {
87
+ const total = (t) => t.fields.reduce((sum, f) => sum + f.substanceMin, 0);
88
+ assert.equal(total(T.BASE_V2_TEMPLATE), total(T.BASE_V1_TEMPLATE),
89
+ 're-pricing what a thought costs to file is the owner\'s call, not this template\'s — '
90
+ + 'R12 distributes v1\'s total across questions that are not interchangeable, it does not move it');
91
+
92
+ const floors = T.BASE_V2_TEMPLATE.fields.map((f) => f.substanceMin);
93
+ assert.ok(new Set(floors).size > 1,
94
+ 'flat floors are the placeholder BV2.R12 replaced — the questions are not interchangeable');
95
+
96
+ // Named by key, because the REASONING is what a future edit would undo: the
97
+ // field that invites the longest answer carries the highest floor, and the
98
+ // anti-ghost-writing field — the one nobody but the filer can answer — carries
99
+ // the lowest, so no floor ever pushes an agent to pad it (the Ideator pack).
100
+ const floorOf = (key) => T.BASE_V2_TEMPLATE.fields.find((f) => f.key === key).substanceMin;
101
+ assert.equal(Math.max(...floors), floorOf('scope'), '"scope it as best as you can" asks for the most');
102
+ assert.equal(Math.min(...floors), floorOf('why_had'), '"what made you have the idea" asks for the least');
103
+
104
+ // ADR 0186's interim karma-eligibility guard, restated here so a later
105
+ // reshuffle cannot drop the template below its own economics.
106
+ assert.ok(T.BASE_V2_TEMPLATE.fields.filter((f) => f.required).length >= 3
107
+ && total(T.BASE_V2_TEMPLATE) >= 120,
108
+ 'ADR 0186 T5: ≥3 required fields and ≥120 total substanceMin to stay karma-eligible');
109
+ });
110
+
77
111
  test('the declaration is frozen — a consumer cannot fork it in place', () => {
78
112
  assert.throws(() => { T.BASE_TEMPLATE.fields.push({ key: 'rogue' }); });
79
113
  assert.throws(() => { T.BASE_V1_TEMPLATE.fields.push({ key: 'rogue' }); });