@bongos/core 1.19.665 → 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 +11 -11
- package/docs/module-api-changelog.md +2 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/conductor-dispatch.js +102 -4
- package/src/module-api.js +1 -1
- package/tests/conductor.mjs +106 -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.666",
|
|
6
|
+
"core_contract": "1.19.666",
|
|
7
|
+
"source_commit": "3fc1c7e12b4d5fa3451f618fb8a971a5da710db7",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-11T05:45:20.037Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 473,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 2613,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "18d8514d20465a8efc1d55968fb7382c7fef86fac6d286475bb414b792238821",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -2787,7 +2787,7 @@
|
|
|
2787
2787
|
{
|
|
2788
2788
|
"path": "docs/module-api-changelog.md",
|
|
2789
2789
|
"mode": "0000644",
|
|
2790
|
-
"sha256": "
|
|
2790
|
+
"sha256": "1a4f6805f1c7cc669b21c253e57d753e5e37c8a4d0b84ea27ad959be2b63c4dc"
|
|
2791
2791
|
},
|
|
2792
2792
|
{
|
|
2793
2793
|
"path": "docs/modules-contract.md",
|
|
@@ -7757,12 +7757,12 @@
|
|
|
7757
7757
|
{
|
|
7758
7758
|
"path": "package-lock.json",
|
|
7759
7759
|
"mode": "0000644",
|
|
7760
|
-
"sha256": "
|
|
7760
|
+
"sha256": "67316339ae447a8943fa26ed89e3974debbd19f41ca5d7082a35a7960632ca75"
|
|
7761
7761
|
},
|
|
7762
7762
|
{
|
|
7763
7763
|
"path": "package.json",
|
|
7764
7764
|
"mode": "0000644",
|
|
7765
|
-
"sha256": "
|
|
7765
|
+
"sha256": "b54f7ae81efd2b4d6c154240adce30ba2a0e4b4356050680704276d4fd8f9f8a"
|
|
7766
7766
|
},
|
|
7767
7767
|
{
|
|
7768
7768
|
"path": "public-docs/index.html",
|
|
@@ -8052,7 +8052,7 @@
|
|
|
8052
8052
|
{
|
|
8053
8053
|
"path": "scripts/gds/conductor-dispatch.js",
|
|
8054
8054
|
"mode": "0000644",
|
|
8055
|
-
"sha256": "
|
|
8055
|
+
"sha256": "6f25380a8b715dad1ce9dd17c0f0a2d7fa7573db6e2434029f0ba1d3c1cdc730"
|
|
8056
8056
|
},
|
|
8057
8057
|
{
|
|
8058
8058
|
"path": "scripts/gds/context-pack.js",
|
|
@@ -9522,7 +9522,7 @@
|
|
|
9522
9522
|
{
|
|
9523
9523
|
"path": "src/module-api.js",
|
|
9524
9524
|
"mode": "0000644",
|
|
9525
|
-
"sha256": "
|
|
9525
|
+
"sha256": "ae4d5ac8a868e23e7f9a456173658719bdbd44e4ed231d60fcd3fe9646c81d47"
|
|
9526
9526
|
},
|
|
9527
9527
|
{
|
|
9528
9528
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10182,7 +10182,7 @@
|
|
|
10182
10182
|
{
|
|
10183
10183
|
"path": "tests/conductor.mjs",
|
|
10184
10184
|
"mode": "0000644",
|
|
10185
|
-
"sha256": "
|
|
10185
|
+
"sha256": "e2c338b765a7fccedd1730bb56e9338e022c41a40ed60406a94c1bb5b7b1599a"
|
|
10186
10186
|
},
|
|
10187
10187
|
{
|
|
10188
10188
|
"path": "tests/confirm_attribution.mjs",
|
|
@@ -1789,5 +1789,7 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
1789
1789
|
landed since 1.19.663 with no explicit bump. run 34558906002. (task 1002620)
|
|
1790
1790
|
1.19.665 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1791
1791
|
landed since 1.19.664 with no explicit bump. run 34560552542. (task 1002620)
|
|
1792
|
+
1.19.666 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
1793
|
+
landed since 1.19.665 with no explicit bump. run 34567142386. (task 1002620)
|
|
1792
1794
|
---------------------------------------------------------------------------
|
|
1793
1795
|
```
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.666",
|
|
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.666",
|
|
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.666",
|
|
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",
|
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
// 0 when a task was selected (a directive was emitted), 1 on a NO-GO / no work.
|
|
45
45
|
|
|
46
46
|
const { execFileSync } = require('node:child_process');
|
|
47
|
+
// basename(cwd) is the worktree name a claim is bound to (task 1003834) — the
|
|
48
|
+
// same value claim.js records via --worktree.
|
|
49
|
+
const path = require('node:path');
|
|
47
50
|
const { cliClient } = require('./cli-lib');
|
|
48
51
|
const grader = require('../../modules/grading/grader');
|
|
49
52
|
// Phase 4.5: reuse the $0 deterministic autonomy precheck (the same gate the
|
|
@@ -195,11 +198,58 @@ function diffStatsSince(base, untracked) {
|
|
|
195
198
|
};
|
|
196
199
|
}
|
|
197
200
|
|
|
201
|
+
// THE WORKTREE IS THE ANSWER, when there is one (task 1003834).
|
|
202
|
+
//
|
|
203
|
+
// A claim BINDS to a dedicated worktree — one claim per session, one worktree
|
|
204
|
+
// per claim (task 1642, ADR 0097) — and `claim.js --worktree` records that
|
|
205
|
+
// worktree's leaf-folder name on the claim row, which is exactly
|
|
206
|
+
// `path.basename(process.cwd())`. So when this dispatch runs inside a claimed
|
|
207
|
+
// worktree there is no inference to do: that worktree's claim IS the task being
|
|
208
|
+
// worked on. Everything below it is a heuristic; this is a fact.
|
|
209
|
+
//
|
|
210
|
+
// Claims from before the column existed carry a null worktree_name and are
|
|
211
|
+
// simply not matched here — the heuristic still covers them.
|
|
212
|
+
function claimForWorktree(claims, worktreeName) {
|
|
213
|
+
if (!Array.isArray(claims) || !worktreeName) return null;
|
|
214
|
+
return claims.find((c) => c && c.worktree_name && c.worktree_name === worktreeName) || null;
|
|
215
|
+
}
|
|
216
|
+
|
|
198
217
|
// Pick the active claim whose touches[] best overlap the changed files — the
|
|
199
218
|
// claim the current diff most plausibly belongs to. With multiple (possibly
|
|
200
219
|
// stale) claims, blindly taking claims[0] grades the diff against the wrong
|
|
201
220
|
// task: the Phase 4 dogfood's Narc FAILed because it graded Conductor code
|
|
202
221
|
// against an unrelated stale offsite-backup claim. Best-overlap fixes that.
|
|
222
|
+
//
|
|
223
|
+
// SPECIFICITY, NOT VOLUME (task 1003834). A raw count of matched files rewards
|
|
224
|
+
// BREADTH, so the more honestly a claim declares its touches[] the more likely
|
|
225
|
+
// it loses. Observed 2026-09-10: reviewing task 1003829's diff, its own claim
|
|
226
|
+
// declared three exact files (`modules/hall-ui/public/studio.{js,html,css}`) and
|
|
227
|
+
// could therefore score at most 3, while a concurrent rename claim declaring
|
|
228
|
+
// `scripts/gds/`, `src/bongos/`, `modules/`, `clients/` matched most of the
|
|
229
|
+
// same diff and won — so the reviewer was briefed on an unrelated task. A
|
|
230
|
+
// pattern like `modules/` covers almost any diff in this repo and must not
|
|
231
|
+
// out-vote an exact path.
|
|
232
|
+
//
|
|
233
|
+
// So each matched file is worth the SPECIFICITY of the pattern that matched it:
|
|
234
|
+
// its path-segment depth, plus a bonus when the pattern is the file itself.
|
|
235
|
+
// Volume still counts — a genuinely broad claim matching fifty files beats a
|
|
236
|
+
// narrow one sharing a single file — but depth breaks the tie the right way.
|
|
237
|
+
function patternWeight(pattern, file) {
|
|
238
|
+
const p = String(pattern || '');
|
|
239
|
+
const depth = p.replace(/\/+$/, '').split('/').filter(Boolean).length;
|
|
240
|
+
return depth + (p === file ? EXACT_MATCH_BONUS : 0);
|
|
241
|
+
}
|
|
242
|
+
const EXACT_MATCH_BONUS = 1;
|
|
243
|
+
|
|
244
|
+
function claimOverlapScore(claim, files) {
|
|
245
|
+
let score = 0;
|
|
246
|
+
for (const f of files) {
|
|
247
|
+
const hit = matchOne(f, (claim && claim.touches) || []);
|
|
248
|
+
if (hit !== null) score += patternWeight(hit, f);
|
|
249
|
+
}
|
|
250
|
+
return score;
|
|
251
|
+
}
|
|
252
|
+
|
|
203
253
|
function bestMatchingClaim(claims, changedFiles) {
|
|
204
254
|
if (!Array.isArray(claims) || claims.length === 0) return null;
|
|
205
255
|
const files = Array.isArray(changedFiles) ? changedFiles : [];
|
|
@@ -207,12 +257,29 @@ function bestMatchingClaim(claims, changedFiles) {
|
|
|
207
257
|
let bestScore = -1;
|
|
208
258
|
for (const c of claims) {
|
|
209
259
|
// Use the canonical matcher; ties keep the earlier claim (strict >).
|
|
210
|
-
const score =
|
|
260
|
+
const score = claimOverlapScore(c, files);
|
|
211
261
|
if (score > bestScore) { bestScore = score; best = c; }
|
|
212
262
|
}
|
|
213
263
|
return best;
|
|
214
264
|
}
|
|
215
265
|
|
|
266
|
+
// The claim this diff belongs to, and HOW that was decided — because the two
|
|
267
|
+
// answers deserve very different trust. 'worktree' is a fact; 'overlap' is a
|
|
268
|
+
// guess, and a guess that must be visible so a reader can discount a review
|
|
269
|
+
// that argues from the brief rather than from the diff.
|
|
270
|
+
//
|
|
271
|
+
// A claim with an EMPTY touches[] scores zero against any diff, which is most
|
|
272
|
+
// tasks in this repo — another reason the worktree signal leads rather than
|
|
273
|
+
// merely breaking ties.
|
|
274
|
+
function attributeClaim(claims, changedFiles, worktreeName) {
|
|
275
|
+
const byWorktree = claimForWorktree(claims, worktreeName);
|
|
276
|
+
if (byWorktree) return { claim: byWorktree, signal: 'worktree', worktree: worktreeName };
|
|
277
|
+
const byOverlap = bestMatchingClaim(claims, changedFiles);
|
|
278
|
+
if (!byOverlap) return { claim: null, signal: 'none', worktree: worktreeName || null };
|
|
279
|
+
const score = claimOverlapScore(byOverlap, Array.isArray(changedFiles) ? changedFiles : []);
|
|
280
|
+
return { claim: byOverlap, signal: score > 0 ? 'overlap' : 'fallback', worktree: worktreeName || null };
|
|
281
|
+
}
|
|
282
|
+
|
|
216
283
|
// --- task context (best-effort) ---
|
|
217
284
|
// Workers grade a diff against a task. For a mid-flight review we use the
|
|
218
285
|
// best-matching active claim's task; otherwise we synthesize a minimal
|
|
@@ -224,7 +291,12 @@ async function resolveTaskContext(changedFiles) {
|
|
|
224
291
|
if (me.ok) {
|
|
225
292
|
const claims = me.data.active_claims || (me.data.active_claim ? [me.data.active_claim] : []);
|
|
226
293
|
if (claims.length > 0) {
|
|
227
|
-
|
|
294
|
+
// The worktree this dispatch was invoked in decides it when it can
|
|
295
|
+
// (task 1003834); the touches[] heuristic is the fallback, and which
|
|
296
|
+
// one answered is reported to the reader rather than hidden.
|
|
297
|
+
const attribution = attributeClaim(claims, changedFiles, path.basename(process.cwd()));
|
|
298
|
+
const c = attribution.claim;
|
|
299
|
+
if (!c) throw new Error('no claim attributed');
|
|
228
300
|
// Pull the full description for richer context (best-effort). Coerce
|
|
229
301
|
// task_id to an integer before interpolating it into the URL path —
|
|
230
302
|
// it's a trusted internal value, but a guard costs nothing and closes
|
|
@@ -243,6 +315,11 @@ async function resolveTaskContext(changedFiles) {
|
|
|
243
315
|
description: description || '(no description fetched — mid-flight review; judge the diff on its own merits)',
|
|
244
316
|
kind: 'unclassified',
|
|
245
317
|
version_id: c.version_id,
|
|
318
|
+
// How this task was attributed to this diff, carried so the header can
|
|
319
|
+
// say it (task 1003834). 'worktree' is a fact; anything else is a
|
|
320
|
+
// guess the reader should weigh.
|
|
321
|
+
attribution: attribution.signal,
|
|
322
|
+
claim_count: claims.length,
|
|
246
323
|
};
|
|
247
324
|
}
|
|
248
325
|
}
|
|
@@ -257,6 +334,22 @@ async function resolveTaskContext(changedFiles) {
|
|
|
257
334
|
|
|
258
335
|
// --- rendering ---
|
|
259
336
|
|
|
337
|
+
// Say how the task context was chosen, but only when saying it changes what a
|
|
338
|
+
// reader should do (task 1003834). Attribution by WORKTREE is certain, and a
|
|
339
|
+
// single claim leaves nothing to confuse, so both stay silent. A guess made
|
|
340
|
+
// while several claims were open is the case that misled a reader before, and
|
|
341
|
+
// it says so out loud.
|
|
342
|
+
function attributionNote(task) {
|
|
343
|
+
if (!task || !task.id) return '';
|
|
344
|
+
const n = Number(task.claim_count) || 0;
|
|
345
|
+
if (task.attribution === 'worktree' || n < 2) return '';
|
|
346
|
+
const how = task.attribution === 'overlap'
|
|
347
|
+
? 'guessed from touches[] overlap'
|
|
348
|
+
: 'no signal at all — first claim taken';
|
|
349
|
+
return `\n ⚠ attribution: ${how}, across ${n} open claims. This worktree is bound to no claim, so the brief`
|
|
350
|
+
+ '\n may belong to different work: weigh findings about the FILES, discount findings about intent.';
|
|
351
|
+
}
|
|
352
|
+
|
|
260
353
|
function renderWorker(w) {
|
|
261
354
|
const lines = [];
|
|
262
355
|
const status = w.error ? `ERROR (${w.error})` : w.verdict.toUpperCase();
|
|
@@ -382,7 +475,7 @@ async function main() {
|
|
|
382
475
|
const routeRank = grader.routeRankPrePass(files);
|
|
383
476
|
|
|
384
477
|
console.log(`🎼 Conductor dispatch → ${kinds.join(', ')} (model=${model}, ${files.length} changed file(s), base=${base.slice(0, 8)})`);
|
|
385
|
-
console.log(` task context: ${task.id ? '#' + task.id + ' ' + task.title : task.title}`);
|
|
478
|
+
console.log(` task context: ${task.id ? '#' + task.id + ' ' + task.title : task.title}${attributionNote(task)}`);
|
|
386
479
|
if (routeRank) console.log(' + deterministic route-rank check (route file in diff)');
|
|
387
480
|
console.log('');
|
|
388
481
|
|
|
@@ -423,4 +516,9 @@ if (require.main === module) {
|
|
|
423
516
|
});
|
|
424
517
|
}
|
|
425
518
|
|
|
426
|
-
module.exports = {
|
|
519
|
+
module.exports = {
|
|
520
|
+
pickSpecialists, bestMatchingClaim, selectAutonomousDispatch, autoDispatch,
|
|
521
|
+
// task 1003834 — the attribution layer, exported so its decisions are pinned
|
|
522
|
+
// rather than only observed through a dispatch run.
|
|
523
|
+
claimForWorktree, claimOverlapScore, attributeClaim, attributionNote,
|
|
524
|
+
};
|
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),
|