@lmzhen/dsh-evolution-merge 0.30.17 → 0.30.19
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/README.md +25 -7
- package/data/minhash_params.json +1063 -0
- package/lib/index.js +7308 -1800
- package/lib/types/adapters/channel.d.ts +1 -0
- package/lib/types/adapters/channel.js +15 -1
- package/lib/types/adapters/engine-screen.d.ts +1 -1
- package/lib/types/adapters/engine-screen.js +5 -2
- package/lib/types/adapters/library.js +4 -1
- package/lib/types/adapters/model-runner.d.ts +0 -5
- package/lib/types/adapters/model-runner.js +8 -2
- package/lib/types/adapters/model.d.ts +31 -0
- package/lib/types/adapters/model.js +47 -0
- package/lib/types/adapters/select.js +12 -8
- package/lib/types/approve-command.d.ts +14 -1
- package/lib/types/approve-command.js +72 -44
- package/lib/types/authoring-flow.d.ts +19 -1
- package/lib/types/authoring-flow.js +49 -15
- package/lib/types/channel-port.d.ts +3 -1
- package/lib/types/channel-port.js +8 -3
- package/lib/types/command-defs.d.ts +5 -0
- package/lib/types/command-defs.js +33 -0
- package/lib/types/command-surface.d.ts +5 -0
- package/lib/types/command-surface.js +11 -0
- package/lib/types/commands.d.ts +3 -4
- package/lib/types/commands.js +33 -72
- package/lib/types/commit-assembly.d.ts +16 -1
- package/lib/types/commit-assembly.js +72 -11
- package/lib/types/commit-execute.d.ts +14 -2
- package/lib/types/commit-execute.js +76 -14
- package/lib/types/commit-plan.d.ts +7 -1
- package/lib/types/commit-plan.js +4 -0
- package/lib/types/commit-receipt.d.ts +3 -0
- package/lib/types/commit-recovery.d.ts +7 -2
- package/lib/types/commit-recovery.js +73 -96
- package/lib/types/contracts.d.ts +44 -1
- package/lib/types/contracts.js +1 -1
- package/lib/types/coordinator.d.ts +3 -2
- package/lib/types/coordinator.js +4 -3
- package/lib/types/decision.js +1 -1
- package/lib/types/doctor.d.ts +18 -1
- package/lib/types/doctor.js +164 -10
- package/lib/types/domain/authoring-manifest.d.ts +26 -0
- package/lib/types/domain/authoring-manifest.js +43 -0
- package/lib/types/domain/claim.d.ts +4 -1
- package/lib/types/domain/claim.js +2 -2
- package/lib/types/domain/commit-journal-store.js +5 -1
- package/lib/types/domain/commit-journal.js +1 -1
- package/lib/types/domain/consistency.d.ts +22 -0
- package/lib/types/domain/consistency.js +50 -0
- package/lib/types/domain/engine-criteria.d.ts +10 -0
- package/lib/types/domain/engine-criteria.js +11 -0
- package/lib/types/domain/engine-input.d.ts +10 -0
- package/lib/types/domain/engine-input.js +26 -0
- package/lib/types/domain/errors.d.ts +15 -1
- package/lib/types/domain/errors.js +23 -4
- package/lib/types/domain/evidence-hash.d.ts +3 -0
- package/lib/types/domain/evidence-hash.js +14 -0
- package/lib/types/domain/failure-route.d.ts +14 -0
- package/lib/types/domain/failure-route.js +12 -0
- package/lib/types/domain/ledger.js +10 -1
- package/lib/types/domain/live-facts.d.ts +31 -0
- package/lib/types/domain/live-facts.js +54 -0
- package/lib/types/domain/merge-limits.d.ts +18 -0
- package/lib/types/domain/merge-limits.js +22 -0
- package/lib/types/domain/merge-report.d.ts +41 -0
- package/lib/types/domain/merge-report.js +2 -0
- package/lib/types/domain/merge-snapshot.d.ts +99 -0
- package/lib/types/domain/merge-snapshot.js +82 -0
- package/lib/types/domain/migrate-v3.d.ts +20 -0
- package/lib/types/domain/migrate-v3.js +68 -0
- package/lib/types/domain/pair-ledger.d.ts +34 -0
- package/lib/types/domain/pair-ledger.js +96 -0
- package/lib/types/domain/pre-1b-backup.d.ts +43 -0
- package/lib/types/domain/pre-1b-backup.js +140 -0
- package/lib/types/domain/provider-key.d.ts +2 -0
- package/lib/types/domain/provider-key.js +11 -0
- package/lib/types/domain/read-probe.d.ts +5 -0
- package/lib/types/domain/read-probe.js +11 -0
- package/lib/types/domain/reconcile.d.ts +47 -0
- package/lib/types/domain/reconcile.js +178 -0
- package/lib/types/domain/resolve-pair.d.ts +2 -9
- package/lib/types/domain/resolve-pair.js +2 -15
- package/lib/types/domain/row-tx.d.ts +27 -0
- package/lib/types/domain/row-tx.js +31 -0
- package/lib/types/domain/row-v3-bridge.d.ts +6 -0
- package/lib/types/domain/row-v3-bridge.js +47 -0
- package/lib/types/domain/row-v3.d.ts +113 -0
- package/lib/types/domain/row-v3.js +107 -0
- package/lib/types/domain/row-v4.d.ts +148 -0
- package/lib/types/domain/row-v4.js +159 -0
- package/lib/types/domain/row.d.ts +1 -0
- package/lib/types/domain/run-coordinator.d.ts +51 -0
- package/lib/types/domain/run-coordinator.js +45 -0
- package/lib/types/domain/schema4.d.ts +53 -0
- package/lib/types/domain/schema4.js +129 -0
- package/lib/types/domain/selection-policy.d.ts +14 -0
- package/lib/types/domain/selection-policy.js +6 -0
- package/lib/types/domain/skipped-expiry.d.ts +5 -0
- package/lib/types/domain/skipped-expiry.js +16 -0
- package/lib/types/domain/stage-aggregate.d.ts +96 -0
- package/lib/types/domain/stage-aggregate.js +293 -0
- package/lib/types/domain/staging-index.d.ts +15 -0
- package/lib/types/domain/staging-index.js +41 -0
- package/lib/types/domain/state-bootstrap.d.ts +43 -0
- package/lib/types/domain/state-bootstrap.js +267 -0
- package/lib/types/domain/state-v3.d.ts +4 -0
- package/lib/types/domain/state-v3.js +4 -0
- package/lib/types/domain/undo-journal.d.ts +19 -0
- package/lib/types/domain/undo-journal.js +20 -0
- package/lib/types/domain/undo-receipt.d.ts +18 -0
- package/lib/types/domain/undo-receipt.js +22 -0
- package/lib/types/domain/validation-plan.d.ts +65 -0
- package/lib/types/domain/validation-plan.js +49 -0
- package/lib/types/facts.d.ts +26 -0
- package/lib/types/facts.js +65 -0
- package/lib/types/gate-filter.d.ts +31 -0
- package/lib/types/gate-filter.js +64 -0
- package/lib/types/index.d.ts +7 -2
- package/lib/types/index.js +185 -82
- package/lib/types/ladder.js +1 -1
- package/lib/types/merge-command.d.ts +3 -1
- package/lib/types/merge-command.js +10 -6
- package/lib/types/merge-routes.d.ts +12 -7
- package/lib/types/merge-routes.js +87 -23
- package/lib/types/merge-tool.d.ts +1 -1
- package/lib/types/merge-tool.js +2 -2
- package/lib/types/metrics.d.ts +16 -0
- package/lib/types/metrics.js +27 -0
- package/lib/types/new-child-authoring.d.ts +5 -1
- package/lib/types/new-child-authoring.js +71 -13
- package/lib/types/params.d.ts +2 -4
- package/lib/types/params.js +13 -0
- package/lib/types/pending-actions.d.ts +2 -2
- package/lib/types/pending-actions.js +17 -17
- package/lib/types/prompts.d.ts +2 -4
- package/lib/types/prompts.js +21 -8
- package/lib/types/propose.d.ts +26 -0
- package/lib/types/propose.js +69 -0
- package/lib/types/read-commands.d.ts +4 -4
- package/lib/types/read-commands.js +19 -26
- package/lib/types/recovery-coordinator.d.ts +34 -0
- package/lib/types/recovery-coordinator.js +84 -0
- package/lib/types/recovery-projection.d.ts +31 -0
- package/lib/types/recovery-projection.js +96 -0
- package/lib/types/resume-command.d.ts +7 -1
- package/lib/types/resume-command.js +50 -14
- package/lib/types/revision.d.ts +17 -0
- package/lib/types/revision.js +39 -3
- package/lib/types/round2-outcome.d.ts +2 -1
- package/lib/types/round2-outcome.js +68 -12
- package/lib/types/run-shell.d.ts +16 -2
- package/lib/types/run-shell.js +49 -5
- package/lib/types/runtime.d.ts +4 -6
- package/lib/types/runtime.js +341 -86
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +2 -0
- package/lib/types/scan-ports.d.ts +21 -1
- package/lib/types/scan-ports.js +51 -23
- package/lib/types/scan-run.d.ts +17 -0
- package/lib/types/scan-run.js +221 -63
- package/lib/types/screening-port.d.ts +47 -0
- package/lib/types/screening-port.js +93 -0
- package/lib/types/screening.d.ts +30 -0
- package/lib/types/screening.js +75 -0
- package/lib/types/stage-tool-registry.d.ts +7 -0
- package/lib/types/stage-tool-registry.js +12 -1
- package/lib/types/state.d.ts +15 -1
- package/lib/types/state.js +39 -17
- package/lib/types/status.d.ts +8 -0
- package/lib/types/status.js +64 -0
- package/lib/types/surface/body.d.ts +4 -0
- package/lib/types/surface/body.js +16 -0
- package/lib/types/surface/merge-view.d.ts +14 -0
- package/lib/types/surface/merge-view.js +126 -0
- package/lib/types/target-claim.d.ts +1 -0
- package/lib/types/target-claim.js +63 -18
- package/lib/types/undo-operation.d.ts +24 -0
- package/lib/types/undo-operation.js +135 -0
- package/lib/types/unit-claim.d.ts +5 -0
- package/lib/types/unit-claim.js +15 -0
- package/lib/types/wire-projection.d.ts +36 -0
- package/lib/types/wire-projection.js +69 -0
- package/lib/types/wire.d.ts +273 -0
- package/lib/types/wire.js +3 -0
- package/package.json +10 -4
- package/lib/types/routes.d.ts +0 -70
- package/lib/types/routes.js +0 -78
package/lib/types/scan-run.js
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
* The scan pair loop. Registry/report lifecycle stays in withMergeRun; this module owns
|
|
3
3
|
* candidate → round1 → pending/fuse → commit transitions and returns the report draft.
|
|
4
4
|
*/
|
|
5
|
-
import { pairKey, } from "./contracts.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { pairKey, rowIdOf, } from "./contracts.js";
|
|
6
|
+
import { loadMergeState } from "./state.js";
|
|
7
|
+
import { pairLedger } from "./domain/pair-ledger.js";
|
|
8
|
+
import { presentStage, dropStage } from "./domain/stage-aggregate.js";
|
|
9
|
+
import { recordPair } from "./domain/run-coordinator.js";
|
|
10
|
+
import { v4ToLegacy } from "./domain/row-v4.js";
|
|
11
|
+
import { criteriaFrom } from "./domain/engine-criteria.js";
|
|
12
|
+
import { withDecisionRevisions } from "./revision.js";
|
|
9
13
|
const isAborted = (signal) => signal.aborted;
|
|
10
14
|
function at(now) {
|
|
11
15
|
return new Date(now()).toISOString();
|
|
@@ -45,8 +49,10 @@ export async function runScan(options) {
|
|
|
45
49
|
const startedAt = options.startedAt ?? options.now();
|
|
46
50
|
const timeoutMs = (options.timeoutMinutes ?? 0) > 0 ? options.timeoutMinutes * 60_000 : 0;
|
|
47
51
|
const pairs = [];
|
|
52
|
+
const degradedNotes = [];
|
|
48
53
|
const totals = { merged: 0, kept: 0, pending: 0, failed: 0 };
|
|
49
54
|
const remaining = [];
|
|
55
|
+
const finalizeRows = [];
|
|
50
56
|
let stopReason = 'completed';
|
|
51
57
|
for (let index = 0; index < candidates.length; index += 1) {
|
|
52
58
|
if (isAborted(options.signal)) {
|
|
@@ -63,28 +69,37 @@ export async function runScan(options) {
|
|
|
63
69
|
await stage('decide', { done: index, total: candidates.length });
|
|
64
70
|
let row;
|
|
65
71
|
try {
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
row = options.runId === undefined ? created.row : { ...created.row, runId: options.runId };
|
|
72
|
+
const key = pairKey(candidate.a, candidate.b);
|
|
73
|
+
const atNow = at(options.now);
|
|
74
|
+
row = { id: rowIdOf(key), pairKey: key, a: candidate.a, b: candidate.b, state: 'verify', queue: 'verify', createdAt: atNow, updatedAt: atNow, ...(options.runId === undefined ? {} : { runId: options.runId }) };
|
|
70
75
|
const before = (await loadMergeState(options.io, options.home)).state.rows.find(item => item.pairKey === row.pairKey);
|
|
71
76
|
if (before?.state === 'verify' && before.runId !== undefined && before.runId !== options.runId
|
|
72
77
|
&& (await options.isOwnerActive?.(before.runId)) === true) {
|
|
73
78
|
continue;
|
|
74
79
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
const status = await pairLedger(options.io, options.home).transact(tx => {
|
|
81
|
+
const current = tx.rows().find(item => item.pairKey === row.pairKey);
|
|
82
|
+
if (current !== undefined && current.state !== 'verify' && current.state !== 'skipped')
|
|
83
|
+
return 'conflict:' + current.state;
|
|
84
|
+
if (current?.state === 'skipped') {
|
|
85
|
+
const removed = tx.transition(current.id, { type: 'restored' }, current.version);
|
|
86
|
+
if (!removed.ok)
|
|
87
|
+
return 'restore-failed';
|
|
88
|
+
}
|
|
89
|
+
if (current?.state === 'verify')
|
|
90
|
+
return 'existing';
|
|
91
|
+
const made = tx.createVerify({
|
|
92
|
+
id: current?.id ?? rowIdOf(row.pairKey), pairKey: row.pairKey, a: row.a, b: row.b,
|
|
93
|
+
version: 0, createdAt: at(options.now), updatedAt: at(options.now), origin: 'agent',
|
|
94
|
+
state: 'verify', verifySubState: 'unverified', attempts: 0,
|
|
95
|
+
...(row.runId === undefined ? {} : { runId: row.runId }),
|
|
96
|
+
});
|
|
97
|
+
return made.ok ? 'created' : 'invalid:' + made.code;
|
|
98
|
+
});
|
|
99
|
+
if (status.startsWith('conflict:'))
|
|
100
|
+
throw new Error('pair already in ' + status.slice(9));
|
|
101
|
+
if (status === 'restore-failed' || status.startsWith('invalid:'))
|
|
102
|
+
throw new Error('cannot place verify row: ' + status);
|
|
88
103
|
}
|
|
89
104
|
catch (error) {
|
|
90
105
|
totals.failed += 1;
|
|
@@ -94,6 +109,15 @@ export async function runScan(options) {
|
|
|
94
109
|
});
|
|
95
110
|
continue;
|
|
96
111
|
}
|
|
112
|
+
if (options.ports.resolve !== undefined && !(await options.ports.resolve(candidate))) {
|
|
113
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
114
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
115
|
+
return current === undefined ? false : tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
116
|
+
});
|
|
117
|
+
pairs.push(pairOf(row, candidate, 'failed', { failure: { stage: 'decide', code: 'source-gone', message: 'source skill disappeared before round1', retryable: false, retryFrom: 'none' } }));
|
|
118
|
+
totals.failed += 1;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
97
121
|
let round1;
|
|
98
122
|
try {
|
|
99
123
|
round1 = await options.ports.round1(candidate, options.signal);
|
|
@@ -107,23 +131,43 @@ export async function runScan(options) {
|
|
|
107
131
|
break;
|
|
108
132
|
}
|
|
109
133
|
if (round1.kind === 'failed') {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
134
|
+
if (round1.degraded !== undefined)
|
|
135
|
+
degradedNotes.push(...round1.degraded);
|
|
136
|
+
const failure = round1.failure ?? failureOf('decide', 'round1 failed');
|
|
137
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
138
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
139
|
+
if (current === undefined)
|
|
140
|
+
return null;
|
|
141
|
+
const moved = tx.transition(row.id, { type: 'failed', failure }, current.version);
|
|
142
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
143
|
+
});
|
|
144
|
+
if (projected === null) {
|
|
145
|
+
pairs.push(pairOf(row, candidate, 'failed', { failure: failureOf('decide', 'row changed before failure record') }));
|
|
146
|
+
totals.failed += 1;
|
|
147
|
+
continue;
|
|
114
148
|
}
|
|
115
|
-
|
|
149
|
+
row = projected;
|
|
150
|
+
pairs.push(pairOf(row, candidate, 'failed', { failure }));
|
|
116
151
|
totals.failed += 1;
|
|
117
152
|
continue;
|
|
118
153
|
}
|
|
119
154
|
if (round1.kind === 'keep') {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
155
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
156
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
157
|
+
if (current === undefined)
|
|
158
|
+
return null;
|
|
159
|
+
const keepReason = round1.decision?.rationale?.trim();
|
|
160
|
+
const moved = tx.transition(row.id, { type: 'skipped', ...(keepReason === undefined || keepReason === '' ? { reason: 'kept' } : { reason: keepReason }), origin: 'agent' }, current.version);
|
|
161
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
162
|
+
});
|
|
163
|
+
if (projected === null) {
|
|
164
|
+
pairs.push(pairOf(row, candidate, 'failed', { failure: failureOf('decide', 'row changed before keep record') }));
|
|
165
|
+
totals.failed += 1;
|
|
166
|
+
continue;
|
|
125
167
|
}
|
|
126
|
-
|
|
168
|
+
row = projected;
|
|
169
|
+
await recordPair(options.io, options.home, { pairKey: row.pairKey, runId: row.runId ?? 'merge', result: 'kept' });
|
|
170
|
+
pairs.push(pairOf(row, candidate, 'kept', { ...(round1.decision === undefined ? {} : { decision: round1.decision }), ...(round1.usage === undefined ? {} : { usage: round1.usage }) }));
|
|
127
171
|
totals.kept += 1;
|
|
128
172
|
continue;
|
|
129
173
|
}
|
|
@@ -132,30 +176,74 @@ export async function runScan(options) {
|
|
|
132
176
|
totals.failed += 1;
|
|
133
177
|
continue;
|
|
134
178
|
}
|
|
179
|
+
const mergeDecision = options.cards !== undefined ? withDecisionRevisions(round1.decision, options.cards, row.a, row.b) : round1.decision;
|
|
135
180
|
if (options.approval) {
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
181
|
+
const event = { type: 'verify-passed', decision: mergeDecision, target: mergeDecision.target, ...(round1.sourceRevision === undefined ? {} : { sourceRevision: round1.sourceRevision }), ...(mergeDecision.promptVersion === undefined ? {} : { promptVersion: mergeDecision.promptVersion }), ...(mergeDecision.promptHash === undefined ? {} : { promptHash: mergeDecision.promptHash }) };
|
|
182
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
183
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
184
|
+
if (current === undefined)
|
|
185
|
+
return null;
|
|
186
|
+
const moved = tx.transition(row.id, event, current.version);
|
|
187
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
188
|
+
});
|
|
189
|
+
if (projected === null) {
|
|
190
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('decide', 'row changed before pending record') }));
|
|
191
|
+
totals.failed += 1;
|
|
192
|
+
continue;
|
|
140
193
|
}
|
|
141
|
-
|
|
194
|
+
row = projected;
|
|
195
|
+
pairs.push(pairOf(row, candidate, 'pending', { decision: mergeDecision }));
|
|
142
196
|
totals.pending += 1;
|
|
143
197
|
continue;
|
|
144
198
|
}
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
const approved = await pairLedger(options.io, options.home).transact(tx => {
|
|
200
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
201
|
+
if (current === undefined)
|
|
202
|
+
return null;
|
|
203
|
+
const moved = tx.transition(row.id, { type: 'auto-approved', decision: mergeDecision, target: mergeDecision.target, ...(row.runId === undefined ? {} : { runId: row.runId }) }, current.version);
|
|
204
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
205
|
+
});
|
|
206
|
+
if (approved === null) {
|
|
207
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before approve record') }));
|
|
148
208
|
totals.failed += 1;
|
|
149
209
|
continue;
|
|
150
210
|
}
|
|
151
|
-
row =
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
156
|
-
row
|
|
157
|
-
|
|
211
|
+
row = approved;
|
|
212
|
+
let stageEntry;
|
|
213
|
+
if (options.ports.stage !== undefined) {
|
|
214
|
+
const staged = await options.ports.stage(row, mergeDecision);
|
|
215
|
+
if (!staged.ok) {
|
|
216
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', staged.message) }));
|
|
217
|
+
totals.failed += 1;
|
|
218
|
+
continue;
|
|
158
219
|
}
|
|
220
|
+
stageEntry = staged.entry;
|
|
221
|
+
}
|
|
222
|
+
const fused = await pairLedger(options.io, options.home).transact(tx => {
|
|
223
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
224
|
+
if (current === undefined)
|
|
225
|
+
return null;
|
|
226
|
+
const moved = tx.transition(row.id, { type: 'staged' }, current.version);
|
|
227
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
228
|
+
});
|
|
229
|
+
if (fused === null) {
|
|
230
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before staging record') }));
|
|
231
|
+
totals.failed += 1;
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
row = fused;
|
|
235
|
+
if (stageEntry !== undefined)
|
|
236
|
+
await presentStage(options.io, options.home, stageEntry);
|
|
237
|
+
if (isAborted(options.signal)) {
|
|
238
|
+
const projected = await pairLedger(options.io, options.home).transact(tx => {
|
|
239
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
240
|
+
if (current === undefined)
|
|
241
|
+
return null;
|
|
242
|
+
const moved = tx.transition(row.id, { type: 'skipped', reason: 'cancelled' }, current.version);
|
|
243
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
244
|
+
});
|
|
245
|
+
if (projected !== null)
|
|
246
|
+
row = projected;
|
|
159
247
|
stopReason = 'cancelled';
|
|
160
248
|
remaining.push(...candidates.slice(index).map(candidate => ({ pairKey: pairKey(candidate.a, candidate.b), queue: 'fuse' })));
|
|
161
249
|
break;
|
|
@@ -163,33 +251,85 @@ export async function runScan(options) {
|
|
|
163
251
|
await stage('author', { done: index, total: candidates.length });
|
|
164
252
|
let round2;
|
|
165
253
|
try {
|
|
166
|
-
round2 = await options.ports.round2(row,
|
|
254
|
+
round2 = await options.ports.round2(row, mergeDecision, options.signal);
|
|
167
255
|
}
|
|
168
256
|
catch (error) {
|
|
169
257
|
round2 = { kind: 'failed', failure: failureOf('author', error) };
|
|
170
258
|
}
|
|
171
259
|
if (round2.kind === 'merged') {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
260
|
+
finalizeRows.push({ id: row.id, unitId: round2.unitId ?? 'unknown', evidenceSha256: round2.evidenceSha256 ?? 'unknown' });
|
|
261
|
+
pairs.push({ ...pairOf(row, candidate, 'merged', {
|
|
262
|
+
decision: mergeDecision,
|
|
263
|
+
...(round2.unitId === undefined ? {} : { unitId: round2.unitId }),
|
|
264
|
+
...(round2.target === undefined ? {} : { target: round2.target }),
|
|
265
|
+
...(round2.usage === undefined ? {} : { usage: round2.usage }),
|
|
266
|
+
...(round2.lengths === undefined ? {} : { lengths: round2.lengths }),
|
|
267
|
+
archived: round2.archived ?? [],
|
|
268
|
+
}), ...(round2.validationPlan === undefined ? {} : { validationPlan: round2.validationPlan }) });
|
|
181
269
|
totals.merged += 1;
|
|
182
270
|
continue;
|
|
183
271
|
}
|
|
184
272
|
const failed = round2.failure ?? failureOf('author', 'round2 failed');
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
273
|
+
if (failed.code === 'merge-over-limit' || failed.code === 'limit' || failed.code === 'merge-validation') {
|
|
274
|
+
const reason = failed.code === 'merge-validation' ? 'validation-failed' : 'merge-over-limit';
|
|
275
|
+
const skipped = await pairLedger(options.io, options.home).transact(tx => {
|
|
276
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
277
|
+
return current === undefined ? false : tx.transition(row.id, { type: 'skipped', reason, origin: 'system', skipSource: 'system' }, current.version).ok;
|
|
278
|
+
});
|
|
279
|
+
if (skipped)
|
|
280
|
+
await dropStage(options.io, options.home, row.id);
|
|
281
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
282
|
+
totals.failed += 1;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (failed.code === 'target-changed' || failed.code === 'target-taken' || failed.code === 'source-changed' || failed.code === 'support-changed' || failed.code === 'source-gone') {
|
|
286
|
+
const code = failed.code === 'target-changed' ? 'target-taken' : failed.code === 'support-changed' ? 'source-changed' : failed.code;
|
|
287
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
288
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
289
|
+
if (current === undefined)
|
|
290
|
+
return false;
|
|
291
|
+
if (code === 'source-gone')
|
|
292
|
+
return tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
293
|
+
return tx.transition(row.id, { type: 'skipped', reason: code, origin: 'system', skipSource: 'system' }, current.version).ok;
|
|
294
|
+
});
|
|
295
|
+
await dropStage(options.io, options.home, row.id);
|
|
296
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: { ...failed, code, retryable: false, retryFrom: 'none' } }));
|
|
297
|
+
totals.failed += 1;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const terminal = failed.code === 'commit-failed' || failed.code === 'validation-failed' || failed.code === 'merge-validation';
|
|
301
|
+
const projected = terminal ? null : await pairLedger(options.io, options.home).transact(tx => {
|
|
302
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
303
|
+
if (current === undefined)
|
|
304
|
+
return null;
|
|
305
|
+
const moved = tx.transition(row.id, { type: 'failed', failure: failed }, current.version);
|
|
306
|
+
return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
|
|
307
|
+
});
|
|
308
|
+
if (projected === null && terminal) {
|
|
309
|
+
await pairLedger(options.io, options.home).transact(tx => {
|
|
310
|
+
const current = tx.rows().find(item => item.id === row.id);
|
|
311
|
+
return current === undefined ? false : tx.remove(row.id, 'superseded', undefined, current.version).ok;
|
|
312
|
+
});
|
|
313
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
314
|
+
totals.failed += 1;
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (projected === null) {
|
|
318
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failureOf('author', 'row changed before failure record') }));
|
|
319
|
+
totals.failed += 1;
|
|
320
|
+
continue;
|
|
189
321
|
}
|
|
190
|
-
|
|
322
|
+
row = projected;
|
|
323
|
+
pairs.push(pairOf(row, candidate, 'failed', { decision: mergeDecision, failure: failed }));
|
|
191
324
|
totals.failed += 1;
|
|
192
325
|
}
|
|
326
|
+
const selectionReportOnly = candidates.reportOnly ?? [];
|
|
327
|
+
const selectionReportOnlyPairs = selectionReportOnly.flatMap(raw => {
|
|
328
|
+
const value = raw;
|
|
329
|
+
if (typeof value.a !== 'string' || typeof value.b !== 'string')
|
|
330
|
+
return [];
|
|
331
|
+
return [{ id: '', pairKey: typeof value.pairKey === 'string' ? value.pairKey : pairKey(value.a, value.b), a: value.a, b: value.b, queue: 'verify', candidate: { a: value.a, b: value.b }, outcome: 'kept', archived: [], ...(value.gate === undefined ? {} : { gate: value.gate }) }];
|
|
332
|
+
});
|
|
193
333
|
return {
|
|
194
334
|
stopReason,
|
|
195
335
|
scope: {
|
|
@@ -198,8 +338,26 @@ export async function runScan(options) {
|
|
|
198
338
|
},
|
|
199
339
|
pairs,
|
|
200
340
|
totals,
|
|
201
|
-
notes: [...(options.notes ?? [])],
|
|
341
|
+
notes: [...(options.notes ?? []), ...degradedNotes],
|
|
342
|
+
reportOnly: [...pairs.filter(pair => pair.outcome === 'kept' || pair.gate?.level === 'L0' || pair.gate?.level === 'L1'), ...selectionReportOnlyPairs],
|
|
343
|
+
validationFailures: pairs.filter(pair => pair.failure !== undefined && (pair.failure.stage === 'validate' || pair.failure.code === 'merge-validation' || pair.failure.code === 'merge-over-limit')),
|
|
344
|
+
archived: [...new Set(pairs.flatMap(pair => pair.archived))],
|
|
345
|
+
...(pairs.find(pair => pair.outcome === 'merged' && pair.unitId !== undefined) === undefined ? {} : { commit: (() => { const pair = pairs.find(item => item.outcome === 'merged' && item.unitId !== undefined); return { ...(pair.unitId === undefined ? {} : { unitId: pair.unitId }), ...(pair.target === undefined ? {} : { target: pair.target.actual ?? pair.target.requested }), ...(pair.target === undefined ? {} : { created: pair.target.created }) }; })() }),
|
|
346
|
+
...(pairs.every(pair => pair.usage === undefined) ? {} : { usage: { inputTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.inputTokens ?? 0), 0), outputTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.outputTokens ?? 0), 0), totalTokens: pairs.reduce((sum, pair) => sum + (pair.usage?.totalTokens ?? 0), 0), costCny: pairs.reduce((sum, pair) => sum + (pair.usage?.costCny ?? 0), 0) } }),
|
|
347
|
+
timing: { screeningMs: 0, gateMs: 0, agentMs: Math.max(0, options.now() - startedAt), commitMs: 0 },
|
|
348
|
+
criteria: pairs.map(pair => pair.criteria ?? criteriaFrom(pair.gate?.level ?? 'L2', pair.gate?.verdict ?? 0, pair.decision?.shouldMerge === true ? 'merge' : 'keep')),
|
|
202
349
|
...(remaining.length === 0 ? {} : { remaining }),
|
|
350
|
+
...(finalizeRows.length === 0 ? {} : { finalize: async () => {
|
|
351
|
+
for (const item of finalizeRows) {
|
|
352
|
+
const removed = await pairLedger(options.io, options.home).transact(tx => {
|
|
353
|
+
const current = tx.rows().find(entry => entry.id === item.id);
|
|
354
|
+
return current === undefined ? true : tx.remove(item.id, 'committed', { unitId: item.unitId, evidenceSha256: item.evidenceSha256 }, current.version).ok;
|
|
355
|
+
});
|
|
356
|
+
if (!removed)
|
|
357
|
+
throw new Error('row changed before commit record: ' + item.id);
|
|
358
|
+
await dropStage(options.io, options.home, item.id);
|
|
359
|
+
}
|
|
360
|
+
} })
|
|
203
361
|
};
|
|
204
362
|
}
|
|
205
363
|
//# sourceMappingURL=scan-run.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real screening port: lexical floor + optional C3 proposal + optional L0–L2 gate.
|
|
3
|
+
* Each stage is independently testable; this file only composes their frozen contracts.
|
|
4
|
+
*/
|
|
5
|
+
import { type MergeCandidate } from './contracts.ts';
|
|
6
|
+
import { type SkillCard, type ScreeningOptions } from './screening.ts';
|
|
7
|
+
import { type ModelPort } from './propose.ts';
|
|
8
|
+
import type { GateCache, GateModelPort } from './ladder.ts';
|
|
9
|
+
export interface ScreeningForRunOptions extends Omit<BuildScreeningOptions, 'signal'> {
|
|
10
|
+
readonly runSeed: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BuildScreeningOptions {
|
|
13
|
+
readonly cards: readonly SkillCard[];
|
|
14
|
+
readonly signal: AbortSignal;
|
|
15
|
+
readonly lexical?: ScreeningOptions;
|
|
16
|
+
readonly propose?: {
|
|
17
|
+
readonly model: ModelPort;
|
|
18
|
+
readonly shardSize?: number;
|
|
19
|
+
readonly maxPairs?: number;
|
|
20
|
+
};
|
|
21
|
+
readonly gate?: {
|
|
22
|
+
readonly model: GateModelPort;
|
|
23
|
+
readonly modelName: string;
|
|
24
|
+
readonly runSeed: string;
|
|
25
|
+
readonly cache?: GateCache;
|
|
26
|
+
readonly auditRate?: number;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export interface ScreeningPortStats {
|
|
30
|
+
readonly lexical: number;
|
|
31
|
+
readonly proposed: number;
|
|
32
|
+
readonly candidates: number;
|
|
33
|
+
readonly kept: number;
|
|
34
|
+
readonly dropped: number;
|
|
35
|
+
readonly gateCalls: number;
|
|
36
|
+
readonly gateErrors: number;
|
|
37
|
+
readonly shards: number;
|
|
38
|
+
}
|
|
39
|
+
export interface ScreeningPortResult {
|
|
40
|
+
readonly candidates: readonly MergeCandidate[];
|
|
41
|
+
readonly stats: ScreeningPortStats;
|
|
42
|
+
readonly degraded: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export declare function buildScreeningCandidates(options: BuildScreeningOptions): Promise<ScreeningPortResult>;
|
|
45
|
+
/** Build the run-scoped screening function; the run id seeds the 10% audit. */
|
|
46
|
+
export declare function createScreeningForRun(options: ScreeningForRunOptions): (signal: AbortSignal) => Promise<readonly MergeCandidate[]>;
|
|
47
|
+
//# sourceMappingURL=screening-port.d.ts.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The real screening port: lexical floor + optional C3 proposal + optional L0–L2 gate.
|
|
3
|
+
* Each stage is independently testable; this file only composes their frozen contracts.
|
|
4
|
+
*/
|
|
5
|
+
import { pairKey } from "./contracts.js";
|
|
6
|
+
import { lexicalCandidates } from "./screening.js";
|
|
7
|
+
import { proposePairs } from "./propose.js";
|
|
8
|
+
import { filterCandidates } from "./gate-filter.js";
|
|
9
|
+
export async function buildScreeningCandidates(options) {
|
|
10
|
+
const lexical = lexicalCandidates(options.cards, options.lexical);
|
|
11
|
+
const merged = new Map();
|
|
12
|
+
for (const candidate of lexical.candidates)
|
|
13
|
+
merged.set(pairKey(candidate.a, candidate.b), candidate);
|
|
14
|
+
const degraded = [];
|
|
15
|
+
let proposed = 0;
|
|
16
|
+
let shards = 0;
|
|
17
|
+
if (options.propose !== undefined) {
|
|
18
|
+
const proposal = await proposePairs(options.cards, options.propose.model, {
|
|
19
|
+
signal: options.signal,
|
|
20
|
+
...(options.propose.shardSize === undefined ? {} : { shardSize: options.propose.shardSize }),
|
|
21
|
+
...(options.propose.maxPairs === undefined ? {} : { maxPairs: options.propose.maxPairs }),
|
|
22
|
+
});
|
|
23
|
+
proposed = proposal.candidates.length;
|
|
24
|
+
shards = proposal.shards;
|
|
25
|
+
degraded.push(...proposal.degraded);
|
|
26
|
+
for (const candidate of proposal.candidates) {
|
|
27
|
+
const key = pairKey(candidate.a, candidate.b);
|
|
28
|
+
const existing = merged.get(key);
|
|
29
|
+
if (existing === undefined) {
|
|
30
|
+
merged.set(key, candidate);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const arms = [...new Set([...(existing.arms ?? []), ...(candidate.arms ?? [])])];
|
|
34
|
+
merged.set(key, {
|
|
35
|
+
...existing,
|
|
36
|
+
...(candidate.reason === undefined || existing.reason !== undefined ? {} : { reason: candidate.reason }),
|
|
37
|
+
...(arms.length === 0 ? {} : { arms }),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let candidates = [...merged.values()].sort((left, right) => (right.score ?? 0) - (left.score ?? 0)
|
|
42
|
+
|| left.a.localeCompare(right.a)
|
|
43
|
+
|| left.b.localeCompare(right.b));
|
|
44
|
+
let kept = candidates.length;
|
|
45
|
+
let dropped = 0;
|
|
46
|
+
let gateCalls = 0;
|
|
47
|
+
let gateErrors = 0;
|
|
48
|
+
if (options.gate !== undefined) {
|
|
49
|
+
const facts = options.cards.map(card => ({ name: card.name, description: card.description, body: card.body }));
|
|
50
|
+
const filtered = await filterCandidates(candidates, {
|
|
51
|
+
facts,
|
|
52
|
+
model: options.gate.model,
|
|
53
|
+
modelName: options.gate.modelName,
|
|
54
|
+
signal: options.signal,
|
|
55
|
+
runSeed: options.gate.runSeed,
|
|
56
|
+
...(options.gate.cache === undefined ? {} : { cache: options.gate.cache }),
|
|
57
|
+
...(options.gate.auditRate === undefined ? {} : { auditRate: options.gate.auditRate }),
|
|
58
|
+
});
|
|
59
|
+
candidates = filtered.kept.map(decision => decision.candidate);
|
|
60
|
+
kept = filtered.kept.length;
|
|
61
|
+
dropped = filtered.dropped.length;
|
|
62
|
+
gateCalls = filtered.calls;
|
|
63
|
+
gateErrors = filtered.errors.length;
|
|
64
|
+
for (const error of filtered.errors)
|
|
65
|
+
degraded.push('gate error: ' + error.candidate.a + ' + ' + error.candidate.b + ': ' + error.message);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
candidates,
|
|
69
|
+
stats: {
|
|
70
|
+
lexical: lexical.candidates.length,
|
|
71
|
+
proposed,
|
|
72
|
+
candidates: merged.size,
|
|
73
|
+
kept,
|
|
74
|
+
dropped,
|
|
75
|
+
gateCalls,
|
|
76
|
+
gateErrors,
|
|
77
|
+
shards,
|
|
78
|
+
},
|
|
79
|
+
degraded,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Build the run-scoped screening function; the run id seeds the 10% audit. */
|
|
83
|
+
export function createScreeningForRun(options) {
|
|
84
|
+
const gate = options.gate === undefined ? undefined : { ...options.gate, runSeed: options.runSeed };
|
|
85
|
+
return async (signal) => (await buildScreeningCandidates({
|
|
86
|
+
cards: options.cards,
|
|
87
|
+
signal,
|
|
88
|
+
...(options.lexical === undefined ? {} : { lexical: options.lexical }),
|
|
89
|
+
...(options.propose === undefined ? {} : { propose: options.propose }),
|
|
90
|
+
...(gate === undefined ? {} : { gate }),
|
|
91
|
+
})).candidates;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=screening-port.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { MergeCandidate } from './contracts.ts';
|
|
2
|
+
export interface SkillCard {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly description: string;
|
|
5
|
+
readonly body: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ScreeningOptions {
|
|
8
|
+
/** Body Jaccard at/above this is a candidate on its own. */
|
|
9
|
+
readonly bodyGate?: number;
|
|
10
|
+
/** Description Jaccard at/above this is a candidate when the body also clears minAffinity. */
|
|
11
|
+
readonly summaryGate?: number;
|
|
12
|
+
/** Minimum body affinity for a summary-led candidate. */
|
|
13
|
+
readonly minAffinity?: number;
|
|
14
|
+
/** Upper bound after sorting by score; absent = no cap. */
|
|
15
|
+
readonly maxCandidates?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface ScreeningStats {
|
|
18
|
+
readonly skills: number;
|
|
19
|
+
readonly filtered: number;
|
|
20
|
+
readonly candidates: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ScreeningResult {
|
|
23
|
+
readonly candidates: readonly MergeCandidate[];
|
|
24
|
+
readonly stats: ScreeningStats;
|
|
25
|
+
}
|
|
26
|
+
/** Shannon entropy over characters; a cheap prefilter signal, not a name-quality judge. */
|
|
27
|
+
export declare function nameEntropy(name: string): number;
|
|
28
|
+
export declare function prefilter(cards: readonly SkillCard[]): readonly SkillCard[];
|
|
29
|
+
export declare function lexicalCandidates(cards: readonly SkillCard[], options?: ScreeningOptions): ScreeningResult;
|
|
30
|
+
//# sourceMappingURL=screening.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic candidate screening: name prefilter + lexical affinity.
|
|
3
|
+
*
|
|
4
|
+
* The semantic proposer (C3) is a separate port; this module is the zero-cost floor that
|
|
5
|
+
* must work without a model. It reuses core `vocabulary`/`affinity` so drift/dedup and
|
|
6
|
+
* merge screening share one tokenizer.
|
|
7
|
+
*/
|
|
8
|
+
import { affinity, vocabulary } from '@lmzhen/dsh-evolution-core';
|
|
9
|
+
/** Shannon entropy over characters; a cheap prefilter signal, not a name-quality judge. */
|
|
10
|
+
export function nameEntropy(name) {
|
|
11
|
+
if (name === '')
|
|
12
|
+
return 0;
|
|
13
|
+
const counts = new Map();
|
|
14
|
+
for (const char of name)
|
|
15
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
16
|
+
let entropy = 0;
|
|
17
|
+
for (const count of counts.values()) {
|
|
18
|
+
const p = count / name.length;
|
|
19
|
+
entropy -= p * Math.log2(p);
|
|
20
|
+
}
|
|
21
|
+
return entropy;
|
|
22
|
+
}
|
|
23
|
+
const MIN_NAME_ENTROPY = 1.5;
|
|
24
|
+
const MIN_NAME_LENGTH = 6;
|
|
25
|
+
export function prefilter(cards) {
|
|
26
|
+
return cards.filter((card) => {
|
|
27
|
+
const name = card.name.trim();
|
|
28
|
+
if (name.length < MIN_NAME_LENGTH)
|
|
29
|
+
return false;
|
|
30
|
+
if (nameEntropy(name) < MIN_NAME_ENTROPY)
|
|
31
|
+
return false;
|
|
32
|
+
return card.body.trim() !== '' || card.description.trim() !== '';
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function sortedPair(a, b) {
|
|
36
|
+
return a < b ? [a, b] : [b, a];
|
|
37
|
+
}
|
|
38
|
+
export function lexicalCandidates(cards, options = {}) {
|
|
39
|
+
const bodyGate = options.bodyGate ?? 0.9;
|
|
40
|
+
const summaryGate = options.summaryGate ?? 0.5;
|
|
41
|
+
const minAffinity = options.minAffinity ?? 0.3;
|
|
42
|
+
const eligible = prefilter(cards);
|
|
43
|
+
const vectors = eligible.map(card => ({
|
|
44
|
+
card,
|
|
45
|
+
summary: vocabulary(card.description),
|
|
46
|
+
body: vocabulary(card.body),
|
|
47
|
+
}));
|
|
48
|
+
const candidates = [];
|
|
49
|
+
for (let i = 0; i < vectors.length; i += 1) {
|
|
50
|
+
for (let j = i + 1; j < vectors.length; j += 1) {
|
|
51
|
+
const left = vectors[i];
|
|
52
|
+
const right = vectors[j];
|
|
53
|
+
if (left === undefined || right === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
const summary = affinity(left.summary, right.summary);
|
|
56
|
+
const body = affinity(left.body, right.body);
|
|
57
|
+
const arms = [];
|
|
58
|
+
if (body >= bodyGate)
|
|
59
|
+
arms.push('body');
|
|
60
|
+
if (summary >= summaryGate && body >= minAffinity)
|
|
61
|
+
arms.push('summary');
|
|
62
|
+
if (arms.length === 0)
|
|
63
|
+
continue;
|
|
64
|
+
const [a, b] = sortedPair(left.card.name, right.card.name);
|
|
65
|
+
candidates.push({ a, b, score: Math.max(body, summary), arms, source: 'lexical' });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
candidates.sort((left, right) => (right.score ?? 0) - (left.score ?? 0) || left.a.localeCompare(right.a) || left.b.localeCompare(right.b));
|
|
69
|
+
const capped = options.maxCandidates === undefined ? candidates : candidates.slice(0, options.maxCandidates);
|
|
70
|
+
return {
|
|
71
|
+
candidates: capped,
|
|
72
|
+
stats: { skills: cards.length, filtered: cards.length - eligible.length, candidates: capped.length },
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=screening.js.map
|
|
@@ -9,6 +9,13 @@ export declare class StageToolRegistry {
|
|
|
9
9
|
bind(childId: string, context: StageToolContext): void;
|
|
10
10
|
unbind(childId: string): void;
|
|
11
11
|
clear(): void;
|
|
12
|
+
/** Diagnostic view for a refused tool call; names only, no content. */
|
|
13
|
+
snapshot(exec: unknown): {
|
|
14
|
+
readonly childId?: string;
|
|
15
|
+
readonly bound: number;
|
|
16
|
+
readonly authors: number;
|
|
17
|
+
readonly keys: readonly string[];
|
|
18
|
+
};
|
|
12
19
|
resolve(exec: unknown): StageToolContext | undefined;
|
|
13
20
|
}
|
|
14
21
|
//# sourceMappingURL=stage-tool-registry.d.ts.map
|