@bongos/core 1.19.134 → 1.19.136

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.134",
6
- "core_contract": "1.19.134",
7
- "source_commit": "7823db141a9cd6375e337f63779958b76c9b064b",
5
+ "core_version": "1.19.136",
6
+ "core_contract": "1.19.136",
7
+ "source_commit": "e8baa6b4bb7636ec78d0d37a48fafb861e716dad",
8
8
  "source_ref": "HEAD",
9
- "built_at": "2026-08-15T03:48:20.281Z",
9
+ "built_at": "2026-08-15T14:17:27.560Z",
10
10
  "redaction": {
11
11
  "model": "docs-redacted+functional-verbatim",
12
12
  "docs_redacted": 303,
13
13
  "agent_docs_stubbed": 21,
14
- "functional_verbatim": 1249,
14
+ "functional_verbatim": 1250,
15
15
  "rules": 3,
16
16
  "gate_literals": 3,
17
17
  "gate": "passed"
18
18
  },
19
- "file_count": 1573,
20
- "tree_sha256": "545ba84996f88bbbb913baafcf7d5dd429dff68ba6178562f03a90efcdf2e807",
19
+ "file_count": 1574,
20
+ "tree_sha256": "c4c465a8bb9c113e17dd02666d74907f5d4057d06743e14d73bcc57ff7482290",
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",
@@ -3239,15 +3239,20 @@
3239
3239
  "mode": "0000644",
3240
3240
  "sha256": "9df48fd541deabb6765cb622fc00f829edc4d6605c746f28a6d76cbfc07e22ad"
3241
3241
  },
3242
+ {
3243
+ "path": "modules/hall-ui/public/my-projects.css",
3244
+ "mode": "0000644",
3245
+ "sha256": "5c702a9c2054287a8523b6901cd46e7caa1cb9580b7e8f04a3898b2c8bd82fd0"
3246
+ },
3242
3247
  {
3243
3248
  "path": "modules/hall-ui/public/my-projects.html",
3244
3249
  "mode": "0000644",
3245
- "sha256": "9a0fc70a8cabe59d8888a863fa9339d43f3ad2ae7d980f69c9f5fa0c81283d4b"
3250
+ "sha256": "49c63ef48e0fc6450ba15d76743c8220b5d5f27702843be71fa077551d92c35b"
3246
3251
  },
3247
3252
  {
3248
3253
  "path": "modules/hall-ui/public/my-projects.js",
3249
3254
  "mode": "0000644",
3250
- "sha256": "39252c26f1ca28fce403b195868ceae01041daf064507fc5760378c134e9eee5"
3255
+ "sha256": "30d22956e21c9ac371d989d8b295631e7bd5df89647457e81a71e0dfffe07cce"
3251
3256
  },
3252
3257
  {
3253
3258
  "path": "modules/hall-ui/public/not-ready.html",
@@ -3337,12 +3342,12 @@
3337
3342
  {
3338
3343
  "path": "modules/hall-ui/public/scouting.html",
3339
3344
  "mode": "0000644",
3340
- "sha256": "ee43e485888979ab7431add208be879526b545d504bd4e0fd2f293f4f765d31c"
3345
+ "sha256": "4f1656ab1deab1a748d294e70bf705173516254dbe3cb8a560a2ea811d5afcb0"
3341
3346
  },
3342
3347
  {
3343
3348
  "path": "modules/hall-ui/public/scouting.js",
3344
3349
  "mode": "0000644",
3345
- "sha256": "075d4e256fc4bc81bfffc1e8fd02df198d06759a0768b4507d4079751b37edb0"
3350
+ "sha256": "0109d6715ba097f2c4329fc01d7e2378a09be728d4cc829354cad3c5af1d8e89"
3346
3351
  },
3347
3352
  {
3348
3353
  "path": "modules/hall-ui/public/sessions.css",
@@ -4172,12 +4177,12 @@
4172
4177
  {
4173
4178
  "path": "package-lock.json",
4174
4179
  "mode": "0000644",
4175
- "sha256": "97adf4b1513014f9dbcf8a3dfb9182e606b4b4f5e7a4da13dd4b4188e670789d"
4180
+ "sha256": "8f0f435bc22d3f90b635703d6255ab5707be608c2263b7d62d2a22f84b239401"
4176
4181
  },
4177
4182
  {
4178
4183
  "path": "package.json",
4179
4184
  "mode": "0000644",
4180
- "sha256": "f17e6fe30f33a4f990ab5be6afe7cc9067d0e1d2ab7eca51dcdac886e5bcfd1d"
4185
+ "sha256": "f67d85ba93fc5babadfce8e44653c771fc0ac07471c0f5ffdcb0b13b0fab244c"
4181
4186
  },
4182
4187
  {
4183
4188
  "path": "public-docs/index.html",
@@ -5762,7 +5767,7 @@
5762
5767
  {
5763
5768
  "path": "src/module-api.js",
5764
5769
  "mode": "0000644",
5765
- "sha256": "681070293ecb18cd5bf028ce0c7367372d5212d0cb7c578e31d323b64b98b639"
5770
+ "sha256": "096a5ae6bd847f8cfff0cb8e12ebeae0c99f3116f0420babdaea9e683a2b9fd3"
5766
5771
  },
5767
5772
  {
5768
5773
  "path": "src/module-loader/catalog.js",
@@ -6792,7 +6797,7 @@
6792
6797
  {
6793
6798
  "path": "tests/grader.mjs",
6794
6799
  "mode": "0000644",
6795
- "sha256": "31557b08f8886f5d76201b5afbe0332e834504b67a391699798170aede9541e8"
6800
+ "sha256": "e97bbe1f1d9bc1b602adbae26e5144675a9324c5b729d0046ffa43407a88922c"
6796
6801
  },
6797
6802
  {
6798
6803
  "path": "tests/grader_bypass.mjs",
@@ -6822,7 +6827,7 @@
6822
6827
  {
6823
6828
  "path": "tests/hall_audit.mjs",
6824
6829
  "mode": "0000644",
6825
- "sha256": "7fad8a81bf0bff3611b7273913e6f2b1c594f5d1cb21737094063fdaf8532542"
6830
+ "sha256": "a048f93e0962bf30869c797e53be26954af276522adba4644a5b233d870e5286"
6826
6831
  },
6827
6832
  {
6828
6833
  "path": "tests/hall_nav.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,
@@ -0,0 +1,40 @@
1
+ /* my-projects.css — page styles for the cross-project view (task 1002868).
2
+ Every color derives from the style.css tokens (pack-driven); this file owns
3
+ only the page's own layout — rows, the join form, the inline notes. */
4
+
5
+ .row {
6
+ display: flex;
7
+ align-items: center;
8
+ gap: 10px;
9
+ background: var(--surface);
10
+ border: 1px solid var(--border);
11
+ border-radius: var(--radius-sm);
12
+ padding: 10px 12px;
13
+ margin-bottom: 8px;
14
+ }
15
+ .row .meta { flex: 1; min-width: 0; }
16
+ .row .name { font-weight: 600; }
17
+ .row .origin { color: var(--accent-text); font-size: 12.5px; word-break: break-all; }
18
+ .row .owner-pill {
19
+ font-size: 11px;
20
+ background: var(--accent);
21
+ color: #fff;
22
+ border-radius: 999px;
23
+ padding: 2px 8px;
24
+ }
25
+
26
+ .empty { margin: 0; color: var(--ink-soft); font-size: 13.5px; }
27
+
28
+ .join { display: flex; gap: 8px; }
29
+ .join input {
30
+ flex: 1;
31
+ font: inherit;
32
+ font-size: 13px;
33
+ padding: 0 10px;
34
+ height: 30px;
35
+ border: 1px solid var(--border-strong);
36
+ border-radius: var(--radius-sm);
37
+ background: var(--surface);
38
+ color: var(--ink);
39
+ }
40
+ .note { margin: 10px 0 0; color: var(--ink-soft); font-size: 13px; min-height: 1.2em; }
@@ -3,76 +3,66 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>My Projects</title>
7
- <style>
8
- :root {
9
- --bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
10
- --rule: #fed7aa; --accent: #ea580c; --accent-deep: #c2410c;
11
- }
12
- * { box-sizing: border-box; }
13
- body {
14
- margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
15
- font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
16
- }
17
- .wrap { max-width: 820px; margin: 0 auto; }
18
- h1 { font-size: 1.9rem; margin: 0 0 .3rem; }
19
- .sub { color: var(--ink-soft); margin: 0 0 2rem; }
20
- h2 { font-size: 1.1rem; margin: 2rem 0 .75rem; border-bottom: 1px solid var(--rule); padding-bottom: .35rem; }
21
- .row {
22
- display: flex; align-items: center; gap: .75rem; background: var(--card);
23
- border: 1px solid var(--rule); border-radius: 12px; padding: .8rem 1rem; margin-bottom: .6rem;
24
- }
25
- .row .meta { flex: 1; min-width: 0; }
26
- .row .name { font-weight: 600; }
27
- .row .origin { color: var(--accent); font-size: .82rem; word-break: break-all; }
28
- .row .owner-pill { font-size: .72rem; background: var(--accent); color: #fff; border-radius: 999px; padding: .1rem .55rem; }
29
- button {
30
- font: inherit; cursor: pointer; border: 1px solid var(--rule); background: #fff; color: var(--ink);
31
- border-radius: 9px; padding: .4rem .8rem;
32
- }
33
- button:hover { border-color: var(--accent); color: var(--accent-deep); }
34
- .join { display: flex; gap: .5rem; margin-top: .75rem; }
35
- .join input { flex: 1; font: inherit; padding: .5rem .7rem; border: 1px solid var(--rule); border-radius: 9px; }
36
- .empty { color: var(--ink-soft); }
37
- .note { margin-top: .5rem; color: var(--ink-soft); font-size: .9rem; min-height: 1.2em; }
38
- a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
39
- @media (prefers-color-scheme: dark) {
40
- :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
41
- button { background: #241c16; }
42
- .join input { background: #241c16; color: inherit; }
43
- }
44
- </style>
6
+ <title>My Projects — {{worldName}}</title>
7
+ <meta name="description" content="Your cross-project view: the projects you own and build on across {{worldName}}.">
8
+ <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
13
+ <link rel="stylesheet" href="/builders/my-projects.css">
45
14
  </head>
46
- <body>
47
- <div class="wrap">
48
- <a class="home" href="/builders">← Back to the hall</a>
49
- <h1>My Projects</h1>
50
- <p class="sub">Every project you build on across the platform.</p>
15
+ <body data-page="my-projects" data-page-title="My Projects">
51
16
 
52
- <section>
53
- <h2>Projects you own</h2>
54
- <div id="owned"><p class="empty">—</p></div>
55
- </section>
17
+ <div class="app">
18
+ <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
19
+ <div class="app-scrim" id="app-scrim" hidden></div>
20
+ <div class="app-body">
21
+ <header class="app-topbar" id="app-topbar"></header>
22
+ <div class="app-content">
23
+ <div class="page-head">
24
+ <p class="page-head__sub">Every project you build on across the platform.</p>
25
+ </div>
56
26
 
57
- <section id="invites-section" hidden>
58
- <h2>Invitations</h2>
59
- <div id="invites"></div>
60
- </section>
27
+ <main>
28
+ <section class="scroll" aria-labelledby="owned-h">
29
+ <h2 id="owned-h" class="scroll__h">Projects you own</h2>
30
+ <div id="owned"><p class="empty">—</p></div>
31
+ </section>
61
32
 
62
- <section>
63
- <h2>Projects you build on</h2>
64
- <div id="building"><p class="empty">—</p></div>
65
- </section>
33
+ <section class="scroll" id="invites-section" hidden aria-labelledby="invites-h">
34
+ <h2 id="invites-h" class="scroll__h">Invitations</h2>
35
+ <div id="invites"></div>
36
+ </section>
66
37
 
67
- <section>
68
- <h2>Join a project</h2>
69
- <div class="join">
70
- <input id="join-origin" type="url" placeholder="https://project.cloudbongos.com" autocomplete="off">
71
- <button id="join-btn" type="button">Request to join</button>
72
- </div>
73
- <p id="join-note" class="note"></p>
74
- </section>
38
+ <section class="scroll" aria-labelledby="building-h">
39
+ <h2 id="building-h" class="scroll__h">Projects you build on</h2>
40
+ <div id="building"><p class="empty">—</p></div>
41
+ </section>
42
+
43
+ <section class="scroll" aria-labelledby="join-h">
44
+ <h2 id="join-h" class="scroll__h">Join a project</h2>
45
+ <div class="join">
46
+ <input id="join-origin" type="url" placeholder="https://project.cloudbongos.com" autocomplete="off">
47
+ <button id="join-btn" type="button" class="btn-ghost">Request to join</button>
48
+ </div>
49
+ <p id="join-note" class="note"></p>
50
+ </section>
51
+ </main>
52
+
53
+ <footer class="app-foot" id="app-foot"></footer>
54
+ </div>
75
55
  </div>
76
- <script src="/my-projects.js"></script>
56
+ </div>
57
+
58
+ <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
59
+
60
+ <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
61
+ chrome exists before the page script runs. -->
62
+ <script src="/builders/shell.js?v=2026-07-02-shell1"></script>
63
+ <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
64
+ <script src="/builders/palette.js" defer></script>
65
+ <script src="/builders/brand-apply.js"></script>
66
+ <script src="/builders/my-projects.js"></script>
77
67
  </body>
78
68
  </html>
@@ -46,7 +46,7 @@
46
46
  row.appendChild(pill);
47
47
  }
48
48
  if (opts.leavable) {
49
- var btn = el('button');
49
+ var btn = el('button', 'btn-ghost');
50
50
  btn.type = 'button';
51
51
  btn.textContent = 'Leave';
52
52
  btn.addEventListener('click', function () { leave(m.client_id, row, btn); });
@@ -91,8 +91,8 @@
91
91
  var host = safeOriginHost(inv.origin);
92
92
  if (host) { var o = el('div', 'origin'); o.textContent = host; meta.appendChild(o); }
93
93
  row.appendChild(meta);
94
- var accept = el('button'); accept.type = 'button'; accept.textContent = 'Accept';
95
- var decline = el('button'); decline.type = 'button'; decline.textContent = 'Decline';
94
+ var accept = el('button', 'btn-ghost'); accept.type = 'button'; accept.textContent = 'Accept';
95
+ var decline = el('button', 'btn-ghost'); decline.type = 'button'; decline.textContent = 'Decline';
96
96
  accept.addEventListener('click', function () { resolveInvite(inv.client_id, true, row, [accept, decline]); });
97
97
  decline.addEventListener('click', function () { resolveInvite(inv.client_id, false, row, [accept, decline]); });
98
98
  row.appendChild(accept); row.appendChild(decline);
@@ -3,43 +3,49 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Scouting — cross-project builders</title>
6
+ <title>Scouting — {{worldName}}</title>
7
+ <meta name="description" content="Cross-project builder activity — an internal talent view for project curators.">
8
+ <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
7
13
  <style>
8
- :root {
9
- --bg: #fff7ed; --card: #ffffff; --ink: #2a1f17; --ink-soft: #6b5d52;
10
- --rule: #fed7aa; --accent: #ea580c;
11
- }
12
- * { box-sizing: border-box; }
13
- body {
14
- margin: 0; padding: 2.5rem 1.25rem 4rem; background: var(--bg); color: var(--ink);
15
- font-family: "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif; line-height: 1.5;
16
- }
17
- .wrap { max-width: 960px; margin: 0 auto; }
18
- a.home { color: var(--accent); text-decoration: none; font-size: .9rem; }
19
- h1 { font-size: 1.9rem; margin: .4rem 0 .3rem; }
20
- .sub { color: var(--ink-soft); margin: 0 0 1.75rem; }
21
- .badge { display: inline-block; font-size: .7rem; text-transform: uppercase; letter-spacing: .06em;
22
- background: var(--accent); color: #fff; border-radius: 999px; padding: .12rem .6rem; vertical-align: middle; }
23
- table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--rule); border-radius: 12px; overflow: hidden; }
24
- th, td { text-align: left; padding: .6rem .8rem; border-bottom: 1px solid var(--rule); font-size: .92rem; }
25
- th { color: var(--ink-soft); font-weight: 600; }
26
- td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
27
- tr:last-child td { border-bottom: none; }
28
- .who { font-weight: 600; }
29
- .note { color: var(--ink-soft); padding: 2.5rem 1rem; text-align: center; }
30
- .scroll { overflow-x: auto; }
31
- @media (prefers-color-scheme: dark) {
32
- :root { --bg: #1a1410; --card: #241c16; --ink: #f5ece3; --ink-soft: #b9a998; --rule: #4a3728; }
33
- }
14
+ #body .who { font-weight: 600; }
34
15
  </style>
35
16
  </head>
36
- <body>
37
- <div class="wrap">
38
- <a class="home" href="/builders">← Back to the hall</a>
39
- <h1>Scouting <span class="badge">Archon only</span></h1>
40
- <p class="sub">Builders active across all hosted projects — an internal talent view. Descriptive only; it grants no authority anywhere.</p>
41
- <div id="body" class="note">Loading…</div>
17
+ <body data-page="scouting" data-page-title="Scouting">
18
+
19
+ <div class="app">
20
+ <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
21
+ <div class="app-scrim" id="app-scrim" hidden></div>
22
+ <div class="app-body">
23
+ <header class="app-topbar" id="app-topbar"></header>
24
+ <div class="app-content">
25
+ <div class="page-head">
26
+ <p class="page-head__sub">Builders active across all hosted projects — an internal talent view. Descriptive only; it grants no authority anywhere.</p>
27
+ </div>
28
+
29
+ <main>
30
+ <section class="scroll" aria-labelledby="scouting-h">
31
+ <h2 id="scouting-h" class="scroll__h">Cross-project builders</h2>
32
+ <div id="body" class="profile__placeholder">Loading…</div>
33
+ </section>
34
+ </main>
35
+
36
+ <footer class="app-foot" id="app-foot"></footer>
37
+ </div>
42
38
  </div>
43
- <script src="/scouting.js"></script>
39
+ </div>
40
+
41
+ <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
42
+
43
+ <!-- The app shell (sidebar + top bar). Dependency-free; loaded FIRST so the
44
+ chrome exists before the page script runs. -->
45
+ <script src="/builders/shell.js?v=2026-07-02-shell1"></script>
46
+ <!-- jump palette (Ctrl/Cmd-K). Optional by design: without it the shell's slot stays hidden. -->
47
+ <script src="/builders/palette.js" defer></script>
48
+ <script src="/builders/brand-apply.js"></script>
49
+ <script src="/builders/scouting.js"></script>
44
50
  </body>
45
51
  </html>
@@ -25,15 +25,16 @@
25
25
  body.className = '';
26
26
  body.textContent = '';
27
27
  if (!builders || builders.length === 0) {
28
- body.className = 'note';
28
+ body.className = 'profile__placeholder';
29
29
  body.textContent = 'No cross-project activity reported yet.';
30
30
  return;
31
31
  }
32
- var scroll = el('div', 'scroll');
33
- var table = el('table');
32
+ // table.ledger, appended straight into #body: the design system's table
33
+ // styling, and #body becomes the :has(> .ledger) phone scroll strip.
34
+ var table = el('table', 'ledger');
34
35
  var thead = el('thead');
35
36
  var hr = el('tr');
36
- [['Builder', ''], ['Projects', 'num'], ['Credits', 'num'], ['Ships', 'num'], ['Karma', 'num'], ['Disciplines', ''], ['Last active', '']]
37
+ [['Builder', ''], ['Projects', 'ledger__num'], ['Credits', 'ledger__num'], ['Ships', 'ledger__num'], ['Karma', 'ledger__num'], ['Disciplines', ''], ['Last active', '']]
37
38
  .forEach(function (h) { var th = el('th', h[1]); th.textContent = h[0]; hr.appendChild(th); });
38
39
  thead.appendChild(hr); table.appendChild(thead);
39
40
  var tbody = el('tbody');
@@ -44,17 +45,16 @@
44
45
  name.textContent = b.github_login || b.display_name || ('#' + b.github_id);
45
46
  who.appendChild(name);
46
47
  tr.appendChild(who);
47
- tr.appendChild(td(String(b.project_count != null ? b.project_count : '—'), 'num'));
48
- tr.appendChild(td(String(b.total_credits != null ? b.total_credits : 0), 'num'));
49
- tr.appendChild(td(String(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0), 'num'));
50
- tr.appendChild(td(String(b.total_karma != null ? b.total_karma : 0), 'num'));
48
+ tr.appendChild(td(String(b.project_count != null ? b.project_count : '—'), 'ledger__num'));
49
+ tr.appendChild(td(String(b.total_credits != null ? b.total_credits : 0), 'ledger__num'));
50
+ tr.appendChild(td(String(b.total_tasks_shipped != null ? b.total_tasks_shipped : 0), 'ledger__num'));
51
+ tr.appendChild(td(String(b.total_karma != null ? b.total_karma : 0), 'ledger__num'));
51
52
  tr.appendChild(td(Array.isArray(b.disciplines) ? b.disciplines.join(', ') : '—'));
52
53
  tr.appendChild(td(fmtRecency(b.last_active)));
53
54
  tbody.appendChild(tr);
54
55
  });
55
56
  table.appendChild(tbody);
56
- scroll.appendChild(table);
57
- body.appendChild(scroll);
57
+ body.appendChild(table);
58
58
  }
59
59
 
60
60
  fetch('/api/gds/scouting', { headers: { Accept: 'application/json' } })
@@ -65,7 +65,7 @@
65
65
  })
66
66
  .then(function (data) { render(data && data.builders); })
67
67
  .catch(function (e) {
68
- body.className = 'note';
68
+ body.className = 'profile__placeholder';
69
69
  body.textContent = (e && e.archon)
70
70
  ? 'This view is Archon-only.'
71
71
  : 'Couldn’t load the scouting view right now.';
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.134",
3
+ "version": "1.19.136",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.134",
9
+ "version": "1.19.136",
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.134",
3
+ "version": "1.19.136",
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
@@ -695,8 +695,12 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
695
695
  // landed since 1.19.132 with no explicit bump. run 31862484011. (task 1002620)
696
696
  // 1.19.134 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
697
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)
700
+ // 1.19.136 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
701
+ // landed since 1.19.135 with no explicit bump. run 31889474899. (task 1002620)
698
702
  // ---------------------------------------------------------------------------
699
- const CORE_VERSION = '1.19.134'; // CI auto-patch carrier (ADR 0161); see changelog above
703
+ const CORE_VERSION = '1.19.136'; // CI auto-patch carrier (ADR 0161); see changelog above
700
704
 
701
705
  // A namespaced logger so a module's log lines are attributable + consistent.
702
706
  // 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({
@@ -30,14 +30,8 @@ const pages = () => fs.readdirSync(HALL).filter((f) => f.endsWith('.html'));
30
30
  // reader whether promoting it would actually fix anything.
31
31
  const INLINE_STYLE_BUDGET = {
32
32
  'task.html': 27, // the task-751 detail-page block, deliberate
33
- // my-projects + scouting are standalone surfaces that load neither style.css
34
- // nor shell.js: their block is not page-local styling ON the design system,
35
- // it IS their whole stylesheet, down to a hardcoded palette. Moving those
36
- // bytes into a .css file would change nothing that matters — what they
37
- // actually need is adoption into the shell + the branding contract, which is
38
- // a far larger change than a CSS relocation.
39
- 'my-projects.html': 36,
40
- 'scouting.html': 26,
33
+ // (my-projects + scouting were carried here as standalone surfaces until task
34
+ // 1002868 adopted them into the shell; their exceptions are gone with them.)
41
35
  // Genuinely page-local, and already token-derived. One line over the default;
42
36
  // a whole stylesheet for the legend strip is not worth the extra request, and
43
37
  // diagram-viewer.css is shared with primer.html so it is the wrong home.
@@ -221,3 +215,43 @@ test('the module doc still records where the 2179 density pass landed', () => {
221
215
  assert.match(doc, /97cbef0/, 'names the commit that actually shipped part 1');
222
216
  assert.match(doc, /--space-1/, 'and an anchor a reader can go verify it against');
223
217
  });
218
+
219
+ // ---- 8. every page rides the shell + the design system (task 1002868) -----
220
+
221
+ // my-projects + scouting were the last two standalone surfaces: no style.css,
222
+ // no shell.js, a private hardcoded palette that silently kept the WCAG-failing
223
+ // accent when the pack moved (idea 1000600). Task 1002868 adopted them, so the
224
+ // rule is now universal and exception-free.
225
+ test('every hall page loads style.css and shell.js', () => {
226
+ const missing = [];
227
+ for (const f of pages()) {
228
+ const html = read(f);
229
+ if (!/<link[^>]+href="[^"]*\bstyle\.css[^"]*"/.test(html)) missing.push(`${f}: no style.css link`);
230
+ if (!/<script[^>]+src="[^"]*\bshell\.js[^"]*"/.test(html)) missing.push(`${f}: no shell.js`);
231
+ }
232
+ assert.deepEqual(missing, [], 'standalone hall surfaces are gone (task 1002868) — adopt the shell, do not fork it');
233
+ });
234
+
235
+ // A page that re-declares a pack color var carries a private palette: it keeps
236
+ // rendering its baked-in colors however the instance pack (or the neutral
237
+ // defaults) move. Definitions only — `var(--accent)` USAGE is the design system
238
+ // working as intended. The 13 names are style.css's theming contract (§3 above
239
+ // pins the same list on the defining side).
240
+ const PACK_COLOR_VARS = [
241
+ 'bg', 'bg-card', 'bg-deep', 'bg-frame', 'ink', 'ink-soft', 'ink-faint',
242
+ 'rule', 'rule-soft', 'accent', 'accent-deep', 'sea', 'terracotta',
243
+ ];
244
+
245
+ test('no page declares a private copy of a pack color var', () => {
246
+ const offenders = [];
247
+ for (const f of pages()) {
248
+ for (const m of read(f).matchAll(/<style[^>]*>([\s\S]*?)<\/style>/g)) {
249
+ for (const v of PACK_COLOR_VARS) {
250
+ // exact-name declaration: `--accent :` never matches `--accent-deep:`
251
+ // (next char is `-`) nor `var(--accent)` (next char is `)`).
252
+ if (new RegExp(`--${v}\\s*:`).test(m[1])) offenders.push(`${f}: defines --${v}`);
253
+ }
254
+ }
255
+ }
256
+ assert.deepEqual(offenders, [], 'palette comes from style.css + the instance pack (themeMarkup), never from a page');
257
+ });