@bongos/core 1.19.450 → 1.19.451
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/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/land-watch.js +28 -9
- package/src/module-api.js +1 -1
- package/tests/land_watch.mjs +48 -3
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.451",
|
|
6
|
+
"core_contract": "1.19.451",
|
|
7
|
+
"source_commit": "f2d301bff1deca7496735a9b24aed2ced913a54a",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-
|
|
9
|
+
"built_at": "2026-09-01T19:29:54.090Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 371,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2183,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "e1ab49577ffad8bf559f652561719a2590467717b87c215918689cc2e740a31e",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -2092,7 +2092,7 @@
|
|
|
2092
2092
|
{
|
|
2093
2093
|
"path": "docs/module-api-changelog.md",
|
|
2094
2094
|
"mode": "0000644",
|
|
2095
|
-
"sha256": "
|
|
2095
|
+
"sha256": "ff76a324ccf245e1fd66a1aa9524c488fa4f54929e070c7b7d048b04521cbf0d"
|
|
2096
2096
|
},
|
|
2097
2097
|
{
|
|
2098
2098
|
"path": "docs/modules-contract.md",
|
|
@@ -6302,12 +6302,12 @@
|
|
|
6302
6302
|
{
|
|
6303
6303
|
"path": "package-lock.json",
|
|
6304
6304
|
"mode": "0000644",
|
|
6305
|
-
"sha256": "
|
|
6305
|
+
"sha256": "288c4f929ae1ec0e24e6ffbb9a911683acf04b6fe8f5e324df3c5c42c1086043"
|
|
6306
6306
|
},
|
|
6307
6307
|
{
|
|
6308
6308
|
"path": "package.json",
|
|
6309
6309
|
"mode": "0000644",
|
|
6310
|
-
"sha256": "
|
|
6310
|
+
"sha256": "dc7522ac5de90eec2d4764a2268601149b1215b3ad3d989326243cca2aab67ea"
|
|
6311
6311
|
},
|
|
6312
6312
|
{
|
|
6313
6313
|
"path": "public-docs/index.html",
|
|
@@ -6882,7 +6882,7 @@
|
|
|
6882
6882
|
{
|
|
6883
6883
|
"path": "scripts/gds/land-watch.js",
|
|
6884
6884
|
"mode": "0000644",
|
|
6885
|
-
"sha256": "
|
|
6885
|
+
"sha256": "72072f2233fdb4fe228597276421eb616077c723f4cdf1af558a29d23ecfadf4"
|
|
6886
6886
|
},
|
|
6887
6887
|
{
|
|
6888
6888
|
"path": "scripts/gds/learning-capture.js",
|
|
@@ -7957,7 +7957,7 @@
|
|
|
7957
7957
|
{
|
|
7958
7958
|
"path": "src/module-api.js",
|
|
7959
7959
|
"mode": "0000644",
|
|
7960
|
-
"sha256": "
|
|
7960
|
+
"sha256": "c9498ea93c030472ed6d09afe79001e23d0fe2c3045f7b5d51b660d0237bcdb8"
|
|
7961
7961
|
},
|
|
7962
7962
|
{
|
|
7963
7963
|
"path": "src/module-loader/catalog.js",
|
|
@@ -9627,7 +9627,7 @@
|
|
|
9627
9627
|
{
|
|
9628
9628
|
"path": "tests/land_watch.mjs",
|
|
9629
9629
|
"mode": "0000644",
|
|
9630
|
-
"sha256": "
|
|
9630
|
+
"sha256": "b1851f3c0965f1bcfea060675d7e28ec7a0787e99dda7f1c3782ed2c0a6b4454"
|
|
9631
9631
|
},
|
|
9632
9632
|
{
|
|
9633
9633
|
"path": "tests/landing_legal_pages.mjs",
|
|
@@ -1337,5 +1337,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1337
1337
|
landed since 1.19.448 with no explicit bump. run 33406714743. (task 1002620)
|
|
1338
1338
|
1.19.450 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1339
1339
|
landed since 1.19.449 with no explicit bump. run 33414986466. (task 1002620)
|
|
1340
|
+
1.19.451 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1341
|
+
landed since 1.19.450 with no explicit bump. run 33549781105. (task 1002620)
|
|
1340
1342
|
---------------------------------------------------------------------------
|
|
1341
1343
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.451",
|
|
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.451",
|
|
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.451",
|
|
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",
|
|
@@ -78,14 +78,21 @@ function removeBreadcrumb(fileOrId) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// PURE decision — exported so the truth table is unit-tested with no fs/network.
|
|
81
|
-
// 'emit' → the task
|
|
82
|
-
// 'drop' → give up watching:
|
|
83
|
-
// completed, released, …) so the confirmed→shipped transition won't happen.
|
|
81
|
+
// 'emit' → the task landed inside the watch window: render the shipped card, then clear.
|
|
82
|
+
// 'drop' → give up watching: past the window, OR it left the land path (grade-failed back
|
|
83
|
+
// to completed, released, …) so the confirmed→shipped transition won't happen.
|
|
84
84
|
// 'keep' → still landing (confirmed), or a transient read failure — retry next turn.
|
|
85
|
-
//
|
|
85
|
+
//
|
|
86
|
+
// The window wins over EVERY status, 'shipped' included (task 1003466). It used not to:
|
|
87
|
+
// 'emit' was checked first, so a breadcrumb for a task that landed days ago still rendered a
|
|
88
|
+
// "Shipped" banner. That went unnoticed until a dead publish-reconciler (task 1002419) let a
|
|
89
|
+
// backlog build — eleven tasks flipped to shipped at once and the builder paid a prompt-turn
|
|
90
|
+
// of stale news for each. A land the builder has already moved past is not news; it is spam.
|
|
91
|
+
// An unknown/unparseable age is treated as past the window for the same reason: we cannot
|
|
92
|
+
// prove the crumb is fresh, and the safe direction is always silence.
|
|
86
93
|
function classifyLand(task, ageMs, { staleMs = DEFAULT_STALE_MS } = {}) {
|
|
94
|
+
if (!Number.isFinite(ageMs) || ageMs > staleMs) return 'drop';
|
|
87
95
|
if (task && task.status === 'shipped') return 'emit';
|
|
88
|
-
if (typeof ageMs === 'number' && ageMs > staleMs) return 'drop';
|
|
89
96
|
if (!task || typeof task.status !== 'string') return 'keep'; // couldn't read → retry
|
|
90
97
|
if (task.status === 'confirmed') return 'keep'; // still landing
|
|
91
98
|
return 'drop'; // no longer on the land path
|
|
@@ -126,21 +133,33 @@ function buildShippedEnvelope(crumb, task) {
|
|
|
126
133
|
|
|
127
134
|
// Walk the breadcrumbs; emit AT MOST ONE shipped card per run (avoid flooding a turn),
|
|
128
135
|
// dropping the give-ups. Returns the envelope to inject, or null. Deps injected for tests.
|
|
136
|
+
//
|
|
137
|
+
// The backlog drains in THIS pass (task 1003466): the newest land gets the card and every
|
|
138
|
+
// other landed crumb is folded into one "also landed" line and cleared. Holding the extras
|
|
139
|
+
// for later turns is what turned a backlog into a card per prompt — the builder had to ask
|
|
140
|
+
// why the session kept showing cards instead of working.
|
|
129
141
|
async function watch({ nowMs = Date.now(), staleMs = DEFAULT_STALE_MS, getTask = fetchTask, readCrumbs = readBreadcrumbs, removeCrumb = removeBreadcrumb } = {}) {
|
|
130
142
|
const crumbs = readCrumbs().sort((a, b) => String(b.at).localeCompare(String(a.at))); // newest first
|
|
131
143
|
let envelope = null;
|
|
144
|
+
const alsoLanded = [];
|
|
132
145
|
for (const crumb of crumbs) {
|
|
133
|
-
const
|
|
146
|
+
const at = Date.parse(crumb.at || '');
|
|
147
|
+
const ageMs = Number.isFinite(at) ? nowMs - at : Infinity; // unparseable → past the window
|
|
134
148
|
const task = await getTask(crumb.taskId);
|
|
135
|
-
const verdict = classifyLand(task,
|
|
149
|
+
const verdict = classifyLand(task, ageMs, { staleMs });
|
|
136
150
|
if (verdict === 'emit') {
|
|
137
|
-
if (!envelope)
|
|
138
|
-
|
|
151
|
+
if (!envelope) envelope = buildShippedEnvelope(crumb, task);
|
|
152
|
+
else alsoLanded.push(crumb.taskId);
|
|
153
|
+
removeCrumb(crumb._file);
|
|
139
154
|
} else if (verdict === 'drop') {
|
|
140
155
|
removeCrumb(crumb._file);
|
|
141
156
|
}
|
|
142
157
|
// 'keep' → leave it
|
|
143
158
|
}
|
|
159
|
+
// Written as `task N`, never `#N` — a bare #NNN autolinks to a GitHub issue and 404s.
|
|
160
|
+
if (envelope && alsoLanded.length) {
|
|
161
|
+
envelope.warnings = [`Also landed: ${alsoLanded.map((id) => `task ${id}`).join(', ')}.`];
|
|
162
|
+
}
|
|
144
163
|
return envelope;
|
|
145
164
|
}
|
|
146
165
|
|
package/src/module-api.js
CHANGED
|
@@ -33,7 +33,7 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
33
33
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
34
34
|
// the entry to that file. Look for a version's history there, not here.
|
|
35
35
|
// ---------------------------------------------------------------------------
|
|
36
|
-
const CORE_VERSION = '1.19.
|
|
36
|
+
const CORE_VERSION = '1.19.451'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
37
37
|
|
|
38
38
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
39
39
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/land_watch.mjs
CHANGED
|
@@ -28,9 +28,20 @@ async function ta(name, fn) {
|
|
|
28
28
|
const STALE = 1000;
|
|
29
29
|
|
|
30
30
|
// ── classifyLand truth table ─────────────────────────────────────────────────────────
|
|
31
|
-
t('shipped
|
|
31
|
+
t('shipped inside the watch window → emit', () => {
|
|
32
32
|
assert.equal(classifyLand({ status: 'shipped' }, 10, { staleMs: STALE }), 'emit');
|
|
33
|
-
|
|
33
|
+
});
|
|
34
|
+
// task 1003466 — the card-spam bug. 'emit' used to beat 'stale', so a breadcrumb for a
|
|
35
|
+
// task that landed days ago still rendered a "Shipped" banner. With watch() emitting one
|
|
36
|
+
// card per run, a backlog of N stale crumbs cost the builder N prompt-turns of stale news.
|
|
37
|
+
t('shipped but PAST the watch window → drop (a land the builder moved past is not news)', () => {
|
|
38
|
+
assert.equal(classifyLand({ status: 'shipped' }, 999999, { staleMs: STALE }), 'drop');
|
|
39
|
+
assert.equal(classifyLand({ status: 'shipped' }, STALE + 1, { staleMs: STALE }), 'drop');
|
|
40
|
+
assert.equal(classifyLand({ status: 'shipped' }, STALE, { staleMs: STALE }), 'emit', 'exactly at the boundary still emits');
|
|
41
|
+
});
|
|
42
|
+
t('an unknown age → drop (an unparseable breadcrumb must not be immortal)', () => {
|
|
43
|
+
assert.equal(classifyLand({ status: 'shipped' }, Infinity, { staleMs: STALE }), 'drop');
|
|
44
|
+
assert.equal(classifyLand({ status: 'confirmed' }, NaN, { staleMs: STALE }), 'drop');
|
|
34
45
|
});
|
|
35
46
|
t('confirmed (still landing) → keep', () => {
|
|
36
47
|
assert.equal(classifyLand({ status: 'confirmed' }, 10, { staleMs: STALE }), 'keep');
|
|
@@ -97,7 +108,41 @@ await ta('emits the NEWEST shipped card, keeps confirmed, drops the rest — one
|
|
|
97
108
|
assert.equal(env.title, 'task_3_card', 'newest shipped wins');
|
|
98
109
|
assert.ok(removed.includes('f3'), 'the emitted crumb is cleared');
|
|
99
110
|
assert.ok(!removed.includes('f2'), 'the still-confirmed crumb is kept');
|
|
100
|
-
assert.ok(
|
|
111
|
+
assert.ok(removed.includes('f1'), 'the 2nd shipped is drained in the SAME pass, not held for another turn');
|
|
112
|
+
assert.ok(/task 1\b/.test((env.warnings || []).join(' ')), 'the 2nd shipped is folded into one warning line');
|
|
113
|
+
});
|
|
114
|
+
// task 1003466 — the whole backlog drains in ONE pass. Before this, five landed tasks cost
|
|
115
|
+
// five prompt-turns of cards; now it is one card plus a single "also landed" line.
|
|
116
|
+
await ta('a backlog of shipped crumbs drains in one pass: one card + one also-landed line', async () => {
|
|
117
|
+
const removed = [];
|
|
118
|
+
const crumbs = ['1', '2', '3', '4', '5'].map((id, i) => ({
|
|
119
|
+
taskId: id, at: `2026-07-01T00:00:0${i + 1}.000Z`, _file: `f${id}`, cardInputs: {},
|
|
120
|
+
}));
|
|
121
|
+
const env = await watch({
|
|
122
|
+
nowMs: Date.parse('2026-07-01T00:00:05.500Z'),
|
|
123
|
+
staleMs: 60_000,
|
|
124
|
+
getTask: async () => ({ status: 'shipped' }),
|
|
125
|
+
readCrumbs: () => crumbs,
|
|
126
|
+
removeCrumb: (f) => removed.push(f),
|
|
127
|
+
});
|
|
128
|
+
assert.equal(env.title, 'task_5_card', 'the newest land is the one that gets a card');
|
|
129
|
+
assert.equal(removed.length, 5, 'every crumb is cleared in this single pass');
|
|
130
|
+
const warn = (env.warnings || []).join(' ');
|
|
131
|
+
for (const id of ['1', '2', '3', '4']) assert.ok(warn.includes(`task ${id}`), `task ${id} named in the warning`);
|
|
132
|
+
assert.ok(!warn.includes('task 5'), 'the carded task is not repeated in the warning');
|
|
133
|
+
assert.ok(!/#d/.test(warn), 'never a bare #NNN in emitted prose (it autolinks to a GitHub issue)');
|
|
134
|
+
});
|
|
135
|
+
await ta('stale shipped crumbs are dropped silently — no card, no warning', async () => {
|
|
136
|
+
const removed = [];
|
|
137
|
+
const env = await watch({
|
|
138
|
+
nowMs: Date.parse('2026-07-08T00:00:00.000Z'), // a week later
|
|
139
|
+
staleMs: 30 * 60 * 1000,
|
|
140
|
+
getTask: async () => ({ status: 'shipped' }),
|
|
141
|
+
readCrumbs: () => [{ taskId: '77', at: '2026-07-01T00:00:00.000Z', _file: 'f77', cardInputs: {} }],
|
|
142
|
+
removeCrumb: (f) => removed.push(f),
|
|
143
|
+
});
|
|
144
|
+
assert.equal(env, null, 'a week-old land renders nothing');
|
|
145
|
+
assert.ok(removed.includes('f77'), 'and the crumb is cleared so it never returns');
|
|
101
146
|
});
|
|
102
147
|
await ta('no shipped → no envelope; drops the give-ups', async () => {
|
|
103
148
|
const removed = [];
|