@bongos/core 1.19.664 → 1.19.666
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 +29 -24
- package/docs/copy-inventory.md +28 -17
- package/docs/copy-registry.json +121 -22
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/hall-kit.js +36 -2
- package/modules/hall-ui/public/studio.css +121 -1
- package/modules/hall-ui/public/studio.html +5 -1
- package/modules/hall-ui/public/studio.js +337 -13
- package/modules/hall-ui/public/studio.states.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/conductor-dispatch.js +102 -4
- package/scripts/hall-preview/server.js +11 -0
- package/scripts/hall-preview/task-visual.js +89 -0
- package/src/module-api.js +1 -1
- package/tests/conductor.mjs +106 -0
- package/tests/hall_studio_world.mjs +435 -8
- package/tests/rank_tier_single_source.mjs +8 -1
- package/tests/task_visuals.mjs +35 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// scripts/hall-preview/task-visual.js — a stand-in ship-time visual for the
|
|
3
|
+
// preview harness (task 1003829).
|
|
4
|
+
//
|
|
5
|
+
// WHY THIS EXISTS. Everything else in this harness is answered from a checked-in
|
|
6
|
+
// JSON fixture, but a ship-time visual is an IMAGE: the API stub answers
|
|
7
|
+
// /api/bongos/* with JSON, so a page that renders <img src="/api/bongos/
|
|
8
|
+
// task-visuals/task-1899-….png"> got a 404 body and a broken frame. The Studio's
|
|
9
|
+
// review card (task 1003829) makes that image the subject of the card, and a
|
|
10
|
+
// surface whose subject cannot be rendered cannot be reviewed — the whole point
|
|
11
|
+
// of this harness.
|
|
12
|
+
//
|
|
13
|
+
// It is a GENERATED placeholder, not a checked-in screenshot: a few flat bands
|
|
14
|
+
// at a real screenshot's aspect ratio, so a reviewer can judge the plate's size,
|
|
15
|
+
// fit, caption and crop without a binary in the repo. Deliberately obvious —
|
|
16
|
+
// nobody should mistake it for a real ship's visual.
|
|
17
|
+
//
|
|
18
|
+
// A ~40-line PNG encoder rather than a dependency, matching the kit's own
|
|
19
|
+
// png.js (a ~50-line DEcoder for the same reason). One IDAT, filter type 0.
|
|
20
|
+
const zlib = require('node:zlib');
|
|
21
|
+
|
|
22
|
+
function chunk(type, data) {
|
|
23
|
+
const len = Buffer.alloc(4);
|
|
24
|
+
len.writeUInt32BE(data.length);
|
|
25
|
+
const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
|
|
26
|
+
const crc = Buffer.alloc(4);
|
|
27
|
+
crc.writeUInt32BE(crc32(body) >>> 0);
|
|
28
|
+
return Buffer.concat([len, body, crc]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let CRC_TABLE = null;
|
|
32
|
+
function crc32(buf) {
|
|
33
|
+
if (!CRC_TABLE) {
|
|
34
|
+
CRC_TABLE = new Int32Array(256);
|
|
35
|
+
for (let n = 0; n < 256; n++) {
|
|
36
|
+
let c = n;
|
|
37
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
38
|
+
CRC_TABLE[n] = c;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let c = -1;
|
|
42
|
+
for (let i = 0; i < buf.length; i++) c = CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
|
43
|
+
return c ^ -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// An RGB PNG from a per-pixel colour function. RGB (colour type 2), 8-bit,
|
|
47
|
+
// non-interlaced — the shape the kit's decoder also speaks.
|
|
48
|
+
function encodeRgb(w, h, at) {
|
|
49
|
+
const stride = w * 3;
|
|
50
|
+
const raw = Buffer.alloc((stride + 1) * h);
|
|
51
|
+
for (let y = 0; y < h; y++) {
|
|
52
|
+
const row = y * (stride + 1);
|
|
53
|
+
raw[row] = 0; // filter: none
|
|
54
|
+
for (let x = 0; x < w; x++) {
|
|
55
|
+
const [r, g, b] = at(x, y);
|
|
56
|
+
const p = row + 1 + x * 3;
|
|
57
|
+
raw[p] = r; raw[p + 1] = g; raw[p + 2] = b;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const ihdr = Buffer.alloc(13);
|
|
61
|
+
ihdr.writeUInt32BE(w, 0);
|
|
62
|
+
ihdr.writeUInt32BE(h, 4);
|
|
63
|
+
ihdr[8] = 8; ihdr[9] = 2; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
|
|
64
|
+
return Buffer.concat([
|
|
65
|
+
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
66
|
+
chunk('IHDR', ihdr),
|
|
67
|
+
chunk('IDAT', zlib.deflateSync(raw, { level: 9 })),
|
|
68
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The stand-in: a wide plate of flat bands with a paler inset, so its edges,
|
|
73
|
+
// aspect ratio and any crop are all visible at a glance. Cached — the harness
|
|
74
|
+
// may serve it on every paint.
|
|
75
|
+
let CACHED = null;
|
|
76
|
+
function placeholderPng() {
|
|
77
|
+
if (CACHED) return CACHED;
|
|
78
|
+
const W = 1200, H = 700;
|
|
79
|
+
const BANDS = [[38, 34, 30], [74, 66, 58], [176, 96, 62], [214, 200, 184], [246, 243, 238]];
|
|
80
|
+
CACHED = encodeRgb(W, H, (x, y) => {
|
|
81
|
+
const inset = x > W * 0.08 && x < W * 0.92 && y > H * 0.14 && y < H * 0.86;
|
|
82
|
+
const band = BANDS[Math.min(BANDS.length - 1, Math.floor((y / H) * BANDS.length))];
|
|
83
|
+
if (!inset) return band;
|
|
84
|
+
return band.map((c) => Math.min(255, Math.round(c + (255 - c) * 0.42)));
|
|
85
|
+
});
|
|
86
|
+
return CACHED;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { placeholderPng, encodeRgb };
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.666'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/conductor.mjs
CHANGED
|
@@ -372,6 +372,112 @@ test('bestMatchingClaim: single claim returned; empty/null → null', () => {
|
|
|
372
372
|
assert.equal(dispatch.bestMatchingClaim(null, ['x']), null);
|
|
373
373
|
});
|
|
374
374
|
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
// Attribution: which claim does this diff belong to? (task 1003834)
|
|
377
|
+
//
|
|
378
|
+
// The regression these pin is not hypothetical — it happened, and the fixture
|
|
379
|
+
// below is the real data from 2026-09-10.
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
|
|
382
|
+
// The observed failure, verbatim. Reviewing task 1003829's diff from its own
|
|
383
|
+
// worktree, the dispatch announced task 1003704 — a concurrent rename claim
|
|
384
|
+
// whose touches[] are four coarse directory prefixes. The reviewer was briefed
|
|
385
|
+
// on unrelated work and had to reason its way out.
|
|
386
|
+
const CLAIM_1003704 = {
|
|
387
|
+
task_id: '1003704',
|
|
388
|
+
title: 'C3b: rename gds-session.json and the gds_session cookie',
|
|
389
|
+
worktree_name: 'task-1003704',
|
|
390
|
+
touches: ['scripts/gds/', 'src/bongos/', 'modules/', 'clients/'],
|
|
391
|
+
};
|
|
392
|
+
const CLAIM_1003829 = {
|
|
393
|
+
task_id: '1003829',
|
|
394
|
+
title: 'The Studio shows an artist no visuals',
|
|
395
|
+
worktree_name: 'task-1003829',
|
|
396
|
+
touches: ['modules/hall-ui/public/studio.js', 'modules/hall-ui/public/studio.html', 'modules/hall-ui/public/studio.css'],
|
|
397
|
+
};
|
|
398
|
+
// The diff as it actually was: three studio files plus the wider blast radius a
|
|
399
|
+
// real change carries. Under a RAW COUNT, 1003704 matches 8 of these and
|
|
400
|
+
// 1003829 only 3 — which is how the wrong task won.
|
|
401
|
+
const DIFF_1003829 = [
|
|
402
|
+
'modules/hall-ui/public/studio.js',
|
|
403
|
+
'modules/hall-ui/public/studio.html',
|
|
404
|
+
'modules/hall-ui/public/studio.css',
|
|
405
|
+
'modules/hall-ui/public/hall-kit.js',
|
|
406
|
+
'modules/hall-ui/public/studio.states.json',
|
|
407
|
+
'scripts/hall-preview/server.js',
|
|
408
|
+
'scripts/hall-preview/task-visual.js',
|
|
409
|
+
'scripts/hall-preview/fixtures/tasks__detail.json',
|
|
410
|
+
'docs/copy-inventory.md',
|
|
411
|
+
'docs/copy-registry.json',
|
|
412
|
+
'tests/hall_studio_world.mjs',
|
|
413
|
+
'tests/task_visuals.mjs',
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
test('attribution: the worktree decides, even when another claim out-matches the diff', () => {
|
|
417
|
+
const claims = [CLAIM_1003704, CLAIM_1003829];
|
|
418
|
+
const got = dispatch.attributeClaim(claims, DIFF_1003829, 'task-1003829');
|
|
419
|
+
assert.equal(got.claim.task_id, '1003829', 'the claim BOUND to this worktree wins');
|
|
420
|
+
assert.equal(got.signal, 'worktree');
|
|
421
|
+
// And it wins regardless of claim order, so this cannot pass by accident.
|
|
422
|
+
assert.equal(dispatch.attributeClaim([CLAIM_1003829, CLAIM_1003704], DIFF_1003829, 'task-1003704').claim.task_id, '1003704');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test('attribution: specificity beats breadth, so the honest touches[] no longer loses', () => {
|
|
426
|
+
// The same fixture with NO worktree signal — running from the main checkout,
|
|
427
|
+
// or a pre-worktree_name claim. The heuristic alone must now get it right.
|
|
428
|
+
const got = dispatch.attributeClaim([CLAIM_1003704, CLAIM_1003829], DIFF_1003829, null);
|
|
429
|
+
assert.equal(got.claim.task_id, '1003829', 'three exact paths outweigh four directory prefixes');
|
|
430
|
+
assert.equal(got.signal, 'overlap');
|
|
431
|
+
// Proof the OLD scoring is what was wrong: by raw count, 1003704 matches more.
|
|
432
|
+
const rawCount = (c) => DIFF_1003829.filter((f) => (c.touches || []).some((t) => f === t || f.startsWith(t.endsWith('/') ? t : t + '/'))).length;
|
|
433
|
+
assert.ok(rawCount(CLAIM_1003704) > rawCount(CLAIM_1003829), 'the broad claim really does match more files');
|
|
434
|
+
assert.ok(dispatch.claimOverlapScore(CLAIM_1003829, DIFF_1003829) > dispatch.claimOverlapScore(CLAIM_1003704, DIFF_1003829), 'and still scores lower');
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
test('attribution: volume still counts — a genuinely broad claim keeps its diff', () => {
|
|
438
|
+
// Specificity must not invert the heuristic: a rename touching fifty files
|
|
439
|
+
// under scripts/gds/ should not lose to a claim sharing one exact file.
|
|
440
|
+
const broad = { task_id: 'broad', touches: ['scripts/gds/'] };
|
|
441
|
+
const narrow = { task_id: 'narrow', touches: ['scripts/gds/one.js'] };
|
|
442
|
+
const files = Array.from({ length: 50 }, (_, i) => `scripts/gds/f${i}.js`).concat('scripts/gds/one.js');
|
|
443
|
+
assert.equal(dispatch.attributeClaim([narrow, broad], files, null).claim.task_id, 'broad');
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('attribution: an empty touches[] is unreachable by overlap — the worktree saves it', () => {
|
|
447
|
+
// Most tasks in this repo declare no touches at all, so they score zero
|
|
448
|
+
// against every diff. That is the other half of why the worktree leads.
|
|
449
|
+
const bare = { task_id: 'bare', worktree_name: 'task-bare', touches: [] };
|
|
450
|
+
assert.equal(dispatch.claimOverlapScore(bare, ['a.js', 'b.js']), 0);
|
|
451
|
+
assert.equal(dispatch.attributeClaim([CLAIM_1003704, bare], ['a.js'], 'task-bare').claim.task_id, 'bare');
|
|
452
|
+
// With no worktree match it cannot win, and the signal says the answer is weak.
|
|
453
|
+
const guess = dispatch.attributeClaim([bare, CLAIM_1003704], ['docs/x.md'], 'somewhere-else');
|
|
454
|
+
assert.equal(guess.signal, 'fallback', 'zero overlap everywhere is reported as no signal, not as a match');
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test('attribution: a worktree bound to no claim does not borrow one silently', () => {
|
|
458
|
+
const claims = [CLAIM_1003704, CLAIM_1003829];
|
|
459
|
+
const got = dispatch.attributeClaim(claims, DIFF_1003829, 'baseline-main-2');
|
|
460
|
+
assert.equal(got.signal, 'overlap', 'it falls back rather than refusing');
|
|
461
|
+
// …and the header SAYS so, which is the part that would have saved the reader.
|
|
462
|
+
const note = dispatch.attributionNote({ id: got.claim.task_id, attribution: got.signal, claim_count: claims.length });
|
|
463
|
+
assert.match(note, /attribution: guessed from touches\[\] overlap, across 2 open claims/);
|
|
464
|
+
assert.match(note, /discount findings about intent/);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test('attribution: the note stays quiet when it would only add noise', () => {
|
|
468
|
+
// Certain attribution, and the single-claim case, have nothing to warn about.
|
|
469
|
+
assert.equal(dispatch.attributionNote({ id: '1', attribution: 'worktree', claim_count: 4 }), '');
|
|
470
|
+
assert.equal(dispatch.attributionNote({ id: '1', attribution: 'overlap', claim_count: 1 }), '');
|
|
471
|
+
assert.equal(dispatch.attributionNote({ id: null, attribution: 'overlap', claim_count: 9 }), '');
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
test('attribution: claimForWorktree ignores claims with no recorded worktree', () => {
|
|
475
|
+
// Pre-column claims carry null; they must not match an absent cwd name.
|
|
476
|
+
assert.equal(dispatch.claimForWorktree([{ task_id: 'x', worktree_name: null }], 'task-x'), null);
|
|
477
|
+
assert.equal(dispatch.claimForWorktree([{ task_id: 'x', worktree_name: 'task-x' }], null), null);
|
|
478
|
+
assert.equal(dispatch.claimForWorktree(null, 'task-x'), null);
|
|
479
|
+
});
|
|
480
|
+
|
|
375
481
|
test('isCovered now delegates to the canonical matcher (still correct)', () => {
|
|
376
482
|
// Regression guard for the dedup: isCovered is a thin wrapper over
|
|
377
483
|
// path-match.matchOne (relocated from the deleted touches-scan in task 879),
|