@lmzhen/dsh-evolution-merge 0.30.24 → 0.30.25
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/lib/index.js +518 -182
- package/lib/types/approve-command.js +2 -1
- package/lib/types/authoring-flow.d.ts +5 -0
- package/lib/types/authoring-flow.js +8 -5
- package/lib/types/commands.d.ts +7 -0
- package/lib/types/commands.js +8 -1
- package/lib/types/commit-execute.d.ts +4 -0
- package/lib/types/commit-execute.js +8 -5
- package/lib/types/commit-receipt.js +3 -1
- package/lib/types/contracts.d.ts +20 -1
- package/lib/types/doctor.js +1 -1
- package/lib/types/domain/errors.js +5 -1
- package/lib/types/domain/merge-snapshot.d.ts +12 -3
- package/lib/types/domain/merge-snapshot.js +9 -4
- package/lib/types/domain/reconcile.js +1 -1
- package/lib/types/domain/row-v4.d.ts +4 -0
- package/lib/types/domain/row-v4.js +9 -9
- package/lib/types/domain/run-coordinator.d.ts +9 -0
- package/lib/types/domain/stage-aggregate.d.ts +6 -1
- package/lib/types/domain/stage-aggregate.js +16 -9
- package/lib/types/domain/validation-plan.js +5 -1
- package/lib/types/index.js +14 -2
- package/lib/types/kernel/adapter-v4.d.ts +12 -0
- package/lib/types/kernel/adapter-v4.js +54 -0
- package/lib/types/kernel/failure.d.ts +8 -0
- package/lib/types/kernel/failure.js +33 -0
- package/lib/types/kernel/index.d.ts +9 -0
- package/lib/types/kernel/index.js +9 -0
- package/lib/types/kernel/invariants.d.ts +4 -0
- package/lib/types/kernel/invariants.js +47 -0
- package/lib/types/kernel/ledger.d.ts +46 -0
- package/lib/types/kernel/ledger.js +62 -0
- package/lib/types/kernel/machine.d.ts +13 -0
- package/lib/types/kernel/machine.js +108 -0
- package/lib/types/kernel/model.d.ts +154 -0
- package/lib/types/kernel/model.js +5 -0
- package/lib/types/kernel/projection.d.ts +28 -0
- package/lib/types/kernel/projection.js +44 -0
- package/lib/types/kernel/recovery.d.ts +31 -0
- package/lib/types/kernel/recovery.js +19 -0
- package/lib/types/kernel/unit.d.ts +43 -0
- package/lib/types/kernel/unit.js +63 -0
- package/lib/types/merge-routes.js +9 -0
- package/lib/types/new-child-authoring.d.ts +1 -0
- package/lib/types/new-child-authoring.js +15 -7
- package/lib/types/pending-actions.d.ts +1 -1
- package/lib/types/pending-actions.js +4 -0
- package/lib/types/recovery-coordinator.d.ts +2 -0
- package/lib/types/recovery-coordinator.js +14 -4
- package/lib/types/recovery-projection.d.ts +4 -2
- package/lib/types/recovery-projection.js +3 -3
- package/lib/types/resume-command.js +2 -1
- package/lib/types/round2-outcome.js +26 -9
- package/lib/types/run-shell.d.ts +1 -0
- package/lib/types/run-shell.js +57 -15
- package/lib/types/runtime.d.ts +4 -1
- package/lib/types/runtime.js +34 -22
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +1 -0
- package/lib/types/scan-ports.js +3 -0
- package/lib/types/scan-run.d.ts +6 -1
- package/lib/types/scan-run.js +34 -18
- package/lib/types/surface/merge-view.js +12 -4
- package/lib/types/undo-operation.d.ts +2 -1
- package/lib/types/undo-operation.js +10 -4
- package/lib/types/wire-projection.js +7 -2
- package/lib/types/wire.d.ts +1 -0
- package/package.json +4 -4
- package/lib/types/adapters/model.d.ts +0 -31
- package/lib/types/adapters/model.js +0 -47
- package/lib/types/gate-cache.d.ts +0 -12
- package/lib/types/gate-cache.js +0 -36
- package/lib/types/gate-filter.d.ts +0 -31
- package/lib/types/gate-filter.js +0 -64
- package/lib/types/ladder.d.ts +0 -65
- package/lib/types/ladder.js +0 -162
- package/lib/types/propose.d.ts +0 -26
- package/lib/types/propose.js +0 -69
- package/lib/types/screening-port.d.ts +0 -47
- package/lib/types/screening-port.js +0 -93
- package/lib/types/screening.d.ts +0 -30
- package/lib/types/screening.js +0 -75
|
@@ -10,10 +10,15 @@ export function actionRefs(row) {
|
|
|
10
10
|
return [{ id: 'discard', command: base + 'discard ' + row.id, confirm: true, enabled: true }, { id: 'pair', command: base + 'pair ' + row.a + ' ' + row.b, confirm: true, enabled: true }];
|
|
11
11
|
if (row.state === 'pending')
|
|
12
12
|
return [action('approve'), action('reject', true), action('discard', true)];
|
|
13
|
-
if (row.state === 'approved' || row.state === 'fuse')
|
|
14
|
-
|
|
13
|
+
if (row.state === 'approved' || row.state === 'fuse') {
|
|
14
|
+
if (row.failure !== undefined && row.failure.retryable === false)
|
|
15
|
+
return [{ id: 'pair', command: base + 'pair ' + row.a + ' ' + row.b, confirm: true, enabled: true }, action('discard', true)];
|
|
16
|
+
return [{ id: 'retry-resume', command: base + 'resume --retry ' + row.id, confirm: true, enabled: true }, action('discard', true), action('status')];
|
|
17
|
+
}
|
|
15
18
|
if (row.state === 'skipped')
|
|
16
19
|
return [action('restore', true)];
|
|
20
|
+
if (row.state === 'verify')
|
|
21
|
+
return [{ id: 'retry-candidates', command: base + 'candidates --retry ' + row.id, confirm: false, enabled: true }];
|
|
17
22
|
return [action('pair')];
|
|
18
23
|
}
|
|
19
24
|
export function toRowFact(row, actions = actionRefs(row)) {
|
package/lib/types/wire.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-merge",
|
|
3
3
|
"description": "Skill auto-merge domain: screening, two-round authoring, queues and commit orchestration (community build)",
|
|
4
|
-
"version": "0.30.
|
|
4
|
+
"version": "0.30.25",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.30.
|
|
36
|
-
"@lmzhen/dsh-evolution-commands": "^0.30.
|
|
37
|
-
"@lmzhen/dsh-evolution-merge-engine": "0.30.
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.30.25",
|
|
36
|
+
"@lmzhen/dsh-evolution-commands": "^0.30.25",
|
|
37
|
+
"@lmzhen/dsh-evolution-merge-engine": "0.30.25"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.5"
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structural ModelPort adapter over the platform llm service. The platform exposes either
|
|
3
|
-
* `stream()` (the current shape) or the older prepareCall()/stream pair; both return raw text
|
|
4
|
-
* because the gate and proposer own JSON extraction.
|
|
5
|
-
*/
|
|
6
|
-
import type { ModelPort } from '../propose.ts';
|
|
7
|
-
export interface ModelRoute {
|
|
8
|
-
readonly provider: string;
|
|
9
|
-
readonly model: string;
|
|
10
|
-
}
|
|
11
|
-
export interface LlmChunkLike {
|
|
12
|
-
readonly type: string;
|
|
13
|
-
readonly text?: string;
|
|
14
|
-
readonly usage?: unknown;
|
|
15
|
-
readonly reason?: {
|
|
16
|
-
readonly kind?: string;
|
|
17
|
-
readonly failure?: {
|
|
18
|
-
readonly message?: string;
|
|
19
|
-
};
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export interface LlmCallLike {
|
|
23
|
-
readonly config: Record<string, unknown>;
|
|
24
|
-
stream(input: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
|
|
25
|
-
}
|
|
26
|
-
export interface LlmServiceLike {
|
|
27
|
-
prepareCall?(config: Record<string, unknown>, signal?: AbortSignal): Promise<LlmCallLike>;
|
|
28
|
-
stream?(options: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
|
|
29
|
-
}
|
|
30
|
-
export declare function createModelPort(llm: LlmServiceLike, route: () => ModelRoute): ModelPort;
|
|
31
|
-
//# sourceMappingURL=model.d.ts.map
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { normalizeProviderKey } from "../domain/provider-key.js";
|
|
2
|
-
function append(chunk, text) {
|
|
3
|
-
if (chunk.type === 'text-delta' || chunk.type === 'text')
|
|
4
|
-
return text + (chunk.text ?? '');
|
|
5
|
-
if (chunk.type === 'finish' && chunk.reason?.kind === 'error') {
|
|
6
|
-
throw new Error('model call failed: ' + (chunk.reason.failure?.message ?? 'unknown'));
|
|
7
|
-
}
|
|
8
|
-
return text;
|
|
9
|
-
}
|
|
10
|
-
export function createModelPort(llm, route) {
|
|
11
|
-
return {
|
|
12
|
-
async callJson(request) {
|
|
13
|
-
const selection = route();
|
|
14
|
-
let text = '';
|
|
15
|
-
if (llm.stream !== undefined) {
|
|
16
|
-
for await (const chunk of llm.stream({
|
|
17
|
-
provider: normalizeProviderKey(selection.provider),
|
|
18
|
-
model: selection.model,
|
|
19
|
-
system: request.system,
|
|
20
|
-
messages: [{ role: 'user', content: [{ type: 'text', text: request.user }] }],
|
|
21
|
-
maxTokens: request.maxTokens,
|
|
22
|
-
temperature: 0,
|
|
23
|
-
signal: request.signal,
|
|
24
|
-
}))
|
|
25
|
-
text = append(chunk, text);
|
|
26
|
-
return text;
|
|
27
|
-
}
|
|
28
|
-
if (llm.prepareCall === undefined)
|
|
29
|
-
throw new Error('llm service has neither stream nor prepareCall');
|
|
30
|
-
const call = await llm.prepareCall({
|
|
31
|
-
provider: selection.provider,
|
|
32
|
-
model: selection.model,
|
|
33
|
-
maxTokens: request.maxTokens,
|
|
34
|
-
temperature: 0,
|
|
35
|
-
}, request.signal);
|
|
36
|
-
for await (const chunk of call.stream({
|
|
37
|
-
...call.config,
|
|
38
|
-
system: request.system,
|
|
39
|
-
messages: [{ role: 'user', content: [{ type: 'text', text: request.user }] }],
|
|
40
|
-
signal: request.signal,
|
|
41
|
-
}))
|
|
42
|
-
text = append(chunk, text);
|
|
43
|
-
return text;
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=model.js.map
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
-
import { GateCache } from './ladder.ts';
|
|
3
|
-
export declare const GATE_CACHE_VERSION = 1;
|
|
4
|
-
export declare function gateCachePath(home: string): string;
|
|
5
|
-
export interface LoadGateCacheResult {
|
|
6
|
-
readonly cache: GateCache;
|
|
7
|
-
readonly note?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function loadGateCache(io: EvolutionIoLike, home: string): Promise<LoadGateCacheResult>;
|
|
10
|
-
export declare function saveGateCache(io: EvolutionIoLike, home: string, cache: GateCache): Promise<void>;
|
|
11
|
-
export declare function hasGateCache(cache: GateCache): boolean;
|
|
12
|
-
//# sourceMappingURL=gate-cache.d.ts.map
|
package/lib/types/gate-cache.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-backed gate cache: <home>/merge/gate-cache.json.
|
|
3
|
-
* Reads are three-state; a malformed file is reported, not overwritten. Writes are one
|
|
4
|
-
* transact so two runs cannot interleave a partial cache.
|
|
5
|
-
*/
|
|
6
|
-
import { join } from 'node:path';
|
|
7
|
-
import { probeText, transactIo } from '@lmzhen/dsh-evolution-core';
|
|
8
|
-
import { GateCache } from "./ladder.js";
|
|
9
|
-
export const GATE_CACHE_VERSION = 1;
|
|
10
|
-
export function gateCachePath(home) {
|
|
11
|
-
return join(home, 'merge', 'gate-cache.json');
|
|
12
|
-
}
|
|
13
|
-
export async function loadGateCache(io, home) {
|
|
14
|
-
const probe = await probeText(io, gateCachePath(home));
|
|
15
|
-
if (probe.kind === 'absent')
|
|
16
|
-
return { cache: new GateCache() };
|
|
17
|
-
if (probe.kind === 'unknown')
|
|
18
|
-
return { cache: new GateCache(), note: probe.reason };
|
|
19
|
-
try {
|
|
20
|
-
const parsed = JSON.parse(probe.value);
|
|
21
|
-
if (parsed.schemaVersion !== GATE_CACHE_VERSION || typeof parsed.entries !== 'object' || parsed.entries === null) {
|
|
22
|
-
return { cache: new GateCache(), note: 'gate cache has an unexpected shape' };
|
|
23
|
-
}
|
|
24
|
-
return { cache: GateCache.fromObject(parsed.entries) };
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
return { cache: new GateCache(), note: 'gate cache is not JSON: ' + (error instanceof Error ? error.message : String(error)) };
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
export async function saveGateCache(io, home, cache) {
|
|
31
|
-
await transactIo(io, gateCachePath(home), () => JSON.stringify({ schemaVersion: GATE_CACHE_VERSION, entries: cache.toObject() }, null, 2) + '\n');
|
|
32
|
-
}
|
|
33
|
-
export function hasGateCache(cache) {
|
|
34
|
-
return cache.size > 0;
|
|
35
|
-
}
|
|
36
|
-
//# sourceMappingURL=gate-cache.js.map
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gate filter: turn lexical/proposed candidates into the candidates scan may process.
|
|
3
|
-
* L0 keeps are dropped unless the deterministic 10% audit re-runs L2 and overrides.
|
|
4
|
-
*/
|
|
5
|
-
import type { MergeCandidate } from './contracts.ts';
|
|
6
|
-
import { type GateCache, type GateFacts, type GateModelPort, type GateVerdict } from './ladder.ts';
|
|
7
|
-
export interface GateFilterOptions {
|
|
8
|
-
readonly facts: readonly GateFacts[];
|
|
9
|
-
readonly model: GateModelPort;
|
|
10
|
-
readonly modelName: string;
|
|
11
|
-
readonly signal: AbortSignal;
|
|
12
|
-
readonly runSeed: string;
|
|
13
|
-
readonly cache?: GateCache;
|
|
14
|
-
readonly auditRate?: number;
|
|
15
|
-
}
|
|
16
|
-
export interface GateDecision {
|
|
17
|
-
readonly candidate: MergeCandidate;
|
|
18
|
-
readonly verdicts: readonly GateVerdict[];
|
|
19
|
-
readonly auditOverride: boolean;
|
|
20
|
-
}
|
|
21
|
-
export interface GateFilterResult {
|
|
22
|
-
readonly kept: readonly GateDecision[];
|
|
23
|
-
readonly dropped: readonly GateDecision[];
|
|
24
|
-
readonly errors: readonly {
|
|
25
|
-
readonly candidate: MergeCandidate;
|
|
26
|
-
readonly message: string;
|
|
27
|
-
}[];
|
|
28
|
-
readonly calls: number;
|
|
29
|
-
}
|
|
30
|
-
export declare function filterCandidates(candidates: readonly MergeCandidate[], options: GateFilterOptions): Promise<GateFilterResult>;
|
|
31
|
-
//# sourceMappingURL=gate-filter.d.ts.map
|
package/lib/types/gate-filter.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { pairKey } from "./contracts.js";
|
|
2
|
-
import { runLadder, selectedForAudit } from "./ladder.js";
|
|
3
|
-
function withGate(candidate, verdicts, auditOverride) {
|
|
4
|
-
const last = verdicts[verdicts.length - 1];
|
|
5
|
-
if (last === undefined)
|
|
6
|
-
return candidate;
|
|
7
|
-
return {
|
|
8
|
-
...candidate,
|
|
9
|
-
gate: {
|
|
10
|
-
level: last.level,
|
|
11
|
-
verdict: last.verdict,
|
|
12
|
-
reason: last.reason,
|
|
13
|
-
...(last.cached ? { cached: true } : {}),
|
|
14
|
-
...(auditOverride ? { auditOverride: true } : {}),
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export async function filterCandidates(candidates, options) {
|
|
19
|
-
const kept = [];
|
|
20
|
-
const dropped = [];
|
|
21
|
-
const errors = [];
|
|
22
|
-
let calls = 0;
|
|
23
|
-
for (const candidate of candidates) {
|
|
24
|
-
const first = await runLadder({
|
|
25
|
-
level: 'L0', candidate, facts: options.facts, model: options.model,
|
|
26
|
-
modelName: options.modelName, signal: options.signal,
|
|
27
|
-
...(options.cache === undefined ? {} : { cache: options.cache }),
|
|
28
|
-
});
|
|
29
|
-
calls += first.calls;
|
|
30
|
-
if (first.route === 'error') {
|
|
31
|
-
errors.push({ candidate, message: first.error ?? 'gate error' });
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
if (first.route !== 'keep' || first.droppedAt !== 'L0') {
|
|
35
|
-
if (first.route === 'merge' || first.route === 'undecided')
|
|
36
|
-
kept.push({ candidate: withGate(candidate, first.verdicts, false), verdicts: first.verdicts, auditOverride: false });
|
|
37
|
-
else
|
|
38
|
-
dropped.push({ candidate, verdicts: first.verdicts, auditOverride: false });
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (!selectedForAudit(pairKey(candidate.a, candidate.b), options.runSeed, options.auditRate ?? 0.1)) {
|
|
42
|
-
dropped.push({ candidate, verdicts: first.verdicts, auditOverride: false });
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
const audit = await runLadder({
|
|
46
|
-
level: 'L0', startLevel: 'L2', candidate, facts: options.facts, model: options.model,
|
|
47
|
-
modelName: options.modelName, signal: options.signal,
|
|
48
|
-
...(options.cache === undefined ? {} : { cache: options.cache }),
|
|
49
|
-
});
|
|
50
|
-
calls += audit.calls;
|
|
51
|
-
if (audit.route === 'merge' || audit.route === 'undecided') {
|
|
52
|
-
const all = [...first.verdicts, ...audit.verdicts];
|
|
53
|
-
kept.push({ candidate: withGate(candidate, all, true), verdicts: all, auditOverride: true });
|
|
54
|
-
}
|
|
55
|
-
else if (audit.route === 'error') {
|
|
56
|
-
errors.push({ candidate, message: audit.error ?? 'audit gate error' });
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
dropped.push({ candidate, verdicts: [...first.verdicts, ...audit.verdicts], auditOverride: true });
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return { kept, dropped, errors, calls };
|
|
63
|
-
}
|
|
64
|
-
//# sourceMappingURL=gate-filter.js.map
|
package/lib/types/ladder.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export type GateLevel = 'L0' | 'L1' | 'L2';
|
|
2
|
-
export type GateVerdictValue = 1 | 0 | -1;
|
|
3
|
-
export interface GateFacts {
|
|
4
|
-
readonly name: string;
|
|
5
|
-
readonly description: string;
|
|
6
|
-
readonly body: string;
|
|
7
|
-
}
|
|
8
|
-
export interface GateCandidate {
|
|
9
|
-
readonly a: string;
|
|
10
|
-
readonly b: string;
|
|
11
|
-
readonly score?: number;
|
|
12
|
-
}
|
|
13
|
-
export interface GateEvidence {
|
|
14
|
-
readonly path: string;
|
|
15
|
-
readonly line: number;
|
|
16
|
-
readonly quote: string;
|
|
17
|
-
}
|
|
18
|
-
export interface GateVerdict {
|
|
19
|
-
readonly level: GateLevel;
|
|
20
|
-
readonly verdict: GateVerdictValue;
|
|
21
|
-
readonly reason: string;
|
|
22
|
-
readonly evidence: readonly GateEvidence[];
|
|
23
|
-
readonly cached: boolean;
|
|
24
|
-
}
|
|
25
|
-
export interface GateModelPort {
|
|
26
|
-
callJson(request: {
|
|
27
|
-
readonly system: string;
|
|
28
|
-
readonly user: string;
|
|
29
|
-
readonly maxTokens: number;
|
|
30
|
-
readonly signal: AbortSignal;
|
|
31
|
-
}): Promise<unknown>;
|
|
32
|
-
}
|
|
33
|
-
export type LadderRoute = 'merge' | 'undecided' | 'keep' | 'error';
|
|
34
|
-
export interface LadderResult {
|
|
35
|
-
readonly route: LadderRoute;
|
|
36
|
-
readonly verdicts: readonly GateVerdict[];
|
|
37
|
-
readonly calls: number;
|
|
38
|
-
readonly droppedAt?: GateLevel;
|
|
39
|
-
readonly error?: string;
|
|
40
|
-
}
|
|
41
|
-
export declare class GateCache {
|
|
42
|
-
private readonly entries;
|
|
43
|
-
get(key: string): GateVerdict | undefined;
|
|
44
|
-
put(key: string, value: GateVerdict): void;
|
|
45
|
-
get size(): number;
|
|
46
|
-
toObject(): Record<string, GateVerdict>;
|
|
47
|
-
static fromObject(value: unknown): GateCache;
|
|
48
|
-
}
|
|
49
|
-
export declare function gateCacheKey(level: GateLevel, payload: string, model: string): string;
|
|
50
|
-
export declare function parseVerdict(level: GateLevel, raw: unknown, cached?: boolean): GateVerdict | undefined;
|
|
51
|
-
export interface RunLadderOptions {
|
|
52
|
-
readonly level: GateLevel;
|
|
53
|
-
readonly candidate: GateCandidate;
|
|
54
|
-
readonly facts: readonly GateFacts[];
|
|
55
|
-
readonly model: GateModelPort;
|
|
56
|
-
readonly modelName: string;
|
|
57
|
-
readonly signal: AbortSignal;
|
|
58
|
-
readonly cache?: GateCache;
|
|
59
|
-
/** Audit path starts at L2 after an L0 keep. */
|
|
60
|
-
readonly startLevel?: GateLevel;
|
|
61
|
-
}
|
|
62
|
-
/** Deterministic 10% L0 audit selection from the run seed + pair key. */
|
|
63
|
-
export declare function selectedForAudit(pairKey: string, seed: string, rate?: number): boolean;
|
|
64
|
-
export declare function runLadder(options: RunLadderOptions): Promise<LadderResult>;
|
|
65
|
-
//# sourceMappingURL=ladder.d.ts.map
|
package/lib/types/ladder.js
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* L0–L2 candidate gate. This is a CANDIDATE classifier, not a content quality judge.
|
|
3
|
-
* Routing follows the frozen table: L0 -1 stops; L0 1/0 → L1; L1 -1 stops; L1 0/1 → L2;
|
|
4
|
-
* L2 1 → agent1; L2 0 → agent1 (undecided); L2 -1 → skipped.
|
|
5
|
-
*/
|
|
6
|
-
import { createHash } from 'node:crypto';
|
|
7
|
-
export class GateCache {
|
|
8
|
-
entries = new Map();
|
|
9
|
-
get(key) { return this.entries.get(key); }
|
|
10
|
-
put(key, value) { this.entries.set(key, value); }
|
|
11
|
-
get size() { return this.entries.size; }
|
|
12
|
-
toObject() { return Object.fromEntries(this.entries); }
|
|
13
|
-
static fromObject(value) {
|
|
14
|
-
const cache = new GateCache();
|
|
15
|
-
if (typeof value !== 'object' || value === null)
|
|
16
|
-
return cache;
|
|
17
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
18
|
-
if (typeof entry !== 'object' || entry === null)
|
|
19
|
-
continue;
|
|
20
|
-
const verdict = entry;
|
|
21
|
-
if (verdict.verdict === 1 || verdict.verdict === 0 || verdict.verdict === -1) {
|
|
22
|
-
cache.put(key, { level: verdict.level === 'L0' || verdict.level === 'L1' || verdict.level === 'L2' ? verdict.level : 'L2', verdict: verdict.verdict, reason: typeof verdict.reason === 'string' ? verdict.reason : '', evidence: Array.isArray(verdict.evidence) ? verdict.evidence : [], cached: true });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return cache;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
export function gateCacheKey(level, payload, model) {
|
|
29
|
-
return createHash('sha256').update(JSON.stringify({ level, payload, model })).digest('hex');
|
|
30
|
-
}
|
|
31
|
-
function payloadFor(level, candidate, facts) {
|
|
32
|
-
const a = facts.get(candidate.a);
|
|
33
|
-
const b = facts.get(candidate.b);
|
|
34
|
-
if (a === undefined || b === undefined)
|
|
35
|
-
throw new Error('candidate names are not in the fact set');
|
|
36
|
-
const base = {
|
|
37
|
-
level,
|
|
38
|
-
skill_a: { name: a.name, description: a.description },
|
|
39
|
-
skill_b: { name: b.name, description: b.description },
|
|
40
|
-
scores: { jaccard_summary: candidate.score ?? null },
|
|
41
|
-
schema: { verdict: '1 | 0 | -1', reason: '<=80 chars' },
|
|
42
|
-
};
|
|
43
|
-
if (level === 'L0')
|
|
44
|
-
return JSON.stringify(base);
|
|
45
|
-
const withHead = {
|
|
46
|
-
...base,
|
|
47
|
-
skill_a: { ...base.skill_a, head: a.body.slice(0, 2400) },
|
|
48
|
-
skill_b: { ...base.skill_b, head: b.body.slice(0, 2400) },
|
|
49
|
-
};
|
|
50
|
-
if (level === 'L1')
|
|
51
|
-
return JSON.stringify(withHead);
|
|
52
|
-
return JSON.stringify({
|
|
53
|
-
...withHead,
|
|
54
|
-
skill_a: { ...withHead.skill_a, body_excerpt: a.body.slice(0, 4000) },
|
|
55
|
-
skill_b: { ...withHead.skill_b, body_excerpt: b.body.slice(0, 4000) },
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
const SYSTEM = 'Judge whether two skill documents describe the same reusable capability. Shared vocabulary or a shared preamble is not duplication. Return only strict JSON: {"verdict":1|0|-1,"reason":"..."}';
|
|
59
|
-
function extractJson(text) {
|
|
60
|
-
const fenced = /`\``(?:json)?\s*([\s\S]*?)`\``/i.exec(text);
|
|
61
|
-
const body = fenced?.[1] ?? text;
|
|
62
|
-
const start = body.indexOf('{');
|
|
63
|
-
if (start < 0)
|
|
64
|
-
return undefined;
|
|
65
|
-
let depth = 0;
|
|
66
|
-
for (let index = start; index < body.length; index += 1) {
|
|
67
|
-
if (body[index] === '{')
|
|
68
|
-
depth += 1;
|
|
69
|
-
else if (body[index] === '}') {
|
|
70
|
-
depth -= 1;
|
|
71
|
-
if (depth === 0) {
|
|
72
|
-
try {
|
|
73
|
-
return JSON.parse(body.slice(start, index + 1));
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return undefined;
|
|
82
|
-
}
|
|
83
|
-
export function parseVerdict(level, raw, cached = false) {
|
|
84
|
-
const value = typeof raw === 'string' ? extractJson(raw) : raw;
|
|
85
|
-
if (typeof value !== 'object' || value === null)
|
|
86
|
-
return undefined;
|
|
87
|
-
const record = value;
|
|
88
|
-
const verdict = record.verdict === 1 || record.verdict === '1' ? 1
|
|
89
|
-
: record.verdict === 0 || record.verdict === '0' ? 0
|
|
90
|
-
: record.verdict === -1 || record.verdict === '-1' ? -1
|
|
91
|
-
: undefined;
|
|
92
|
-
if (verdict === undefined)
|
|
93
|
-
return undefined;
|
|
94
|
-
const reason = typeof record.reason === 'string' ? record.reason.slice(0, 80) : '';
|
|
95
|
-
const evidence = Array.isArray(record.evidence)
|
|
96
|
-
? record.evidence
|
|
97
|
-
.filter(item => typeof item === 'object' && item !== null)
|
|
98
|
-
.map((item) => {
|
|
99
|
-
const entry = item;
|
|
100
|
-
return { path: typeof entry.path === 'string' ? entry.path : '', line: typeof entry.line === 'number' ? entry.line : -1, quote: typeof entry.quote === 'string' ? entry.quote.slice(0, 120) : '' };
|
|
101
|
-
})
|
|
102
|
-
: [];
|
|
103
|
-
return { level, verdict, reason, evidence, cached };
|
|
104
|
-
}
|
|
105
|
-
/** Deterministic 10% L0 audit selection from the run seed + pair key. */
|
|
106
|
-
export function selectedForAudit(pairKey, seed, rate = 0.1) {
|
|
107
|
-
const digest = createHash('sha256').update(seed + '\u0000' + pairKey).digest();
|
|
108
|
-
return digest[0] !== undefined && digest[0] < Math.round(Math.max(0, Math.min(1, rate)) * 256);
|
|
109
|
-
}
|
|
110
|
-
export async function runLadder(options) {
|
|
111
|
-
const facts = new Map(options.facts.map(fact => [fact.name, fact]));
|
|
112
|
-
const verdicts = [];
|
|
113
|
-
let calls = 0;
|
|
114
|
-
let level = options.startLevel ?? 'L0';
|
|
115
|
-
for (;;) {
|
|
116
|
-
let payload;
|
|
117
|
-
try {
|
|
118
|
-
payload = payloadFor(level, options.candidate, facts);
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
return { route: 'error', verdicts, calls, error: error instanceof Error ? error.message : String(error) };
|
|
122
|
-
}
|
|
123
|
-
const key = gateCacheKey(level, payload, options.modelName);
|
|
124
|
-
const cached = options.cache?.get(key);
|
|
125
|
-
let verdict;
|
|
126
|
-
if (cached !== undefined)
|
|
127
|
-
verdict = { ...cached, level, cached: true };
|
|
128
|
-
else {
|
|
129
|
-
try {
|
|
130
|
-
calls += 1;
|
|
131
|
-
const raw = await options.model.callJson({ system: SYSTEM, user: payload, maxTokens: 500, signal: options.signal });
|
|
132
|
-
verdict = parseVerdict(level, raw);
|
|
133
|
-
if (verdict !== undefined)
|
|
134
|
-
options.cache?.put(key, verdict);
|
|
135
|
-
}
|
|
136
|
-
catch (error) {
|
|
137
|
-
return { route: 'error', verdicts, calls, error: error instanceof Error ? error.message : String(error) };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (verdict === undefined)
|
|
141
|
-
return { route: 'error', verdicts, calls, error: 'invalid gate JSON' };
|
|
142
|
-
verdicts.push(verdict);
|
|
143
|
-
if (level === 'L0') {
|
|
144
|
-
if (verdict.verdict === -1)
|
|
145
|
-
return { route: 'keep', verdicts, calls, droppedAt: 'L0' };
|
|
146
|
-
level = 'L1';
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
if (level === 'L1') {
|
|
150
|
-
if (verdict.verdict === -1)
|
|
151
|
-
return { route: 'keep', verdicts, calls, droppedAt: 'L1' };
|
|
152
|
-
level = 'L2';
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (verdict.verdict === 1)
|
|
156
|
-
return { route: 'merge', verdicts, calls };
|
|
157
|
-
if (verdict.verdict === 0)
|
|
158
|
-
return { route: 'undecided', verdicts, calls };
|
|
159
|
-
return { route: 'keep', verdicts, calls, droppedAt: 'L2' };
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
//# sourceMappingURL=ladder.js.map
|
package/lib/types/propose.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* C3 semantic proposer: one library-level model call (sharded when the library is large)
|
|
3
|
-
* that proposes candidate pairs. It never judges merge quality; L0–L2 does that.
|
|
4
|
-
*/
|
|
5
|
-
import type { MergeCandidate } from './contracts.ts';
|
|
6
|
-
import type { SkillCard } from './screening.ts';
|
|
7
|
-
export interface ModelPort {
|
|
8
|
-
callJson(request: {
|
|
9
|
-
readonly system: string;
|
|
10
|
-
readonly user: string;
|
|
11
|
-
readonly maxTokens: number;
|
|
12
|
-
readonly signal: AbortSignal;
|
|
13
|
-
}): Promise<unknown>;
|
|
14
|
-
}
|
|
15
|
-
export interface ProposeOptions {
|
|
16
|
-
readonly shardSize?: number;
|
|
17
|
-
readonly maxPairs?: number;
|
|
18
|
-
readonly signal: AbortSignal;
|
|
19
|
-
}
|
|
20
|
-
export interface ProposeResult {
|
|
21
|
-
readonly candidates: readonly MergeCandidate[];
|
|
22
|
-
readonly shards: number;
|
|
23
|
-
readonly degraded: readonly string[];
|
|
24
|
-
}
|
|
25
|
-
export declare function proposePairs(cards: readonly SkillCard[], port: ModelPort, options: ProposeOptions): Promise<ProposeResult>;
|
|
26
|
-
//# sourceMappingURL=propose.d.ts.map
|
package/lib/types/propose.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
const SYSTEM = 'You propose skill pairs that may be merged. Return only a JSON object: {"pairs":[{"a":"skill-a","b":"skill-b","reason":"one sentence"}]}. Use only supplied skill names. Do not decide whether a merge is good.';
|
|
2
|
-
function parsePairs(raw, known, cap) {
|
|
3
|
-
let value = raw;
|
|
4
|
-
if (typeof raw === 'string') {
|
|
5
|
-
try {
|
|
6
|
-
value = JSON.parse(raw);
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
if (typeof value !== 'object' || value === null)
|
|
13
|
-
return [];
|
|
14
|
-
const pairs = value.pairs;
|
|
15
|
-
if (!Array.isArray(pairs))
|
|
16
|
-
return [];
|
|
17
|
-
const out = [];
|
|
18
|
-
const seen = new Set();
|
|
19
|
-
for (const item of pairs) {
|
|
20
|
-
if (out.length >= cap)
|
|
21
|
-
break;
|
|
22
|
-
const pair = item;
|
|
23
|
-
if (typeof pair.a !== 'string' || typeof pair.b !== 'string')
|
|
24
|
-
continue;
|
|
25
|
-
const a = pair.a.trim();
|
|
26
|
-
const b = pair.b.trim();
|
|
27
|
-
if (a === b || !known.has(a) || !known.has(b))
|
|
28
|
-
continue;
|
|
29
|
-
const [first, second] = a < b ? [a, b] : [b, a];
|
|
30
|
-
const key = first + '\u0000' + second;
|
|
31
|
-
if (seen.has(key))
|
|
32
|
-
continue;
|
|
33
|
-
seen.add(key);
|
|
34
|
-
out.push({ a: first, b: second, source: 'proposed', ...(typeof pair.reason === 'string' && pair.reason.trim() !== '' ? { reason: pair.reason.trim() } : {}) });
|
|
35
|
-
}
|
|
36
|
-
return out;
|
|
37
|
-
}
|
|
38
|
-
export async function proposePairs(cards, port, options) {
|
|
39
|
-
const shardSize = Math.max(2, options.shardSize ?? 60);
|
|
40
|
-
const cap = Math.max(0, options.maxPairs ?? 200);
|
|
41
|
-
const known = new Set(cards.map(card => card.name));
|
|
42
|
-
const candidates = [];
|
|
43
|
-
const degraded = [];
|
|
44
|
-
let shards = 0;
|
|
45
|
-
for (let start = 0; start < cards.length && candidates.length < cap; start += shardSize) {
|
|
46
|
-
const shard = cards.slice(start, start + shardSize);
|
|
47
|
-
if (shard.length < 2)
|
|
48
|
-
continue;
|
|
49
|
-
shards += 1;
|
|
50
|
-
try {
|
|
51
|
-
const raw = await port.callJson({
|
|
52
|
-
system: SYSTEM,
|
|
53
|
-
user: JSON.stringify(shard.map(card => ({
|
|
54
|
-
name: card.name,
|
|
55
|
-
description: card.description.slice(0, 400),
|
|
56
|
-
body: card.body.slice(0, 600),
|
|
57
|
-
}))),
|
|
58
|
-
maxTokens: 1200,
|
|
59
|
-
signal: options.signal,
|
|
60
|
-
});
|
|
61
|
-
candidates.push(...parsePairs(raw, known, cap - candidates.length));
|
|
62
|
-
}
|
|
63
|
-
catch (error) {
|
|
64
|
-
degraded.push('proposal shard failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return { candidates, shards, degraded };
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=propose.js.map
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|