@bongos/core 1.19.133 → 1.19.135

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.133",
6
- "core_contract": "1.19.133",
7
- "source_commit": "ab4e04d82f1699e1781d780bf327f3da0150ddb9",
5
+ "core_version": "1.19.135",
6
+ "core_contract": "1.19.135",
7
+ "source_commit": "888206791acb8371163b4909f0d01592cd8d8a1f",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-15T03:42:15.630Z",
9
+ "built_at": "2026-08-15T03:59:48.918Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 303,
@@ -17,7 +17,7 @@
17
17
  "gate": "passed"
18
18
  },
19
19
  "file_count": 1573,
20
- "tree_sha256": "90710d328ac6f0b388a9c427df22ad7fb6fc7087ea9b5baebc708525e61aac50",
20
+ "tree_sha256": "8caf88024c497018a5d895be214220b6aa689b494d915c19cfeba7976ae93205",
21
21
  "files": [
22
22
  {
23
23
  "path": ".claude/skills/blocker-review/SKILL.md",
@@ -3012,7 +3012,7 @@
3012
3012
  {
3013
3013
  "path": "modules/grading/grader-workers/worker-prompt.js",
3014
3014
  "mode": "0000644",
3015
- "sha256": "5b614e96ed951c5eebb5c40c93612f59ff79da768055233b59790599421a70ec"
3015
+ "sha256": "986c7b039f392704613160c7ee10738030ef47972150d867e0972f6dfb761ae4"
3016
3016
  },
3017
3017
  {
3018
3018
  "path": "modules/grading/grader-workers/worker-verdict.js",
@@ -3022,7 +3022,7 @@
3022
3022
  {
3023
3023
  "path": "modules/grading/grader.js",
3024
3024
  "mode": "0000644",
3025
- "sha256": "edf6ecc725b37fe2c515174e6b06025f82fae10172e6f13d8fcfbb060c11474a"
3025
+ "sha256": "49ae9286fea73fbf67a89e42bf10a14f9fb04d7857582accaab402ad966fafd6"
3026
3026
  },
3027
3027
  {
3028
3028
  "path": "modules/grading/module.json",
@@ -4172,12 +4172,12 @@
4172
4172
  {
4173
4173
  "path": "package-lock.json",
4174
4174
  "mode": "0000644",
4175
- "sha256": "fd95462763cc12a1a060875e7ba46a46b8e3a396cbda3ad743ea926357ddfda9"
4175
+ "sha256": "2ab902a16af6a378e0d37d108096211715103b4b1cfeccc4d8bb2f1619b5fe4a"
4176
4176
  },
4177
4177
  {
4178
4178
  "path": "package.json",
4179
4179
  "mode": "0000644",
4180
- "sha256": "73ce7f53c0bab1ed2c0ab9dc6f6b8e64fadc8d01b6baa9afcfa2b10710f196a5"
4180
+ "sha256": "30e48a7fe9316928d30c21508aaadc2f8a4aa9963acc74da16f1decfeb6ef7b3"
4181
4181
  },
4182
4182
  {
4183
4183
  "path": "public-docs/index.html",
@@ -5762,7 +5762,7 @@
5762
5762
  {
5763
5763
  "path": "src/module-api.js",
5764
5764
  "mode": "0000644",
5765
- "sha256": "6d426bc93757b3261642a8969c3ee1e87ab9c2c60e2cdb74539db67759501bbd"
5765
+ "sha256": "705d95d94403240aa779f550a285fbc72f9c0729c83568dc5d28e7b4f5ceebbe"
5766
5766
  },
5767
5767
  {
5768
5768
  "path": "src/module-loader/catalog.js",
@@ -6792,7 +6792,7 @@
6792
6792
  {
6793
6793
  "path": "tests/grader.mjs",
6794
6794
  "mode": "0000644",
6795
- "sha256": "31557b08f8886f5d76201b5afbe0332e834504b67a391699798170aede9541e8"
6795
+ "sha256": "e97bbe1f1d9bc1b602adbae26e5144675a9324c5b729d0046ffa43407a88922c"
6796
6796
  },
6797
6797
  {
6798
6798
  "path": "tests/grader_bypass.mjs",
@@ -115,12 +115,21 @@ function buildWorkerPrompt({
115
115
  lines.push('---');
116
116
  lines.push(`## Post-change file contents (${contextBundle.files.length} of ${(changedFiles || []).length} changed files, bundled)`);
117
117
  lines.push('');
118
- lines.push('The post-change contents of the changed files below are included inline — use your Read tool ONLY for files not bundled here (listed after the blocks) or for unchanged context files.');
118
+ lines.push('The post-change contents of the changed files below are included inline — use your Read tool ONLY for files not bundled here (listed after the blocks), for any block marked TRUNCATED, or for unchanged context files.');
119
+ lines.push('');
120
+ // task 1002905: a TRUNCATED block is a head PREFIX, not the file. A worker
121
+ // that searched the prefix, missed a change 62k chars in, and reported it as
122
+ // absent produced five fabricated blockers on a correct ship — so say what
123
+ // truncation means and forbid the absence claim it invites.
124
+ lines.push('A block marked TRUNCATED shows only the first N characters of that file — everything past that point is ABSENT FROM THIS PROMPT, not absent from the file. Never report code as missing, unexported, unchanged, or "not applied" on the strength of a truncated block: Read the file first, and if you cannot, say so rather than asserting absence.');
119
125
  lines.push('');
120
126
  for (const f of contextBundle.files) {
121
127
  // The filename is builder-controlled — scrub it like every other
122
128
  // untrusted string that lands OUTSIDE a fence (panel hacker finding).
123
- const label = `FILE: ${scrubUntrusted(String(f.file))}${f.truncated ? ' (truncated)' : ''}`;
129
+ const truncNote = f.truncated
130
+ ? ` (TRUNCATED — first ${f.content.length} of ${f.chars_total ?? 'unknown'} chars; the rest is NOT shown)`
131
+ : '';
132
+ const label = `FILE: ${scrubUntrusted(String(f.file))}${truncNote}`;
124
133
  lines.push(...fenceBlock(label, f.content));
125
134
  lines.push('');
126
135
  }
@@ -1166,22 +1166,59 @@ const CONTEXT_BUNDLE_MAX_FILE_CHARS = 16_000;
1166
1166
  // Binary-ish extensions never worth inlining (the diff already names them).
1167
1167
  const CONTEXT_BUNDLE_SKIP_RE = /\.(?:png|jpe?g|gif|webp|ico|svgz|woff2?|ttf|eot|zip|gz|tgz|tar|pdf|mp[34]|wav|ogg|bin|exe|dll|node)$/i;
1168
1168
 
1169
+ // Added lines per file, from the unified diff. Only lines long enough to be a
1170
+ // meaningful fingerprint (a bare `}` or `});` matches anywhere) — used to prove
1171
+ // a truncated bundle prefix actually SHOWS the change (task 1002905).
1172
+ const BUNDLE_EVIDENCE_MIN_CHARS = 12;
1173
+ function diffAddedLinesByFile(diff) {
1174
+ const byFile = new Map();
1175
+ let cur = null;
1176
+ let prevWasOldHeader = false;
1177
+ for (const line of String(diff || '').split('\n')) {
1178
+ // A '+++ ' header only counts right after its '--- ' partner: a content line
1179
+ // reading '++ x' arrives as '+++ x' and must not be mistaken for a header.
1180
+ if (prevWasOldHeader && line.startsWith('+++ ')) {
1181
+ prevWasOldHeader = false;
1182
+ const p = line.slice(4).trim().replace(/^b\//, '');
1183
+ cur = p === '/dev/null' ? null : p;
1184
+ continue;
1185
+ }
1186
+ prevWasOldHeader = line.startsWith('--- ');
1187
+ if (prevWasOldHeader || line.startsWith('diff --git')) continue;
1188
+ if (!cur || !line.startsWith('+')) continue;
1189
+ const t = line.slice(1).trim();
1190
+ if (t.length < BUNDLE_EVIDENCE_MIN_CHARS) continue;
1191
+ if (!byFile.has(cur)) byFile.set(cur, []);
1192
+ byFile.get(cur).push(t);
1193
+ }
1194
+ return byFile;
1195
+ }
1196
+
1169
1197
  // Pure-ish (fs reads only, injectable): returns { files: [{file, content,
1170
- // truncated}], skipped: [file...], total_chars } for the post-change contents
1171
- // of changedFiles under cwd. Deleted/unreadable files land in skipped —
1198
+ // truncated, chars_total}], skipped: [file...], total_chars } for the post-change
1199
+ // contents of changedFiles under cwd. Deleted/unreadable files land in skipped —
1172
1200
  // their absence from the bundle is visible, never silent.
1173
1201
  //
1174
- // Security (panel hacker finding, this task): changedFiles is builder-
1202
+ // Security (panel hacker finding, task 1002660): changedFiles is builder-
1175
1203
  // controlled and git commits symlinks, so a naive readFileSync would follow a
1176
1204
  // committed link OUT of the repo (SSH keys, session tokens) and inject the
1177
1205
  // target into worker prompts + the stored grade. lstat-refuse symlinks and
1178
1206
  // non-files, and require the resolved path to stay under cwd (containment) —
1179
1207
  // a refused entry lands in skipped, visible like every other exclusion.
1180
- function buildContextBundle({ changedFiles, cwd, fsImpl = fs } = {}) {
1208
+ function buildContextBundle({ changedFiles, cwd, diff, fsImpl = fs } = {}) {
1181
1209
  const files = [];
1182
1210
  const skipped = [];
1183
1211
  let total = 0;
1184
1212
  const root = path.resolve(cwd || process.cwd());
1213
+ // task 1002905: the fabricated all-blocker FAIL. A file over the per-file cap
1214
+ // is truncated from the HEAD, so on a large file (ship.js: 250k chars) a change
1215
+ // at char 62k lands entirely outside the bundle — and the prompt presents what
1216
+ // IS bundled as "the post-change contents". A worker then searches the prefix,
1217
+ // doesn't find the new code, and reports it as absent (5 blockers, every claim
1218
+ // exactly the pre-change state). A prefix that contains none of the change is
1219
+ // worse than no bundle at all, so refuse it: skipped[] already tells the worker
1220
+ // to Read the file.
1221
+ const addedByFile = diffAddedLinesByFile(diff);
1185
1222
  for (const rel of (Array.isArray(changedFiles) ? changedFiles : [])) {
1186
1223
  if (files.length >= CONTEXT_BUNDLE_MAX_FILES || total >= CONTEXT_BUNDLE_MAX_TOTAL_CHARS) {
1187
1224
  skipped.push(rel);
@@ -1196,14 +1233,19 @@ function buildContextBundle({ changedFiles, cwd, fsImpl = fs } = {}) {
1196
1233
  if (!st.isFile() || st.isSymbolicLink()) { skipped.push(rel); continue; }
1197
1234
  content = fsImpl.readFileSync(abs, 'utf8');
1198
1235
  } catch (_) { skipped.push(rel); continue; } // deleted by the diff, or unreadable
1236
+ const charsTotal = content.length;
1199
1237
  let truncated = false;
1200
1238
  if (content.length > CONTEXT_BUNDLE_MAX_FILE_CHARS) {
1201
1239
  content = content.slice(0, CONTEXT_BUNDLE_MAX_FILE_CHARS);
1202
1240
  truncated = true;
1203
1241
  }
1242
+ if (truncated) {
1243
+ const added = addedByFile.get(rel);
1244
+ if (added && added.length && !added.some((a) => content.includes(a))) { skipped.push(rel); continue; }
1245
+ }
1204
1246
  if (total + content.length > CONTEXT_BUNDLE_MAX_TOTAL_CHARS) { skipped.push(rel); continue; }
1205
1247
  total += content.length;
1206
- files.push({ file: rel, content, truncated });
1248
+ files.push({ file: rel, content, truncated, chars_total: charsTotal });
1207
1249
  }
1208
1250
  return { files, skipped, total_chars: total };
1209
1251
  }
@@ -1727,7 +1769,9 @@ async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats
1727
1769
 
1728
1770
  // task 1002660: one read of the changed files, shared by all four workers —
1729
1771
  // kills the per-worker re-exploration that dominated panel cost.
1730
- const contextBundle = buildContextBundle({ changedFiles, cwd: runOpts.cwd });
1772
+ // diff (task 1002905): lets the bundle prove a truncated prefix actually shows
1773
+ // the change instead of handing a worker a head fragment as "post-change".
1774
+ const contextBundle = buildContextBundle({ changedFiles, cwd: runOpts.cwd, diff });
1731
1775
  // task 1002660: cache-wrapped spawns. Same prompt + model replays at $0
1732
1776
  // (llm-cache, System 5) — the prompt embeds the diff + bundle, so the key
1733
1777
  // needs no separate scope sha. Absent llm-cache → plain runSubagent.
@@ -1767,6 +1811,17 @@ async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats
1767
1811
  builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL,
1768
1812
  grader_model: graderModel,
1769
1813
  total_ms: totalMs,
1814
+ // task 1002905: what the panel could actually SEE, recorded on every grade.
1815
+ // Diagnosing the fabricated all-blocker FAIL cost a session because the grade
1816
+ // record said nothing about the read root or how much of each file the workers
1817
+ // were shown; a "the panel read the wrong tree" claim is now one field away.
1818
+ context_bundle: {
1819
+ read_root: path.resolve(runOpts.cwd || process.cwd()),
1820
+ bundled: contextBundle.files.map((f) => ({
1821
+ file: f.file, chars: f.content.length, chars_total: f.chars_total ?? null, truncated: f.truncated,
1822
+ })),
1823
+ skipped: contextBundle.skipped,
1824
+ },
1770
1825
  });
1771
1826
  }
1772
1827
 
@@ -1997,6 +2052,7 @@ module.exports = {
1997
2052
  selectGraderModel,
1998
2053
  workerModelFor, // task 1002660: per-worker tier override (cross-model-guarded)
1999
2054
  buildContextBundle, // task 1002660: shared post-change file contents, read once per panel
2055
+ diffAddedLinesByFile, // task 1002905: added-line fingerprints proving a truncated prefix shows the change
2000
2056
  validateAndScore,
2001
2057
  aggregateShots,
2002
2058
  runSubagent,
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.133",
3
+ "version": "1.19.135",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.133",
9
+ "version": "1.19.135",
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.133",
3
+ "version": "1.19.135",
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
@@ -693,8 +693,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
693
693
  // landed since 1.19.131 with no explicit bump. run 31862317758. (task 1002620)
694
694
  // 1.19.133 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
695
695
  // landed since 1.19.132 with no explicit bump. run 31862484011. (task 1002620)
696
+ // 1.19.134 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
697
+ // landed since 1.19.133 with no explicit bump. run 31862749526. (task 1002620)
698
+ // 1.19.135 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
699
+ // landed since 1.19.134 with no explicit bump. run 31863235867. (task 1002620)
696
700
  // ---------------------------------------------------------------------------
697
- const CORE_VERSION = '1.19.133'; // CI auto-patch carrier (ADR 0161); see changelog above
701
+ const CORE_VERSION = '1.19.135'; // CI auto-patch carrier (ADR 0161); see changelog above
698
702
 
699
703
  // A namespaced logger so a module's log lines are attributable + consistent.
700
704
  // Usage: const log = api.logger('dev-box'); log.info('mounted');
package/tests/grader.mjs CHANGED
@@ -908,6 +908,74 @@ test('buildContextBundle truncates oversized files and marks them', () => {
908
908
  assert.ok(bundle.files[0].content.length <= 16_000);
909
909
  });
910
910
 
911
+ // task 1002905 — the fabricated all-blocker FAIL.
912
+ //
913
+ // Task 1001916 shipped a 2-file diff from a feature worktree. ship.js is
914
+ // 250,432 chars; the change (a new helper, its call site, its export) sits at
915
+ // char ~62,000 — far past the 16,000-char per-file bundle cap. The bundle
916
+ // therefore carried ONLY lines 1-265 of ship.js, and the prompt told the worker
917
+ // to Read only files NOT bundled. The Quality worker searched the prefix, did
918
+ // not find the new symbols, and returned 5 blockers asserting the change "does
919
+ // not exist" — every claim exactly the pre-change state. (Three sibling workers
920
+ // in the SAME process with the SAME cwd read the real files and passed, which
921
+ // rules out the spawn cwd.) A truncated prefix that contains none of the change
922
+ // must never be presented as "the post-change contents".
923
+ test('buildContextBundle REFUSES a truncated prefix that shows none of the change (task 1002905)', () => {
924
+ const head = '// preamble\n'.repeat(2_000); // ~24k chars, all before the change
925
+ const big = `${head}function formatPublishFailure(data, status, branch) {\n return 'x';\n}\n`;
926
+ const diff = [
927
+ 'diff --git a/scripts/gds/ship.js b/scripts/gds/ship.js',
928
+ '--- a/scripts/gds/ship.js',
929
+ '+++ b/scripts/gds/ship.js',
930
+ '@@ -1226,0 +1226,3 @@',
931
+ '+function formatPublishFailure(data, status, branch) {',
932
+ "+ return 'x';",
933
+ '+}',
934
+ ].join('\n');
935
+ const bundle = grader.buildContextBundle({
936
+ changedFiles: ['scripts/gds/ship.js'], cwd: '/r', diff,
937
+ fsImpl: { lstatSync: () => plainFileStat, readFileSync: () => big },
938
+ });
939
+ assert.deepEqual(bundle.files, [], 'a prefix with none of the change is not bundled');
940
+ assert.deepEqual(bundle.skipped, ['scripts/gds/ship.js'], 'it lands in skipped, where the prompt tells the worker to Read it');
941
+ });
942
+
943
+ test('buildContextBundle still bundles a truncated file when the change IS visible in the prefix (task 1002905)', () => {
944
+ const big = `const NEW_HELPER_MARKER = 1;\n${'// filler\n'.repeat(4_000)}`;
945
+ const diff = [
946
+ '--- a/src/a.js',
947
+ '+++ b/src/a.js',
948
+ '@@ -1,0 +1,1 @@',
949
+ '+const NEW_HELPER_MARKER = 1;',
950
+ ].join('\n');
951
+ const bundle = grader.buildContextBundle({
952
+ changedFiles: ['src/a.js'], cwd: '/r', diff,
953
+ fsImpl: { lstatSync: () => plainFileStat, readFileSync: () => big },
954
+ });
955
+ assert.equal(bundle.files.length, 1, 'the cost saving survives when the prefix carries the change');
956
+ assert.equal(bundle.files[0].truncated, true);
957
+ assert.equal(bundle.files[0].chars_total, big.length, 'the full size rides along so the prompt can state the extent');
958
+ });
959
+
960
+ test('worker prompts mark a TRUNCATED block and exempt it from the do-not-Read rule (task 1002905)', () => {
961
+ const prompt = grader.WORKERS.quality.buildPrompt({
962
+ task: { id: 1, title: 't', description: 'd' },
963
+ valueSummary: 'v',
964
+ diff: 'diff --git a/x b/x',
965
+ changedFiles: ['src/big.js'],
966
+ contextBundle: {
967
+ files: [{ file: 'src/big.js', content: 'const a = 1;', truncated: true, chars_total: 250_432 }],
968
+ skipped: [],
969
+ total_chars: 12,
970
+ },
971
+ });
972
+ assert.ok(/FILE: src\/big\.js \(TRUNCATED/.test(prompt), 'the label says TRUNCATED, not a quiet "(truncated)"');
973
+ assert.ok(prompt.includes('250432'), 'the label states how much of the file is missing');
974
+ assert.ok(/TRUNCATED/.test(prompt.split('Post-change file contents')[1].split('```')[0]),
975
+ 'the lead instruction names truncated blocks as a Read exemption');
976
+ assert.ok(/never report code as missing/i.test(prompt), 'absence claims from a truncated block are forbidden');
977
+ });
978
+
911
979
  test('buildContextBundle REFUSES symlinks — a committed link cannot pull out-of-repo content into prompts (hacker finding)', () => {
912
980
  const files = { 'src/a.js': 'ok', 'evil-link.js': 'should never be read' };
913
981
  const bundle = grader.buildContextBundle({