@bongos/core 1.19.162 → 1.19.163
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.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.163",
|
|
6
|
+
"core_contract": "1.19.163",
|
|
7
|
+
"source_commit": "cf29eabb9874ecd380580fa762e09cf7ab412a1e",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-16T16:
|
|
9
|
+
"built_at": "2026-08-16T16:52:34.284Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 305,
|
|
13
13
|
"agent_docs_stubbed": 21,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 1262,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 1588,
|
|
20
|
+
"tree_sha256": "5c874eb035676d192575fc9366d0ddb5dac5a4af849f8c7034694ddb591d421c",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -2662,7 +2662,7 @@
|
|
|
2662
2662
|
{
|
|
2663
2663
|
"path": "modules/dev-box/app/src/vendor/bongos-client.cjs",
|
|
2664
2664
|
"mode": "0000644",
|
|
2665
|
-
"sha256": "
|
|
2665
|
+
"sha256": "257ac54c66f7969090065a34c29e2874fa6f9d111b6e75f0510ab1a2fcb3ef17"
|
|
2666
2666
|
},
|
|
2667
2667
|
{
|
|
2668
2668
|
"path": "modules/dev-box/app/test/unit.mjs",
|
|
@@ -3454,6 +3454,11 @@
|
|
|
3454
3454
|
"mode": "0000644",
|
|
3455
3455
|
"sha256": "73349f0209190305a61e2f4d771fa466c12be4f9034b4134b60a9e4a560e1c5a"
|
|
3456
3456
|
},
|
|
3457
|
+
{
|
|
3458
|
+
"path": "modules/ideas/completeness.js",
|
|
3459
|
+
"mode": "0000644",
|
|
3460
|
+
"sha256": "84d977372c79a46f8481176b7cdc740cbaca9480af3464b7b66183aad6d6e180"
|
|
3461
|
+
},
|
|
3457
3462
|
{
|
|
3458
3463
|
"path": "modules/ideas/inbox.js",
|
|
3459
3464
|
"mode": "0000644",
|
|
@@ -4197,12 +4202,12 @@
|
|
|
4197
4202
|
{
|
|
4198
4203
|
"path": "package-lock.json",
|
|
4199
4204
|
"mode": "0000644",
|
|
4200
|
-
"sha256": "
|
|
4205
|
+
"sha256": "572ae700360b73fdbc62135f01397fd335bcb5a735d113962a8343377672268c"
|
|
4201
4206
|
},
|
|
4202
4207
|
{
|
|
4203
4208
|
"path": "package.json",
|
|
4204
4209
|
"mode": "0000644",
|
|
4205
|
-
"sha256": "
|
|
4210
|
+
"sha256": "1460066c89ad9a7db9dc62ae0b6766c5e11cc18d8268fb95a4ae8767325569c5"
|
|
4206
4211
|
},
|
|
4207
4212
|
{
|
|
4208
4213
|
"path": "public-docs/index.html",
|
|
@@ -5797,7 +5802,7 @@
|
|
|
5797
5802
|
{
|
|
5798
5803
|
"path": "src/module-api.js",
|
|
5799
5804
|
"mode": "0000644",
|
|
5800
|
-
"sha256": "
|
|
5805
|
+
"sha256": "4e7edb74be33ea3e2b126dadb6a51dce083c4df6eb8c0521c0c7f484997edbe4"
|
|
5801
5806
|
},
|
|
5802
5807
|
{
|
|
5803
5808
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6904,6 +6909,11 @@
|
|
|
6904
6909
|
"mode": "0000644",
|
|
6905
6910
|
"sha256": "7d384cd84cdc93b8e7b57df291a4600a40f43346fd2a52168324ecde219f1388"
|
|
6906
6911
|
},
|
|
6912
|
+
{
|
|
6913
|
+
"path": "tests/idea_completeness.mjs",
|
|
6914
|
+
"mode": "0000644",
|
|
6915
|
+
"sha256": "e7b6af463185ea77f40d62408026c55c7fb077f18999b972564b3b5bc7656ca5"
|
|
6916
|
+
},
|
|
6907
6917
|
{
|
|
6908
6918
|
"path": "tests/idea_grades.mjs",
|
|
6909
6919
|
"mode": "0000644",
|
|
@@ -354,6 +354,8 @@ function createClient(opts = {}) {
|
|
|
354
354
|
deleteGovernanceRolesRoleKeyPermissionsPermissionKey: (args) => request("DELETE", "/governance/roles/{roleKey}/permissions/{permissionKey}", { hasBody: false }, args),
|
|
355
355
|
// PUT /governance/roles/{roleKey}/permissions/{permissionKey} — rank: archon — PUT /governance/roles/:roleKey/permissions/:permissionKey
|
|
356
356
|
putGovernanceRolesRoleKeyPermissionsPermissionKey: (args) => request("PUT", "/governance/roles/{roleKey}/permissions/{permissionKey}", { hasBody: true }, args),
|
|
357
|
+
// POST /governance/roles/{roleKey}/reset — rank: archon — POST /governance/roles/:roleKey/reset
|
|
358
|
+
postGovernanceRolesRoleKeyReset: (args) => request("POST", "/governance/roles/{roleKey}/reset", { hasBody: true }, args),
|
|
357
359
|
},
|
|
358
360
|
"grades": {
|
|
359
361
|
// GET /grades/by-builder — rank: metic+archon — GET /grades/by-builder
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// modules/ideas/completeness.js — the deterministic Full Idea completeness
|
|
2
|
+
// scorer (task 1002918, BV1.R142, goal 1000062).
|
|
3
|
+
//
|
|
4
|
+
// WHY DETERMINISTIC (the owner's call, recorded on the goal): the score feeds
|
|
5
|
+
// karma (R147), so it must be predictable, free and reproducible — the same
|
|
6
|
+
// input scores the same number on every machine, every day, with no model
|
|
7
|
+
// having a good day. An AI depth-judge is a deliberately LATER seam; nothing
|
|
8
|
+
// here calls a network or reads a clock.
|
|
9
|
+
//
|
|
10
|
+
// WHAT IT MEASURES — PRESENCE + SUBSTANCE, NOTHING SMARTER. It walks the R141
|
|
11
|
+
// template declaration (never five hard-coded names): each declared field
|
|
12
|
+
// contributes equally; a field earns its share in proportion to how much
|
|
13
|
+
// actual text it carries against the template's `substanceMin` floor, capped
|
|
14
|
+
// at 1. "Actual text" is the trimmed, whitespace-collapsed length, so a page
|
|
15
|
+
// of blank lines scores as the nothing it is. That is the whole algorithm —
|
|
16
|
+
// honest about its limit: a determined padder can beat a character counter,
|
|
17
|
+
// and catching THAT is the future AI seam (goal notes), not a cleverness this
|
|
18
|
+
// number should pretend to.
|
|
19
|
+
//
|
|
20
|
+
// NEVER THROWS INTO THE CAPTURE PATH (the task's hard requirement): malformed
|
|
21
|
+
// input — unknown template, non-object values, non-string fields — returns a
|
|
22
|
+
// null score, not an exception. A null completeness_score is a legal stored
|
|
23
|
+
// state (ideas_001_grades.sql: scoring never blocks capture), so "could not
|
|
24
|
+
// score" and "not scored yet" deliberately look the same downstream.
|
|
25
|
+
//
|
|
26
|
+
// The write side already exists (inbox.js setIdeaCompleteness, 0–100 int);
|
|
27
|
+
// capture (R143) computes-and-stores via this module, and the triage ordering
|
|
28
|
+
// (R148) reads the stored number — neither re-implements the arithmetic.
|
|
29
|
+
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const { templateFor, BUILTIN_TEMPLATES } = require('./templates');
|
|
33
|
+
const { DEFAULT_TEMPLATE_KEY } = require('./inbox');
|
|
34
|
+
|
|
35
|
+
// The substance of a value: the length of its trimmed, whitespace-collapsed
|
|
36
|
+
// text. Non-strings carry no substance (validation refuses them upstream; the
|
|
37
|
+
// scorer just declines to credit them).
|
|
38
|
+
function substanceOf(value) {
|
|
39
|
+
if (typeof value !== 'string') return 0;
|
|
40
|
+
return value.trim().replace(/\s+/g, ' ').length;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Score field_values against a template. Returns
|
|
44
|
+
// { score, fields: [{ key, substance, floor, ratio }] }
|
|
45
|
+
// where score is an integer 0–100 (ready for setIdeaCompleteness) or NULL when
|
|
46
|
+
// the input cannot be scored. `fields` is the per-field breakdown in TEMPLATE
|
|
47
|
+
// ORDER (fieldOrder's contract) — the hall's live meter (R152) and the triage
|
|
48
|
+
// view (R148) render it without redoing the arithmetic.
|
|
49
|
+
function scoreFieldValues(fieldValues, { templateKey = DEFAULT_TEMPLATE_KEY, registry = BUILTIN_TEMPLATES } = {}) {
|
|
50
|
+
const template = templateFor(templateKey, registry);
|
|
51
|
+
if (!template || !template.fields.length) return { score: null, fields: [] };
|
|
52
|
+
if (!fieldValues || typeof fieldValues !== 'object' || Array.isArray(fieldValues)) {
|
|
53
|
+
return { score: null, fields: [] };
|
|
54
|
+
}
|
|
55
|
+
const fields = template.fields.map((f) => {
|
|
56
|
+
const substance = substanceOf(fieldValues[f.key]);
|
|
57
|
+
const floor = f.substanceMin > 0 ? f.substanceMin : 1;
|
|
58
|
+
return { key: f.key, substance, floor, ratio: Math.min(1, substance / floor) };
|
|
59
|
+
});
|
|
60
|
+
const mean = fields.reduce((sum, f) => sum + f.ratio, 0) / fields.length;
|
|
61
|
+
return { score: Math.round(mean * 100), fields };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The one-number convenience for callers that only store: integer 0–100, or
|
|
65
|
+
// null when unscorable. This is what R143's capture writes through
|
|
66
|
+
// setIdeaCompleteness — and because it never throws, the capture path needs no
|
|
67
|
+
// guard around it.
|
|
68
|
+
function completenessScore(fieldValues, opts) {
|
|
69
|
+
return scoreFieldValues(fieldValues, opts).score;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { completenessScore, scoreFieldValues, substanceOf };
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.163",
|
|
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.163",
|
|
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.163",
|
|
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
|
@@ -751,8 +751,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
751
751
|
// landed since 1.19.160 with no explicit bump. run 31959299652. (task 1002620)
|
|
752
752
|
// 1.19.162 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
753
753
|
// landed since 1.19.161 with no explicit bump. run 31959496815. (task 1002620)
|
|
754
|
+
// 1.19.163 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
755
|
+
// landed since 1.19.162 with no explicit bump. run 31959864781. (task 1002620)
|
|
754
756
|
// ---------------------------------------------------------------------------
|
|
755
|
-
const CORE_VERSION = '1.19.
|
|
757
|
+
const CORE_VERSION = '1.19.163'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
756
758
|
|
|
757
759
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
758
760
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// tests/idea_completeness.mjs
|
|
2
|
+
//
|
|
3
|
+
// task 1002918 (BV1.R142, goal 1000062) — the deterministic completeness scorer.
|
|
4
|
+
//
|
|
5
|
+
// The three claims under guard, straight from the task's done-when:
|
|
6
|
+
// 1. SAME INPUT, SAME SCORE — determinism is what lets the number feed karma.
|
|
7
|
+
// 2. The empty / thin / padded / complete shapes score sensibly, with padding
|
|
8
|
+
// (whitespace, blank lines) earning exactly nothing.
|
|
9
|
+
// 3. IT NEVER THROWS — malformed input is a null score, because scoring must
|
|
10
|
+
// never block capture (the R140 schema's contract).
|
|
11
|
+
//
|
|
12
|
+
// Pure module, pure tests — no DB, no harness.
|
|
13
|
+
//
|
|
14
|
+
// Run: node tests/idea_completeness.mjs
|
|
15
|
+
|
|
16
|
+
import { strict as assert } from 'node:assert';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const C = require('../modules/ideas/completeness.js');
|
|
21
|
+
const { BASE_TEMPLATE } = require('../modules/ideas/templates.js');
|
|
22
|
+
|
|
23
|
+
let passed = 0;
|
|
24
|
+
let failed = 0;
|
|
25
|
+
function test(name, fn) {
|
|
26
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
27
|
+
catch (err) { failed++; console.error(` FAIL ${name}\n ${err.message}`); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Five answers comfortably past every substanceMin (40 chars).
|
|
31
|
+
const COMPLETE = Object.fromEntries(
|
|
32
|
+
BASE_TEMPLATE.fields.map((f) => [f.key, `A real, substantive answer for ${f.key} that clears the declared floor easily.`]),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
test('a complete idea scores 100, an empty one 0', () => {
|
|
36
|
+
assert.equal(C.completenessScore(COMPLETE), 100);
|
|
37
|
+
assert.equal(C.completenessScore({}), 0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('same input, same score — byte-for-byte determinism', () => {
|
|
41
|
+
const a = C.completenessScore(COMPLETE);
|
|
42
|
+
for (let i = 0; i < 5; i++) assert.equal(C.completenessScore({ ...COMPLETE }), a);
|
|
43
|
+
assert.deepEqual(C.scoreFieldValues(COMPLETE), C.scoreFieldValues(COMPLETE));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('a thin answer earns its proportion, not the full share', () => {
|
|
47
|
+
const oneThin = { ...COMPLETE, purpose: 'ten chars.' }; // 10 of the 40-char floor
|
|
48
|
+
const s = C.completenessScore(oneThin);
|
|
49
|
+
assert.ok(s > 80 && s < 100, `four full + one quarter-credit field ≈ 85, got ${s}`);
|
|
50
|
+
const breakdown = C.scoreFieldValues(oneThin).fields.find((f) => f.key === 'purpose');
|
|
51
|
+
assert.equal(breakdown.substance, 10);
|
|
52
|
+
assert.equal(breakdown.ratio, 0.25);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('padding earns exactly nothing: whitespace and blank lines are not substance', () => {
|
|
56
|
+
const padded = { ...COMPLETE, purpose: ' \n\n\t \n \n\n ' };
|
|
57
|
+
const b = C.scoreFieldValues(padded).fields.find((f) => f.key === 'purpose');
|
|
58
|
+
assert.equal(b.substance, 0, 'a page of blank lines is the nothing it is');
|
|
59
|
+
const spaced = { ...COMPLETE, purpose: 'a b\n\n\nc d e' }; // collapses to 9
|
|
60
|
+
const b2 = C.scoreFieldValues(spaced).fields.find((f) => f.key === 'purpose');
|
|
61
|
+
assert.equal(b2.substance, 9, 'runs of whitespace collapse to one');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('one missing field costs exactly its equal share', () => {
|
|
65
|
+
const { fit, ...four } = COMPLETE;
|
|
66
|
+
assert.equal(C.completenessScore(four), 80, 'five equal fields: one absent = -20');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('the breakdown comes back in TEMPLATE order, ready to render', () => {
|
|
70
|
+
const keys = C.scoreFieldValues(COMPLETE).fields.map((f) => f.key);
|
|
71
|
+
assert.deepEqual(keys, ['purpose', 'big_picture', 'why_had', 'why_matters', 'fit']);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('it never throws: malformed input is a null score, not an exception', () => {
|
|
75
|
+
for (const bad of [null, undefined, [], 'a string', 42]) {
|
|
76
|
+
assert.equal(C.completenessScore(bad), null, `completenessScore(${JSON.stringify(bad)})`);
|
|
77
|
+
}
|
|
78
|
+
assert.equal(C.completenessScore(COMPLETE, { templateKey: 'no-such-template' }), null);
|
|
79
|
+
// a non-string FIELD does not throw either — it just earns nothing
|
|
80
|
+
assert.equal(typeof C.completenessScore({ ...COMPLETE, purpose: 42 }), 'number');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('the scorer walks the declaration — a different template scores by ITS fields', () => {
|
|
84
|
+
const registry = {
|
|
85
|
+
'two-v1': { key: 'two-v1', label: 'Two', fields: [
|
|
86
|
+
{ key: 'a', label: 'A', required: true, substanceMin: 10 },
|
|
87
|
+
{ key: 'b', label: 'B', required: true, substanceMin: 10 },
|
|
88
|
+
] },
|
|
89
|
+
};
|
|
90
|
+
const opts = { templateKey: 'two-v1', registry };
|
|
91
|
+
assert.equal(C.completenessScore({ a: 'ten chars!', b: 'ten chars!' }, opts), 100);
|
|
92
|
+
assert.equal(C.completenessScore({ a: 'ten chars!' }, opts), 50);
|
|
93
|
+
// base fields mean nothing to it
|
|
94
|
+
assert.equal(C.completenessScore(COMPLETE, opts), 0);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
console.log(`\nidea_completeness: ${passed} passed, ${failed} failed`);
|
|
98
|
+
if (failed > 0) process.exit(1);
|