@bongos/core 1.19.225 → 1.19.226
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 +11 -11
- package/modules/ideas/completeness.js +44 -1
- package/modules/ideas/routes/inbox.js +12 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +3 -1
- package/tests/idea_completeness.mjs +30 -0
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.226",
|
|
6
|
+
"core_contract": "1.19.226",
|
|
7
|
+
"source_commit": "b99c0b52cf19ffd33ad98d0fc3dd763011559401",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-18T04:
|
|
9
|
+
"built_at": "2026-08-18T04:50:04.434Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 311,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1640,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "024eb9d71d1dd27389cb4db58a347eadfad7412b37e48d140a9c113cc8ee9234",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -3517,7 +3517,7 @@
|
|
|
3517
3517
|
{
|
|
3518
3518
|
"path": "modules/ideas/completeness.js",
|
|
3519
3519
|
"mode": "0000644",
|
|
3520
|
-
"sha256": "
|
|
3520
|
+
"sha256": "07e5666362c91f92d34388b4f6f4507cc5387866d317970478e6cae417c19975"
|
|
3521
3521
|
},
|
|
3522
3522
|
{
|
|
3523
3523
|
"path": "modules/ideas/inbox.js",
|
|
@@ -3547,7 +3547,7 @@
|
|
|
3547
3547
|
{
|
|
3548
3548
|
"path": "modules/ideas/routes/inbox.js",
|
|
3549
3549
|
"mode": "0000644",
|
|
3550
|
-
"sha256": "
|
|
3550
|
+
"sha256": "9e9288725b66b696bdc565e693c82f046bd78665ae070e3f571ff987f82a7b7b"
|
|
3551
3551
|
},
|
|
3552
3552
|
{
|
|
3553
3553
|
"path": "modules/ideas/templates.js",
|
|
@@ -4302,12 +4302,12 @@
|
|
|
4302
4302
|
{
|
|
4303
4303
|
"path": "package-lock.json",
|
|
4304
4304
|
"mode": "0000644",
|
|
4305
|
-
"sha256": "
|
|
4305
|
+
"sha256": "93e2a0f8b239aa724d99178fa5cfcd7794d7a09c9e820a9ba3f8da94a81ec2b0"
|
|
4306
4306
|
},
|
|
4307
4307
|
{
|
|
4308
4308
|
"path": "package.json",
|
|
4309
4309
|
"mode": "0000644",
|
|
4310
|
-
"sha256": "
|
|
4310
|
+
"sha256": "378978670cd1efdcc3929f44178118e321f8e381bb923aff175716af2b24134d"
|
|
4311
4311
|
},
|
|
4312
4312
|
{
|
|
4313
4313
|
"path": "public-docs/index.html",
|
|
@@ -5922,7 +5922,7 @@
|
|
|
5922
5922
|
{
|
|
5923
5923
|
"path": "src/module-api.js",
|
|
5924
5924
|
"mode": "0000644",
|
|
5925
|
-
"sha256": "
|
|
5925
|
+
"sha256": "d7a3216e0393ff4a4cb0fe3c7823b9c8a294ff45549a4a698b95c78ea93e4ad8"
|
|
5926
5926
|
},
|
|
5927
5927
|
{
|
|
5928
5928
|
"path": "src/module-loader/catalog.js",
|
|
@@ -7077,7 +7077,7 @@
|
|
|
7077
7077
|
{
|
|
7078
7078
|
"path": "tests/idea_completeness.mjs",
|
|
7079
7079
|
"mode": "0000644",
|
|
7080
|
-
"sha256": "
|
|
7080
|
+
"sha256": "7e2024b3f1fa79a6bdec55b134be6337d4867df0a77fedb205f32b783a3d6193"
|
|
7081
7081
|
},
|
|
7082
7082
|
{
|
|
7083
7083
|
"path": "tests/idea_full_capture.mjs",
|
|
@@ -69,4 +69,47 @@ function completenessScore(fieldValues, opts) {
|
|
|
69
69
|
return scoreFieldValues(fieldValues, opts).score;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
// ── the sub-bar rejection payload (BV1.R09 of goal 1000069, ADR 0175 §4) ────
|
|
73
|
+
//
|
|
74
|
+
// Criterion C2's other half: an idea that never reaches the board (sub-bar,
|
|
75
|
+
// ADR 0175 §5) still returns to its author WITH A REASON THEY CAN ACT ON. The
|
|
76
|
+
// reason is computed, not written: the per-field breakdown above already knows
|
|
77
|
+
// which section carried the least substance, so the weakest section is flagged
|
|
78
|
+
// automatically — same shape as a board objection (reason_md + section_key,
|
|
79
|
+
// government_003's vote payload), so R13's return flow renders one thing.
|
|
80
|
+
//
|
|
81
|
+
// DETERMINISTIC AND DERIVED, NEVER STORED: the scorer is deterministic, so the
|
|
82
|
+
// flag is recomputable from field_values at any time — storing it would be a
|
|
83
|
+
// second copy that drifts. Ties go to TEMPLATE ORDER (the fields array is
|
|
84
|
+
// already in it), so the flag is stable for identical inputs.
|
|
85
|
+
//
|
|
86
|
+
// BAR-AGNOSTIC BY DESIGN: whether a score is sub-bar is decided where the bar
|
|
87
|
+
// lives (economy's IDEA_FULL_PASS_BAR / the board's gate) — this constructs the
|
|
88
|
+
// payload once a caller has decided. Passing an unscorable input returns null,
|
|
89
|
+
// the same "cannot score" posture as everything above.
|
|
90
|
+
|
|
91
|
+
function weakestField(fieldValues, opts) {
|
|
92
|
+
const { fields } = scoreFieldValues(fieldValues, opts);
|
|
93
|
+
if (!fields.length) return null;
|
|
94
|
+
return fields.reduce((worst, f) => (f.ratio < worst.ratio ? f : worst));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// The rejection payload for a sub-bar Full Idea: { section_key, reason_md } in
|
|
98
|
+
// the board-objection shape. The reason names the section by its TEMPLATE LABEL
|
|
99
|
+
// (the words the author saw when writing) and states the shortfall in the
|
|
100
|
+
// scorer's own honest terms — substance against the floor, nothing smarter.
|
|
101
|
+
function weakestSectionPayload(fieldValues, { templateKey = DEFAULT_TEMPLATE_KEY, registry = BUILTIN_TEMPLATES } = {}) {
|
|
102
|
+
const worst = weakestField(fieldValues, { templateKey, registry });
|
|
103
|
+
if (!worst) return null;
|
|
104
|
+
const template = templateFor(templateKey, registry);
|
|
105
|
+
const field = template.fields.find((f) => f.key === worst.key);
|
|
106
|
+
const label = field ? field.label : worst.key;
|
|
107
|
+
return {
|
|
108
|
+
section_key: worst.key,
|
|
109
|
+
reason_md: `Completeness is below the bar, and "${label}" is the thinnest section — `
|
|
110
|
+
+ `${worst.substance} character${worst.substance === 1 ? '' : 's'} of substance against a floor of ${worst.floor}. `
|
|
111
|
+
+ 'Flesh that section out and resubmit; the score is deterministic, so the same words always score the same.',
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = { completenessScore, scoreFieldValues, substanceOf, weakestField, weakestSectionPayload };
|
|
@@ -7,7 +7,7 @@ const inbox = require('../inbox');
|
|
|
7
7
|
// Full Idea capture (task 1002919 / BV1.R143): the template validator (R141)
|
|
8
8
|
// and the deterministic scorer (R142) — both pure, both walk the declaration.
|
|
9
9
|
const { validateFieldValues } = require('../templates');
|
|
10
|
-
const { completenessScore } = require('../completeness');
|
|
10
|
+
const { completenessScore, weakestSectionPayload } = require('../completeness');
|
|
11
11
|
const { validateOrRespond, LIMITS, parseId } = api;
|
|
12
12
|
|
|
13
13
|
const INBOX_STATUS_VALUES = ['open', 'promoted', 'discarded', 'merged'];
|
|
@@ -166,6 +166,17 @@ module.exports = function buildInboxRouter() {
|
|
|
166
166
|
});
|
|
167
167
|
if (w && w.opened && w.item) {
|
|
168
168
|
idea.board_window = { item_id: w.item.id, closes_at: w.item.closes_at };
|
|
169
|
+
} else if (w && w.reason === 'below_bar') {
|
|
170
|
+
// R09 (goal 1000069, criterion C2): a sub-bar idea returns to
|
|
171
|
+
// its author WITH A REASON THEY CAN ACT ON — the weakest section
|
|
172
|
+
// auto-flagged from the per-field scores, in the same shape as a
|
|
173
|
+
// board objection. Riding the BOARD'S OWN below_bar answer keeps
|
|
174
|
+
// the bar in exactly one place (board.js); this module never
|
|
175
|
+
// decides what "sub-bar" means. Derived, not stored — the scorer
|
|
176
|
+
// is deterministic, so R13's return flow recomputes the same
|
|
177
|
+
// flag from field_values whenever it needs it.
|
|
178
|
+
const flag = weakestSectionPayload(body.field_values, templateKey ? { templateKey } : undefined);
|
|
179
|
+
if (flag) idea.rejection = flag;
|
|
169
180
|
}
|
|
170
181
|
}
|
|
171
182
|
} catch (boardErr) {
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.226",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.226",
|
|
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.
|
|
3
|
+
"version": "1.19.226",
|
|
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
|
@@ -877,8 +877,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
877
877
|
// landed since 1.19.223 with no explicit bump. run 32098083321. (task 1002620)
|
|
878
878
|
// 1.19.225 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
879
879
|
// landed since 1.19.224 with no explicit bump. run 32099793591. (task 1002620)
|
|
880
|
+
// 1.19.226 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
881
|
+
// landed since 1.19.225 with no explicit bump. run 32100543772. (task 1002620)
|
|
880
882
|
// ---------------------------------------------------------------------------
|
|
881
|
-
const CORE_VERSION = '1.19.
|
|
883
|
+
const CORE_VERSION = '1.19.226'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
882
884
|
|
|
883
885
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
884
886
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -94,5 +94,35 @@ test('the scorer walks the declaration — a different template scores by ITS fi
|
|
|
94
94
|
assert.equal(C.completenessScore(COMPLETE, opts), 0);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
|
+
// ── the sub-bar rejection payload (BV1.R09 of goal 1000069, criterion C2) ───
|
|
98
|
+
|
|
99
|
+
test('the weakest section is the LOWEST ratio, with ties going to template order', () => {
|
|
100
|
+
const values = { ...COMPLETE, big_picture: 'thin', why_matters: 'thin' }; // two equally thin
|
|
101
|
+
const worst = C.weakestField(values);
|
|
102
|
+
assert.equal(worst.key, 'big_picture', 'the earlier of two equal-thinnest fields, by template order');
|
|
103
|
+
// strictly-thinnest wins regardless of position
|
|
104
|
+
assert.equal(C.weakestField({ ...COMPLETE, fit: 'x' }).key, 'fit');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('the rejection payload is board-objection-shaped, names the template LABEL, and states the shortfall', () => {
|
|
108
|
+
const flag = C.weakestSectionPayload({ ...COMPLETE, fit: 'too thin' });
|
|
109
|
+
assert.equal(flag.section_key, 'fit', 'exactly one of the five section keys — the board-vote shape');
|
|
110
|
+
assert.ok(flag.reason_md.includes('How does the Idea Fit'), 'the reason speaks the words the author saw');
|
|
111
|
+
assert.ok(/\d+ characters? of substance against a floor of \d+/.test(flag.reason_md),
|
|
112
|
+
'the shortfall is stated in the scorer\'s own honest terms');
|
|
113
|
+
const field = BASE_TEMPLATE.fields.find((f) => f.key === 'fit');
|
|
114
|
+
assert.ok(flag.reason_md.includes(String(field.substanceMin)), 'the floor named is the template\'s real one');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('the payload is DERIVED, deterministic, and null on unscorable input — never stored, never throws', () => {
|
|
118
|
+
const values = { ...COMPLETE, purpose: 'meh' };
|
|
119
|
+
assert.deepEqual(C.weakestSectionPayload(values), C.weakestSectionPayload(values),
|
|
120
|
+
'same words, same flag — recomputable whenever R13 needs it, so storing it would only drift');
|
|
121
|
+
for (const bad of [null, undefined, 42, [], 'text']) {
|
|
122
|
+
assert.equal(C.weakestSectionPayload(bad), null, `${String(bad)} cannot be flagged`);
|
|
123
|
+
}
|
|
124
|
+
assert.equal(C.weakestSectionPayload({}, { templateKey: 'no-such-template' }), null);
|
|
125
|
+
});
|
|
126
|
+
|
|
97
127
|
console.log(`\nidea_completeness: ${passed} passed, ${failed} failed`);
|
|
98
128
|
if (failed > 0) process.exit(1);
|