@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 +11 -11
- package/modules/grading/grader-workers/worker-prompt.js +11 -2
- package/modules/grading/grader.js +62 -6
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +5 -1
- package/tests/grader.mjs +68 -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.135",
|
|
6
|
+
"core_contract": "1.19.135",
|
|
7
|
+
"source_commit": "888206791acb8371163b4909f0d01592cd8d8a1f",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-15T03:
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
4175
|
+
"sha256": "2ab902a16af6a378e0d37d108096211715103b4b1cfeccc4d8bb2f1619b5fe4a"
|
|
4176
4176
|
},
|
|
4177
4177
|
{
|
|
4178
4178
|
"path": "package.json",
|
|
4179
4179
|
"mode": "0000644",
|
|
4180
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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({
|