@bongos/core 1.20.12 → 1.20.13

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.20.12",
6
- "core_contract": "1.20.12",
7
- "source_commit": "157de30a7a166f1941ba54aaa8f57964f7f73362",
5
+ "core_version": "1.20.13",
6
+ "core_contract": "1.20.13",
7
+ "source_commit": "1e8134c4bbd7d8623139bfd31ba2701c5bf120ac",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-09-30T11:33:34.437Z",
9
+ "built_at": "2026-09-30T11:48:47.948Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 556,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 3208,
20
- "tree_sha256": "0da694c2943c85befcb0d6d52c1d9ae4480c66b425a0393140773878b4483763",
20
+ "tree_sha256": "ae284b5e2f56e08cb0b29b13e77a015dc35db8f5be3a99a96a731704c7da0053",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/ask-for-help/SKILL.md",
@@ -2772,7 +2772,7 @@
2772
2772
  {
2773
2773
  "path": "docs/module-api-changelog.md",
2774
2774
  "mode": "0000644",
2775
- "sha256": "86429e308db13d625c1255ae509c712448f405ada592fc1ea1c2fe6fe8866d46"
2775
+ "sha256": "a0725bf013ddc823d06b866f5008497dddf1bf63e2e22852cd6eb79525ce5052"
2776
2776
  },
2777
2777
  {
2778
2778
  "path": "docs/modules-contract.md",
@@ -8862,12 +8862,12 @@
8862
8862
  {
8863
8863
  "path": "package-lock.json",
8864
8864
  "mode": "0000644",
8865
- "sha256": "464fd91cbcd4c95177dde0d4e40720505686cfd677a575c0f7d7a8415f9a957b"
8865
+ "sha256": "09eb2f9f11cdcb5cb24ccddee43c9d09d5e1135a452e0827542ea96d70b2e85f"
8866
8866
  },
8867
8867
  {
8868
8868
  "path": "package.json",
8869
8869
  "mode": "0000644",
8870
- "sha256": "2f825d119f8d7b46e1f4ffab61515532848aa2634b53b6b66adbf1581bffc3b3"
8870
+ "sha256": "c80938c396a4982970d0850791d002ea2f5c49b93c8e75f6066c33c77bc7ae78"
8871
8871
  },
8872
8872
  {
8873
8873
  "path": "public-docs/index.html",
@@ -8887,7 +8887,7 @@
8887
8887
  {
8888
8888
  "path": "release-notes.json",
8889
8889
  "mode": "0000644",
8890
- "sha256": "fdb871eaf32d4f1b1525a225cc5b28207359df70e1714f735eb060fac12743aa"
8890
+ "sha256": "a3de1adc96eea0bd98814403baf635fc2523506d45ca0df0ae263c917b324757"
8891
8891
  },
8892
8892
  {
8893
8893
  "path": "scripts/bongos-mcp.js",
@@ -9047,7 +9047,7 @@
9047
9047
  {
9048
9048
  "path": "scripts/gds/autobongos-run.js",
9049
9049
  "mode": "0000644",
9050
- "sha256": "daa234935da267393e61dd24e0ace8b0e23a8bd06a0814c6a25e9d00f4a56736"
9050
+ "sha256": "5467681b59e569c4d7fccd4ed6ea93cd598a26ddd2fd42c384375f37ae0243ea"
9051
9051
  },
9052
9052
  {
9053
9053
  "path": "scripts/gds/autobongos-service.cmd",
@@ -9062,7 +9062,7 @@
9062
9062
  {
9063
9063
  "path": "scripts/gds/autobongos-verify.js",
9064
9064
  "mode": "0000644",
9065
- "sha256": "3e8da5c85aded9368171612fc5ba7beabecc8ef60b7c7d12c3c5bd58af222e12"
9065
+ "sha256": "0be5358189145464d2893cebd53057b9fb35be94c2b56bb1b09f386c1f5a6252"
9066
9066
  },
9067
9067
  {
9068
9068
  "path": "scripts/gds/autonomy-gate.js",
@@ -11002,7 +11002,7 @@
11002
11002
  {
11003
11003
  "path": "src/module-api.js",
11004
11004
  "mode": "0000644",
11005
- "sha256": "159b8f961396d15ce8bf1a5589ec100b0b9b1d0bc02ccb8b1c37a8ce82512dd7"
11005
+ "sha256": "20017aec62763445738eeed4b8c1d23cbe6789a77223992022cb4c9e7747111c"
11006
11006
  },
11007
11007
  {
11008
11008
  "path": "src/module-loader/catalog.js",
@@ -11447,12 +11447,12 @@
11447
11447
  {
11448
11448
  "path": "tests/autobongos_loop.mjs",
11449
11449
  "mode": "0000644",
11450
- "sha256": "9debac618f88be1bb87220c66924c441cffcc5f403867de66622a9a2fc3a98e3"
11450
+ "sha256": "e14d2c211c28ebada8e915d0d336989e74410afb68924d9b89dc8efd666457c8"
11451
11451
  },
11452
11452
  {
11453
11453
  "path": "tests/autobongos_verify.mjs",
11454
11454
  "mode": "0000644",
11455
- "sha256": "aeec5016fbdbe11d4067ba76e97e5f0a3b4e33bf2a1fafe050cd456ac76b1a66"
11455
+ "sha256": "8b0958dba2589ca94671843a30d317da35b76df6eff9ab75660a0a253f98b7d3"
11456
11456
  },
11457
11457
  {
11458
11458
  "path": "tests/autonomous_fit_protected_paths.mjs",
@@ -2681,5 +2681,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
2681
2681
  landed since 1.20.10 with no explicit bump. run 36672606449. (task 1002620)
2682
2682
  1.20.12 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2683
2683
  landed since 1.20.11 with no explicit bump. run 36709211337. (task 1002620)
2684
+ 1.20.13 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
2685
+ landed since 1.20.12 with no explicit bump. run 36710762884. (task 1002620)
2684
2686
  ---------------------------------------------------------------------------
2685
2687
  ```
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.20.12",
3
+ "version": "1.20.13",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.20.12",
9
+ "version": "1.20.13",
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.20.12",
3
+ "version": "1.20.13",
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",
@@ -8124,5 +8124,11 @@
8124
8124
  "id": "1004190",
8125
8125
  "text": "The task list no longer offers you work the system will refuse: a job waiting on another job that hasn't finished yet is now hidden until it's actually ready to start."
8126
8126
  }
8127
+ ],
8128
+ "1.20.13": [
8129
+ {
8130
+ "id": "1004180",
8131
+ "text": "When the unattended runner fails to pick up a task, its log now always says why, starting with the sentence that explains it, instead of an empty line or a fragment cut off mid-word."
8132
+ }
8127
8133
  ]
8128
8134
  }
@@ -42,7 +42,7 @@ const gauge = require('./autonomy-gauge.js');
42
42
  const {
43
43
  buildWorkerArgs, buildWorkerPrompt, classifyRun, DEFAULT_MODEL, DEFAULT_WORKER_TIMEOUT_MS,
44
44
  } = require('./autobongos-loop.js');
45
- const { verifyShip } = require('./autobongos-verify.js');
45
+ const { verifyShip, failureReason } = require('./autobongos-verify.js');
46
46
  const fence = require('../../modules/autonomy/fence.js');
47
47
  // The ADR 0043 protected-path registry, reused rather than re-derived. scripts/
48
48
  // sits outside the kernel boundary, so this direct core require is allowed here
@@ -466,7 +466,7 @@ async function iteration(opts, deps = {}) {
466
466
  const wtName = deps.worktreeName ? deps.worktreeName(task.id) : worktreeName(task.id);
467
467
  const made = await runCmd(process.execPath, [path.join('scripts', 'gds', 'worktree.js'), 'add', wtName, '--base', 'origin/main']);
468
468
  if (!made.ok) {
469
- return log({ event: 'worktree_failed', task_id: task.id, worktree: wtName, reason: (made.stderr || made.stdout).slice(-400) });
469
+ return log({ event: 'worktree_failed', task_id: task.id, worktree: wtName, reason: failureReason(made, 'worktree.js') });
470
470
  }
471
471
  const wtPath = path.join(REPO_ROOT, '.claude', 'worktrees', wtName);
472
472
 
@@ -475,7 +475,7 @@ async function iteration(opts, deps = {}) {
475
475
  const claimed = await runCmd(process.execPath, [path.join(REPO_ROOT, 'scripts', 'gds', 'claim.js'), String(task.id)], { cwd: wtPath });
476
476
  if (!claimed.ok) {
477
477
  await runCmd(process.execPath, [path.join('scripts', 'gds', 'worktree.js'), 'remove', wtName]);
478
- return log({ event: 'claim_failed', task_id: task.id, worktree: wtName, reason: (claimed.stderr || claimed.stdout).slice(-400) });
478
+ return log({ event: 'claim_failed', task_id: task.id, worktree: wtName, reason: failureReason(claimed, 'claim.js') });
479
479
  }
480
480
 
481
481
  // 5. Work, in that tree.
@@ -966,5 +966,5 @@ module.exports = {
966
966
  readArgv, pickTask, iteration, record, logPath, spawnWorker, workerEnv, WORKER_ENV_ALLOW, resolveWorkerBin,
967
967
  worktreeName, readFence, reconcileLeftoverClaims, pollSignals, waitOrJump, forever, CLAIM_PREFIX, countWorkedSince, codeDrifted, loadedHead, UPGRADE_EXIT_CODE, MIN_UPTIME_BEFORE_UPGRADE_MS,
968
968
  heartbeatPath, writeHeartbeat, readHeartbeat, pidAlive, anotherRunnerIsAlive, HEARTBEAT_STALE_MS,
969
- isRunnerTree, RUNNER_TREE_RE, postHeartbeat, RUNNER_STARTED_AT,
969
+ isRunnerTree, RUNNER_TREE_RE, postHeartbeat, RUNNER_STARTED_AT, failureReason,
970
970
  };
@@ -40,6 +40,31 @@ const { execFile } = require('node:child_process');
40
40
 
41
41
  const { classifyStrand, WATCHED } = require('./strand-watch.js');
42
42
 
43
+ // failureReason — what a failed child process said, fit for one run-log field
44
+ // (task 1004180).
45
+ //
46
+ // Two defects this replaces, both seen live. `(stderr || stdout).slice(-400)`
47
+ // logged reason:"" when a child failed with nothing on either stream — which
48
+ // reads as "the cause was captured and it was nothing". And it kept only the
49
+ // TAIL, so a long refusal card lost its first line, the one that says WHY, and
50
+ // the log began mid-word ("ot look for a way round it."). So: prefer stderr,
51
+ // else stdout; collapse whitespace so a multi-line card reads as one line; keep
52
+ // the HEAD plus a short tail with a marked cut; and when there is no text at all,
53
+ // say which program exited with which code rather than saying nothing.
54
+ function failureReason(res, what, max = 400) {
55
+ const flat = (s) => String(s || '').replace(/\s+/g, ' ').trim();
56
+ const text = flat(res && res.stderr) || flat(res && res.stdout);
57
+ if (!text) {
58
+ const code = res && res.code !== undefined && res.code !== null ? res.code : '?';
59
+ return `${what} exited ${code} with no output`;
60
+ }
61
+ if (text.length <= max) return text;
62
+ const cut = ' … ';
63
+ const tail = Math.floor((max - cut.length) / 3);
64
+ const head = max - cut.length - tail;
65
+ return text.slice(0, head) + cut + text.slice(text.length - tail);
66
+ }
67
+
43
68
  const REPO_ROOT = path.resolve(__dirname, '..', '..');
44
69
 
45
70
  // Statuses a claim can still be sitting in when the worker is already gone.
@@ -318,7 +343,7 @@ async function verifyShip({ taskId, taskTitle = null, worker = null }, deps = {}
318
343
  else {
319
344
  const r = await release(taskId, `autobongos: ${v.shape} — ${String(v.reason || '').slice(0, 180)}`);
320
345
  out.released = !!(r && r.ok);
321
- if (!out.released) out.release_error = (r && (r.stderr || r.stdout) ? String(r.stderr || r.stdout).slice(-300) : 'release failed');
346
+ if (!out.released) out.release_error = failureReason(r, 'release.js', 300);
322
347
  }
323
348
  }
324
349
 
@@ -374,4 +399,4 @@ if (require.main === module) {
374
399
  });
375
400
  }
376
401
 
377
- module.exports = { classifyLedger, blockerFor, probeArtifact, fileBlocker, verifyShip };
402
+ module.exports = { classifyLedger, blockerFor, probeArtifact, fileBlocker, verifyShip, failureReason };
package/src/module-api.js CHANGED
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
75
75
  // MAJOR (see allowBoxScope below): passes the request through untouched.
76
76
  function deprecatedNoopMiddleware(_req, _res, next) { next(); }
77
77
 
78
- const CORE_VERSION = '1.20.12'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
78
+ const CORE_VERSION = '1.20.13'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
79
79
 
80
80
  // A namespaced logger so a module's log lines are attributable + consistent.
81
81
  // Usage: const log = api.logger('discord'); log.info('mounted');
@@ -381,6 +381,47 @@ test('a refused claim tears the tree down again and never spawns a worker', asyn
381
381
  assert.match(row.reason, /ALREADY_CLAIMED/);
382
382
  });
383
383
 
384
+ // task 1004180 — observed live: reason:"" when claim.js failed with nothing on
385
+ // either stream, and reasons that began mid-word ("ot look for a way round it.")
386
+ // because `.slice(-400)` kept only the TAIL of a long refusal card.
387
+ test('a claim that fails SILENTLY still says what happened, never an empty reason', async () => {
388
+ const h = harness({ run: async (bin, args) => {
389
+ const script = String(args[0]).split(/[\\/]/).pop();
390
+ return script === 'claim.js' ? { ok: false, code: 3, stdout: '', stderr: ' ' } : { ok: true, code: 0, stdout: '', stderr: '' };
391
+ } });
392
+ const row = await runner.iteration(opts, h.deps);
393
+ assert.equal(row.event, 'claim_failed');
394
+ assert.equal(row.reason, 'claim.js exited 3 with no output');
395
+ });
396
+
397
+ test('a worktree that cannot be made says so even with no output', async () => {
398
+ const h = harness({ run: async () => ({ ok: false, code: 128, stdout: '', stderr: '' }) });
399
+ const row = await runner.iteration(opts, h.deps);
400
+ assert.equal(row.event, 'worktree_failed');
401
+ assert.equal(row.reason, 'worktree.js exited 128 with no output');
402
+ });
403
+
404
+ test('a long refusal keeps its HEAD — the part that says why — not just the tail', async () => {
405
+ const why = 'Your work changes the tools that would be used to update it, so the automatic update was refused on purpose.';
406
+ const long = why + ' ' + 'x'.repeat(2000) + ' By design: Yes. Do not look for a way round it.';
407
+ const h = harness({ run: async (bin, args) => {
408
+ const script = String(args[0]).split(/[\\/]/).pop();
409
+ return script === 'claim.js' ? { ok: false, code: 1, stdout: '', stderr: long } : { ok: true, code: 0, stdout: '', stderr: '' };
410
+ } });
411
+ const row = await runner.iteration(opts, h.deps);
412
+ assert.ok(row.reason.startsWith(why), 'the reason must start where the message starts');
413
+ assert.ok(row.reason.endsWith('Do not look for a way round it.'), 'and still carry the tail');
414
+ assert.ok(row.reason.includes('…'), 'the cut is marked');
415
+ assert.ok(row.reason.length <= 400, `bounded, got ${row.reason.length}`);
416
+ });
417
+
418
+ test('failureReason: stdout is used when stderr is empty, whitespace is collapsed, short text is untouched', () => {
419
+ assert.equal(runner.failureReason({ code: 1, stdout: 'line one' + String.fromCharCode(10) + ' line two', stderr: '' }, 'claim.js'), 'line one line two');
420
+ assert.equal(runner.failureReason({ code: 1, stdout: 'ignored', stderr: 'wins' }, 'claim.js'), 'wins');
421
+ assert.equal(runner.failureReason(null, 'claim.js'), 'claim.js exited ? with no output');
422
+ assert.equal(runner.failureReason({ code: 1, stderr: 'a'.repeat(400) }, 'x').length, 400);
423
+ });
424
+
384
425
  test('a worker that cannot finish frees the claim; one that really shipped does not', async () => {
385
426
  // Which of these happens is now decided by the LEDGER, not by the worker's own
386
427
  // verdict: a stuck worker leaves the task still `active`, a finished one leaves
@@ -265,6 +265,28 @@ test('a release that fails does not get reported as a release', async () => {
265
265
  assert.equal(out.blocker.linked, false);
266
266
  });
267
267
 
268
+ // task 1004180 — the release error used to keep only the last 300 characters,
269
+ // cutting off the start of the message, which is the part that says why.
270
+ test('a long release error keeps its head, and an empty one still names the exit', async () => {
271
+ const why = 'Only the original session can release it.';
272
+ const long = await verifyShip({ taskId: 42 }, {
273
+ api: fakeApi(),
274
+ task: task({ id: '42', status: 'active' }),
275
+ release: async () => ({ ok: false, code: 1, stderr: why + ' ' + 'y'.repeat(1000) }),
276
+ fileBlocker: async (_a, _r, opts) => ({ filed: true, blockerId: 7, linked: opts.link }),
277
+ });
278
+ assert.ok(long.release_error.startsWith(why), long.release_error.slice(0, 60));
279
+ assert.ok(long.release_error.length <= 300);
280
+
281
+ const silent = await verifyShip({ taskId: 42 }, {
282
+ api: fakeApi(),
283
+ task: task({ id: '42', status: 'active' }),
284
+ release: async () => ({ ok: false, code: 2, stdout: '', stderr: '' }),
285
+ fileBlocker: async (_a, _r, opts) => ({ filed: true, blockerId: 7, linked: opts.link }),
286
+ });
287
+ assert.equal(silent.release_error, 'release.js exited 2 with no output');
288
+ });
289
+
268
290
  test('the artifact probe only runs when the ledger claims success', async () => {
269
291
  let probes = 0;
270
292
  const probeArtifactSpy = async () => { probes += 1; return notOnMain; };