@lmzhen/dsh-evolution-merge 0.28.0 → 0.30.0
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 +21 -18
- package/lib/index.js +1583 -1209
- package/lib/types/adapters/channel.js +29 -0
- package/lib/types/adapters/engine-screen.d.ts +55 -0
- package/lib/types/adapters/engine-screen.js +45 -0
- package/lib/types/adapters/library.js +14 -0
- package/lib/types/adapters/model-runner.d.ts +28 -0
- package/lib/types/adapters/model-runner.js +34 -0
- package/lib/types/adapters/select.d.ts +3 -0
- package/lib/types/adapters/select.js +35 -0
- package/lib/types/adapters/undo.js +35 -0
- package/lib/types/approve-command.js +42 -0
- package/lib/types/authoring-flow.d.ts +7 -2
- package/lib/types/authoring-flow.js +48 -0
- package/lib/types/channel-port.d.ts +2 -1
- package/lib/types/channel-port.js +74 -0
- package/lib/types/commands.d.ts +9 -0
- package/lib/types/commands.js +79 -0
- package/lib/types/commit-assembly.d.ts +2 -0
- package/lib/types/commit-assembly.js +48 -0
- package/lib/types/commit-execute.js +52 -0
- package/lib/types/commit-plan.js +56 -0
- package/lib/types/commit-receipt.d.ts +6 -1
- package/lib/types/commit-receipt.js +85 -0
- package/lib/types/contracts.d.ts +21 -7
- package/lib/types/contracts.js +57 -0
- package/lib/types/coordinator.d.ts +11 -0
- package/lib/types/coordinator.js +131 -0
- package/lib/types/decision.js +61 -0
- package/lib/types/doctor.d.ts +1 -1
- package/lib/types/doctor.js +50 -0
- package/lib/types/domain/claim.d.ts +10 -0
- package/lib/types/domain/claim.js +12 -0
- package/lib/types/domain/commit-journal-store.d.ts +6 -0
- package/lib/types/domain/commit-journal-store.js +22 -0
- package/lib/types/domain/commit-journal.d.ts +16 -0
- package/lib/types/domain/commit-journal.js +25 -0
- package/lib/types/domain/errors.d.ts +10 -0
- package/lib/types/domain/errors.js +7 -0
- package/lib/types/domain/failure-route.d.ts +25 -0
- package/lib/types/domain/failure-route.js +22 -0
- package/lib/types/domain/indexes.d.ts +21 -0
- package/lib/types/domain/indexes.js +15 -0
- package/lib/types/domain/ledger.d.ts +17 -0
- package/lib/types/domain/ledger.js +65 -0
- package/lib/types/domain/markdown.d.ts +2 -0
- package/lib/types/domain/markdown.js +57 -0
- package/lib/types/domain/migrate-v1.d.ts +41 -0
- package/lib/types/domain/migrate-v1.js +23 -0
- package/lib/types/domain/pair-index-store.d.ts +6 -0
- package/lib/types/domain/pair-index-store.js +25 -0
- package/lib/types/domain/pair-index.d.ts +9 -0
- package/lib/types/domain/pair-index.js +28 -0
- package/lib/types/domain/resolve-pair.d.ts +11 -0
- package/lib/types/domain/resolve-pair.js +17 -0
- package/lib/types/domain/row.d.ts +113 -0
- package/lib/types/domain/row.js +111 -0
- package/lib/types/domain/run-record.d.ts +45 -0
- package/lib/types/domain/run-record.js +24 -0
- package/lib/types/domain/state.d.ts +57 -0
- package/lib/types/domain/state.js +65 -0
- package/lib/types/gate-cache.js +36 -0
- package/lib/types/index.d.ts +6 -7
- package/lib/types/index.js +241 -0
- package/lib/types/ladder.js +162 -0
- package/lib/types/merge-command.d.ts +3 -0
- package/lib/types/merge-command.js +43 -0
- package/lib/types/merge-routes.d.ts +15 -4
- package/lib/types/merge-routes.js +99 -0
- package/lib/types/merge-tool.js +69 -0
- package/lib/types/new-child-authoring.d.ts +1 -1
- package/lib/types/new-child-authoring.js +83 -0
- package/lib/types/pair-command.js +18 -0
- package/lib/types/params.d.ts +1 -0
- package/lib/types/params.js +49 -0
- package/lib/types/pending-actions.js +28 -0
- package/lib/types/prompts.d.ts +7 -5
- package/lib/types/prompts.js +60 -0
- package/lib/types/read-commands.js +32 -0
- package/lib/types/report.js +70 -0
- package/lib/types/resume-command.js +57 -0
- package/lib/types/round2-outcome.js +50 -0
- package/lib/types/routes.d.ts +20 -2
- package/lib/types/routes.js +75 -0
- package/lib/types/rules.js +55 -0
- package/lib/types/run-shell.d.ts +7 -0
- package/lib/types/run-shell.js +69 -0
- package/lib/types/runtime.d.ts +5 -4
- package/lib/types/runtime.js +269 -0
- package/lib/types/scan-command.d.ts +1 -0
- package/lib/types/scan-command.js +30 -0
- package/lib/types/scan-ports.d.ts +1 -1
- package/lib/types/scan-ports.js +93 -0
- package/lib/types/scan-run.d.ts +4 -0
- package/lib/types/scan-run.js +186 -0
- package/lib/types/skill-card.d.ts +12 -0
- package/lib/types/skill-card.js +2 -0
- package/lib/types/skipped.js +24 -0
- package/lib/types/stage-tool-registry.js +32 -0
- package/lib/types/stage-tool.js +31 -0
- package/lib/types/staged-tree.js +75 -0
- package/lib/types/staging-files.js +86 -0
- package/lib/types/staging.d.ts +13 -0
- package/lib/types/staging.js +149 -0
- package/lib/types/state.js +103 -0
- package/lib/types/status.d.ts +2 -1
- package/lib/types/status.js +28 -0
- package/lib/types/supersede.js +14 -0
- package/lib/types/undo-merge.js +23 -0
- package/package.json +4 -7
- package/lib/types/adapters/model.d.ts +0 -31
- package/lib/types/gate-filter.d.ts +0 -31
- package/lib/types/propose.d.ts +0 -26
- package/lib/types/screening-port.d.ts +0 -47
- package/lib/types/screening.d.ts +0 -30
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function convert(outcome) {
|
|
2
|
+
if (outcome.kind !== 'ok')
|
|
3
|
+
return outcome;
|
|
4
|
+
return {
|
|
5
|
+
kind: 'ok',
|
|
6
|
+
childId: String(outcome.childId),
|
|
7
|
+
turn: { text: outcome.turn.text, stopReason: outcome.turn.stopReason },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function subagentChannelAdapter(channel) {
|
|
11
|
+
return {
|
|
12
|
+
async open(spec) {
|
|
13
|
+
if (channel.open === undefined)
|
|
14
|
+
return { kind: 'unavailable', code: 'E-323', reason: 'subagent channel has no open()' };
|
|
15
|
+
return convert(await channel.open(spec));
|
|
16
|
+
},
|
|
17
|
+
async continueTurn(childId, promptText, options) {
|
|
18
|
+
if (channel.continueTurn === undefined)
|
|
19
|
+
return { kind: 'unavailable', code: 'E-323', reason: 'subagent channel has no continueTurn()' };
|
|
20
|
+
return convert(await channel.continueTurn(childId, promptText, options));
|
|
21
|
+
},
|
|
22
|
+
async dispose(childId) {
|
|
23
|
+
if (channel.dispose === undefined)
|
|
24
|
+
return;
|
|
25
|
+
await channel.dispose(childId);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=channel.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import type { MergeCandidate } from '../contracts.ts';
|
|
3
|
+
import type { SkillCard } from '../skill-card.ts';
|
|
4
|
+
export type EngineScreenResult = {
|
|
5
|
+
readonly ok: true;
|
|
6
|
+
readonly report: Record<string, unknown>;
|
|
7
|
+
} | {
|
|
8
|
+
readonly ok: false;
|
|
9
|
+
readonly error: {
|
|
10
|
+
readonly code: string;
|
|
11
|
+
readonly stage?: string;
|
|
12
|
+
readonly detail?: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export interface EngineScreenPort {
|
|
16
|
+
screen(input: {
|
|
17
|
+
readonly corpusDir: string;
|
|
18
|
+
readonly outDir: string;
|
|
19
|
+
readonly cfg?: Record<string, unknown>;
|
|
20
|
+
readonly signal?: AbortSignal;
|
|
21
|
+
readonly onProgress?: (p: {
|
|
22
|
+
stage: string;
|
|
23
|
+
done?: number;
|
|
24
|
+
total?: number;
|
|
25
|
+
}) => void;
|
|
26
|
+
readonly runner?: (prompt: string, cfg?: Record<string, unknown>) => Promise<string>;
|
|
27
|
+
}): Promise<EngineScreenResult>;
|
|
28
|
+
}
|
|
29
|
+
export interface CandidateSelection {
|
|
30
|
+
readonly candidates: readonly MergeCandidate[];
|
|
31
|
+
readonly skipped: readonly {
|
|
32
|
+
readonly pairKey: string;
|
|
33
|
+
readonly a: string;
|
|
34
|
+
readonly b: string;
|
|
35
|
+
readonly reason?: string;
|
|
36
|
+
}[];
|
|
37
|
+
readonly reportOnly: readonly unknown[];
|
|
38
|
+
readonly candidatePairs?: number;
|
|
39
|
+
}
|
|
40
|
+
export type ScreeningCandidates = readonly MergeCandidate[] & {
|
|
41
|
+
readonly candidatePairs?: number;
|
|
42
|
+
};
|
|
43
|
+
export interface EngineScreeningDeps {
|
|
44
|
+
readonly io: EvolutionIoLike;
|
|
45
|
+
readonly home: string;
|
|
46
|
+
readonly engine: EngineScreenPort;
|
|
47
|
+
readonly select: (report: Record<string, unknown>) => CandidateSelection;
|
|
48
|
+
readonly cfg?: Record<string, unknown>;
|
|
49
|
+
readonly runner?: (prompt: string, cfg?: Record<string, unknown>) => Promise<string>;
|
|
50
|
+
readonly onSelection?: (selection: CandidateSelection, runId: string) => Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
/** Write the exact card bodies as a support-free corpus snapshot and return its root. */
|
|
53
|
+
export declare function snapshotCards(io: EvolutionIoLike, home: string, runId: string, cards: readonly SkillCard[]): Promise<string>;
|
|
54
|
+
export declare function createEngineScreening(deps: EngineScreeningDeps): (cards: readonly SkillCard[], runId: string, signal: AbortSignal) => Promise<readonly MergeCandidate[]>;
|
|
55
|
+
//# sourceMappingURL=engine-screen.d.ts.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G3 screen adapter: cards -> corpus snapshot -> EnginePort.screen.
|
|
3
|
+
*
|
|
4
|
+
* The vendor engine reads a corpus from disk; host cards are content. This module
|
|
5
|
+
* writes only SKILL.md support-free snapshots under the run directory. It does not
|
|
6
|
+
* decide candidates: the Q02 selector is injected so engine/report-map stays the
|
|
7
|
+
* single source of that policy.
|
|
8
|
+
*/
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
function safeName(name, index) {
|
|
11
|
+
const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
12
|
+
return (cleaned === '' ? 'skill-' + String(index) : cleaned) + '__' + String(index);
|
|
13
|
+
}
|
|
14
|
+
/** Write the exact card bodies as a support-free corpus snapshot and return its root. */
|
|
15
|
+
export async function snapshotCards(io, home, runId, cards) {
|
|
16
|
+
const root = join(home, 'merge', 'runs', runId, 'corpus');
|
|
17
|
+
for (let index = 0; index < cards.length; index += 1) {
|
|
18
|
+
const card = cards[index];
|
|
19
|
+
if (card === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
await io.writeText(join(root, safeName(card.name, index), 'SKILL.md'), card.body);
|
|
22
|
+
}
|
|
23
|
+
return root;
|
|
24
|
+
}
|
|
25
|
+
export function createEngineScreening(deps) {
|
|
26
|
+
return async (cards, runId, signal) => {
|
|
27
|
+
const corpusDir = await snapshotCards(deps.io, deps.home, runId, cards);
|
|
28
|
+
const outDir = join(deps.home, 'merge', 'runs', runId, 'engine');
|
|
29
|
+
const result = await deps.engine.screen({
|
|
30
|
+
corpusDir,
|
|
31
|
+
outDir,
|
|
32
|
+
...(deps.cfg === undefined ? {} : { cfg: deps.cfg }),
|
|
33
|
+
...(deps.runner === undefined ? {} : { runner: deps.runner }),
|
|
34
|
+
signal,
|
|
35
|
+
});
|
|
36
|
+
if (!result.ok) {
|
|
37
|
+
throw new Error('engine screen failed: ' + result.error.code + (result.error.stage === undefined ? '' : ' @' + result.error.stage) + (result.error.detail === undefined ? '' : ': ' + result.error.detail));
|
|
38
|
+
}
|
|
39
|
+
const selection = deps.select(result.report);
|
|
40
|
+
if (deps.onSelection !== undefined)
|
|
41
|
+
await deps.onSelection(selection, runId);
|
|
42
|
+
return Object.assign(selection.candidates.slice(), selection.candidatePairs === undefined ? {} : { candidatePairs: selection.candidatePairs });
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=engine-screen.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function skillLibraryPort(skills) {
|
|
2
|
+
const archivedNames = skills.archivedNames;
|
|
3
|
+
return {
|
|
4
|
+
readSkill: name => skills.read(name),
|
|
5
|
+
isArchived: async (name) => {
|
|
6
|
+
if (typeof archivedNames !== 'function')
|
|
7
|
+
return false;
|
|
8
|
+
const names = await archivedNames.call(skills);
|
|
9
|
+
return names === null ? true : names.includes(name);
|
|
10
|
+
},
|
|
11
|
+
commitMerge: plan => skills.commitMerge(plan),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=library.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeepSeek judge runner over the platform llm service. The engine calls
|
|
3
|
+
* runner(prompt, cfg) and expects raw model text; the platform owns provider
|
|
4
|
+
* selection, usage accounting and transport.
|
|
5
|
+
*/
|
|
6
|
+
export interface ModelRoute {
|
|
7
|
+
readonly provider: string;
|
|
8
|
+
readonly model: string;
|
|
9
|
+
}
|
|
10
|
+
export interface LlmChunkLike {
|
|
11
|
+
readonly type: string;
|
|
12
|
+
readonly text?: string;
|
|
13
|
+
readonly reason?: {
|
|
14
|
+
readonly kind?: string;
|
|
15
|
+
readonly failure?: {
|
|
16
|
+
readonly message?: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface LlmServiceLike {
|
|
21
|
+
stream?(options: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
|
|
22
|
+
prepareCall?(config: Record<string, unknown>, signal?: AbortSignal): Promise<{
|
|
23
|
+
readonly config: Record<string, unknown>;
|
|
24
|
+
stream(input: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createEngineRunner(llm: LlmServiceLike, route: () => ModelRoute): (prompt: string) => Promise<string>;
|
|
28
|
+
//# sourceMappingURL=model-runner.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function append(chunk, text) {
|
|
2
|
+
if (chunk.type === 'text-delta' || chunk.type === 'text')
|
|
3
|
+
return text + (chunk.text ?? '');
|
|
4
|
+
if (chunk.type === 'finish' && chunk.reason?.kind === 'error') {
|
|
5
|
+
throw new Error('model call failed: ' + (chunk.reason.failure?.message ?? 'unknown'));
|
|
6
|
+
}
|
|
7
|
+
return text;
|
|
8
|
+
}
|
|
9
|
+
export function createEngineRunner(llm, route) {
|
|
10
|
+
return async (prompt) => {
|
|
11
|
+
const selection = route();
|
|
12
|
+
let text = '';
|
|
13
|
+
if (llm.stream !== undefined) {
|
|
14
|
+
for await (const chunk of llm.stream({
|
|
15
|
+
provider: selection.provider,
|
|
16
|
+
model: selection.model,
|
|
17
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
|
|
18
|
+
temperature: 0,
|
|
19
|
+
}))
|
|
20
|
+
text = append(chunk, text);
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
if (llm.prepareCall === undefined)
|
|
24
|
+
throw new Error('llm service has neither stream nor prepareCall');
|
|
25
|
+
const call = await llm.prepareCall({ provider: selection.provider, model: selection.model, temperature: 0 });
|
|
26
|
+
for await (const chunk of call.stream({
|
|
27
|
+
...call.config,
|
|
28
|
+
messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
|
|
29
|
+
}))
|
|
30
|
+
text = append(chunk, text);
|
|
31
|
+
return text;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=model-runner.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Host selector correction: engine selector does not route judge errors to candidates. */
|
|
2
|
+
import { pairKeyOf, selectCandidates, toEngineCandidate } from '@lmzhen/dsh-evolution-merge-engine';
|
|
3
|
+
export function selectWithErrors(report) {
|
|
4
|
+
const base = selectCandidates(report);
|
|
5
|
+
const doc = (report.reportDoc ?? report);
|
|
6
|
+
const rows = Array.isArray(doc.pairs_final) ? doc.pairs_final : [];
|
|
7
|
+
const errors = new Set();
|
|
8
|
+
for (const row of rows) {
|
|
9
|
+
if (row.error !== true)
|
|
10
|
+
continue;
|
|
11
|
+
const a = typeof row.a === 'string' ? row.a : '';
|
|
12
|
+
const b = typeof row.b === 'string' ? row.b : '';
|
|
13
|
+
if (a !== '' && b !== '')
|
|
14
|
+
errors.add(pairKeyOf(a, b));
|
|
15
|
+
}
|
|
16
|
+
const candidates = base.candidates.filter(candidate => !errors.has(pairKeyOf(candidate.a, candidate.b)));
|
|
17
|
+
const skipped = base.skipped.filter(item => !errors.has(item.pairKey));
|
|
18
|
+
const reportOnly = base.reportOnly.filter((row) => {
|
|
19
|
+
const value = row;
|
|
20
|
+
if (typeof value.a !== 'string' || typeof value.b !== 'string')
|
|
21
|
+
return true;
|
|
22
|
+
return !errors.has(pairKeyOf(value.a, value.b));
|
|
23
|
+
});
|
|
24
|
+
for (const row of rows) {
|
|
25
|
+
if (row.error !== true)
|
|
26
|
+
continue;
|
|
27
|
+
const a = typeof row.a === 'string' ? row.a : '';
|
|
28
|
+
const b = typeof row.b === 'string' ? row.b : '';
|
|
29
|
+
if (a === '' || b === '')
|
|
30
|
+
continue;
|
|
31
|
+
candidates.push(toEngineCandidate(row, typeof row.reason === 'string' ? row.reason : 'judge-error'));
|
|
32
|
+
}
|
|
33
|
+
return { ...base, candidates, skipped, reportOnly };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillLibrary-backed undo sink. Blobs are content-addressed but the library exposes them by
|
|
3
|
+
* version, so the first readBlob call indexes all version bodies once; an undo is rare enough
|
|
4
|
+
* that this is preferable to adding a second hash index.
|
|
5
|
+
*/
|
|
6
|
+
import { contentHash } from '@lmzhen/dsh-evolution-core';
|
|
7
|
+
export function skillLibraryUndoPort(skills) {
|
|
8
|
+
let blobs;
|
|
9
|
+
const readBlob = async (hash) => {
|
|
10
|
+
if (blobs === undefined) {
|
|
11
|
+
blobs = new Map();
|
|
12
|
+
for (const summary of await skills.list()) {
|
|
13
|
+
for (const version of await skills.listVersions(summary.name)) {
|
|
14
|
+
const body = await skills.readVersion(summary.name, version.v);
|
|
15
|
+
if (body !== null)
|
|
16
|
+
blobs.set(contentHash(body), body);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return blobs.get(hash) ?? null;
|
|
21
|
+
};
|
|
22
|
+
const sink = {
|
|
23
|
+
readSkill: name => skills.read(name),
|
|
24
|
+
readSupport: (name, path) => skills.readSupportFile(name, path),
|
|
25
|
+
readBlob,
|
|
26
|
+
async hasArchived(name) { return (await skills.read(name)) === null; },
|
|
27
|
+
restoreFromArchive: name => skills.restoreFromArchive(name),
|
|
28
|
+
archive: (name, reason) => skills.archive(name, { reason }),
|
|
29
|
+
updateSkill: (name, body, anchor) => skills.update(name, body, undefined, anchor),
|
|
30
|
+
writeSupportFile: (name, path, content, anchor) => skills.writeSupportFile(name, path, content, undefined, anchor),
|
|
31
|
+
removeSupportFile: (name, path, anchor) => skills.removeSupportFile(name, path, undefined, anchor),
|
|
32
|
+
};
|
|
33
|
+
return { ...sink, listVersions: name => skills.listVersions(name) };
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=undo.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { approvePending } from "./pending-actions.js";
|
|
2
|
+
import { withMergeRun } from "./run-shell.js";
|
|
3
|
+
import { recordRound2Outcome } from "./round2-outcome.js";
|
|
4
|
+
export async function runApproveCommand(options) {
|
|
5
|
+
const taken = await approvePending(options.io, options.home, options.now, options.rowId);
|
|
6
|
+
if (!taken.ok)
|
|
7
|
+
return { ok: false, message: taken.message };
|
|
8
|
+
const row = taken.row;
|
|
9
|
+
const decision = row.decision;
|
|
10
|
+
if (decision === undefined)
|
|
11
|
+
return { ok: false, message: 'pending row has no decision' };
|
|
12
|
+
return await withMergeRun({
|
|
13
|
+
io: options.io,
|
|
14
|
+
home: options.home,
|
|
15
|
+
registry: options.registry,
|
|
16
|
+
command: 'approve',
|
|
17
|
+
approval: { platformWrite: options.platformWrite, mergeApproval: true },
|
|
18
|
+
now: options.now,
|
|
19
|
+
async body(ctx) {
|
|
20
|
+
const stage = (name) => ctx.stage(name, { done: 0, total: 1 });
|
|
21
|
+
await stage('author');
|
|
22
|
+
let outcome;
|
|
23
|
+
try {
|
|
24
|
+
outcome = await options.ports.round2(row, decision, ctx.signal, ctx.runId);
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
outcome = { kind: 'failed', failure: { stage: 'author', code: 'merge-failed', message: error instanceof Error ? error.message : String(error), retryable: true, retryFrom: 'agent2' } };
|
|
28
|
+
}
|
|
29
|
+
const base = { scope: { candidatePairs: 1, queueBefore: { verify: 0, pending: 0, fuse: 1, skipped: 0 } }, notes: [] };
|
|
30
|
+
const record = await recordRound2Outcome(options.io, options.home, options.now, row, outcome);
|
|
31
|
+
return {
|
|
32
|
+
...base,
|
|
33
|
+
stopReason: record.merged ? 'completed' : 'failed',
|
|
34
|
+
pairs: [record.pair],
|
|
35
|
+
totals: record.merged
|
|
36
|
+
? { merged: 1, kept: 0, pending: 0, failed: 0 }
|
|
37
|
+
: { merged: 0, kept: 0, pending: 0, failed: 1 },
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=approve-command.js.map
|
|
@@ -7,12 +7,15 @@ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
|
7
7
|
import { type RuleFinding } from './rules.ts';
|
|
8
8
|
import { type CommitPort, type CommitStagedResult } from './commit-execute.ts';
|
|
9
9
|
import type { StagedTree } from './rules.ts';
|
|
10
|
-
import type { SkillCard } from './
|
|
10
|
+
import type { SkillCard } from './skill-card.ts';
|
|
11
11
|
export interface RunAuthoringOptions {
|
|
12
12
|
readonly io: EvolutionIoLike;
|
|
13
13
|
readonly home: string;
|
|
14
14
|
readonly runId: string;
|
|
15
|
+
readonly stageKey?: string;
|
|
16
|
+
readonly reuseStaging?: boolean;
|
|
15
17
|
readonly target: string;
|
|
18
|
+
readonly mode?: 'merge-into-existing' | 'create-umbrella' | 'keep';
|
|
16
19
|
readonly cards: readonly SkillCard[];
|
|
17
20
|
readonly author: (staging: {
|
|
18
21
|
readonly home: string;
|
|
@@ -25,7 +28,9 @@ export interface RunAuthoringOptions {
|
|
|
25
28
|
export type AuthoringResult = {
|
|
26
29
|
readonly ok: true;
|
|
27
30
|
readonly tree: StagedTree;
|
|
28
|
-
readonly commit: CommitStagedResult
|
|
31
|
+
readonly commit: Extract<CommitStagedResult, {
|
|
32
|
+
readonly ok: true;
|
|
33
|
+
}>;
|
|
29
34
|
} | {
|
|
30
35
|
readonly ok: false;
|
|
31
36
|
readonly message: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createStaging, setStagingTarget } from "./staging.js";
|
|
2
|
+
import { readStagedTree } from "./staged-tree.js";
|
|
3
|
+
import { validateStagedTree, hasBlockingFinding } from "./rules.js";
|
|
4
|
+
import { commitStagedRun } from "./commit-execute.js";
|
|
5
|
+
export async function runAuthoring(options) {
|
|
6
|
+
const targetCard = options.cards.find(card => card.name === options.target);
|
|
7
|
+
if (targetCard?.protectionUnknown === true || targetCard?.protectedBy === 'pinned')
|
|
8
|
+
return { ok: false, message: 'target is pinned: ' + options.target };
|
|
9
|
+
const stageKey = options.stageKey ?? options.runId;
|
|
10
|
+
if (options.reuseStaging === true) {
|
|
11
|
+
if (!await setStagingTarget(options.io, options.home, stageKey, options.target, options.mode))
|
|
12
|
+
return { ok: false, message: 'staging missing or unreadable: ' + stageKey };
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
const created = await createStaging(options.io, options.home, {
|
|
16
|
+
runId: stageKey,
|
|
17
|
+
target: options.target,
|
|
18
|
+
...(options.mode === undefined ? {} : { mode: options.mode }),
|
|
19
|
+
sources: options.cards.map(card => ({ name: card.name, body: card.body, files: card.files ?? [] })),
|
|
20
|
+
promptVersion: 'merge-b/v1',
|
|
21
|
+
ttlDays: 7,
|
|
22
|
+
now: options.now,
|
|
23
|
+
});
|
|
24
|
+
if (!created.ok)
|
|
25
|
+
return { ok: false, message: created.message };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
await options.author({ home: options.home, runId: stageKey });
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return { ok: false, message: 'author failed: ' + (error instanceof Error ? error.message : String(error)) };
|
|
32
|
+
}
|
|
33
|
+
const tree = await readStagedTree(options.io, options.home, stageKey);
|
|
34
|
+
if (tree.kind !== 'present')
|
|
35
|
+
return { ok: false, message: 'staged tree is not readable: ' + (tree.kind === 'unknown' ? tree.reason : 'absent') };
|
|
36
|
+
const findings = validateStagedTree(tree.value);
|
|
37
|
+
if (hasBlockingFinding(findings))
|
|
38
|
+
return { ok: false, message: 'staged tree failed the rule gate', findings };
|
|
39
|
+
const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
|
|
40
|
+
origin: 'subagent',
|
|
41
|
+
summary: options.summary,
|
|
42
|
+
now: options.now,
|
|
43
|
+
});
|
|
44
|
+
if (!commit.ok)
|
|
45
|
+
return { ok: false, message: commit.message };
|
|
46
|
+
return { ok: true, tree: tree.value, commit };
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=authoring-flow.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* outside this adapter: the caller owns staging -> commitStagedRun.
|
|
5
5
|
*/
|
|
6
6
|
import type { MergeDecision, MergeRow } from './contracts.ts';
|
|
7
|
-
import type { SkillCard } from './
|
|
7
|
+
import type { SkillCard } from './skill-card.ts';
|
|
8
8
|
import type { ScanRound1Outcome } from './scan-run.ts';
|
|
9
9
|
export interface ChannelTurnLike {
|
|
10
10
|
readonly text: string;
|
|
@@ -45,6 +45,7 @@ export interface ChannelRound1Options {
|
|
|
45
45
|
readonly allow: readonly string[];
|
|
46
46
|
readonly cwd?: string;
|
|
47
47
|
readonly timeoutMs: number;
|
|
48
|
+
readonly onChildOpened?: (childId: string) => void;
|
|
48
49
|
}
|
|
49
50
|
export interface ChannelRound1Result {
|
|
50
51
|
readonly outcome: ScanRound1Outcome;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parseDecision } from "./decision.js";
|
|
2
|
+
import { buildRound1Prompt, buildRound2Prompt, STANDBY_PROMPT } from "./prompts.js";
|
|
3
|
+
export async function runRound1OverChannel(options, candidate, signal) {
|
|
4
|
+
let childId;
|
|
5
|
+
const dispose = async () => {
|
|
6
|
+
if (childId !== undefined) {
|
|
7
|
+
const id = childId;
|
|
8
|
+
childId = undefined;
|
|
9
|
+
try {
|
|
10
|
+
await options.channel.dispose(id);
|
|
11
|
+
}
|
|
12
|
+
catch { /* teardown must not replace the outcome */ }
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const opened = await options.channel.open({
|
|
16
|
+
parent: options.parent,
|
|
17
|
+
label: 'merge-round1',
|
|
18
|
+
promptText: STANDBY_PROMPT,
|
|
19
|
+
kind: 'reviewer',
|
|
20
|
+
allow: options.allow,
|
|
21
|
+
...(options.cwd === undefined ? {} : { cwd: options.cwd }),
|
|
22
|
+
timeoutMs: options.timeoutMs,
|
|
23
|
+
signal,
|
|
24
|
+
});
|
|
25
|
+
if (opened.kind === 'unavailable')
|
|
26
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: opened.code, message: opened.reason, retryable: true, retryFrom: 'agent1' } }, dispose };
|
|
27
|
+
if (opened.kind === 'aborted')
|
|
28
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: 'aborted', message: 'round1 aborted', retryable: false, retryFrom: 'none' } }, dispose };
|
|
29
|
+
childId = opened.childId;
|
|
30
|
+
if (opened.turn.stopReason !== 'completed') {
|
|
31
|
+
await dispose();
|
|
32
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: opened.turn.stopReason, message: 'standby did not complete', retryable: true, retryFrom: 'agent1' } }, dispose };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
options.onChildOpened?.(opened.childId);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
await dispose();
|
|
39
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: 'child-bind', message: String(error), retryable: true, retryFrom: 'agent1' } }, dispose };
|
|
40
|
+
}
|
|
41
|
+
const round1 = await options.channel.continueTurn(opened.childId, buildRound1Prompt(candidate, options.cards), { timeoutMs: options.timeoutMs, signal });
|
|
42
|
+
if (round1.kind !== 'ok') {
|
|
43
|
+
await dispose();
|
|
44
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: round1.kind, message: round1.kind === 'unavailable' ? round1.reason : 'round1 aborted', retryable: round1.kind !== 'aborted', retryFrom: round1.kind === 'aborted' ? 'none' : 'agent1' } }, dispose };
|
|
45
|
+
}
|
|
46
|
+
if (round1.turn.stopReason !== 'completed') {
|
|
47
|
+
await dispose();
|
|
48
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: round1.turn.stopReason, message: 'round1 did not complete', retryable: true, retryFrom: 'agent1' } }, dispose };
|
|
49
|
+
}
|
|
50
|
+
const parsed = parseDecision(round1.turn.text, candidate);
|
|
51
|
+
if (!parsed.ok) {
|
|
52
|
+
await dispose();
|
|
53
|
+
return { outcome: { kind: 'failed', failure: { stage: 'decide', code: parsed.code, message: parsed.message, retryable: true, retryFrom: 'agent1' } }, dispose };
|
|
54
|
+
}
|
|
55
|
+
if (!parsed.decision.shouldMerge || parsed.decision.mode === 'keep') {
|
|
56
|
+
await dispose();
|
|
57
|
+
return { outcome: { kind: 'keep', decision: parsed.decision }, dispose };
|
|
58
|
+
}
|
|
59
|
+
const child = opened.childId;
|
|
60
|
+
return {
|
|
61
|
+
outcome: { kind: 'merge', decision: parsed.decision },
|
|
62
|
+
childId: child,
|
|
63
|
+
dispose,
|
|
64
|
+
async author(row, decision, turnSignal) {
|
|
65
|
+
const turn = await options.channel.continueTurn(child, buildRound2Prompt(row, decision), { timeoutMs: options.timeoutMs, signal: turnSignal });
|
|
66
|
+
if (turn.kind !== 'ok')
|
|
67
|
+
throw new Error(turn.kind === 'unavailable' ? turn.reason : 'round2 aborted');
|
|
68
|
+
if (turn.turn.stopReason !== 'completed')
|
|
69
|
+
throw new Error('round2 did not complete: ' + turn.turn.stopReason);
|
|
70
|
+
return turn.turn.text;
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=channel-port.js.map
|
package/lib/types/commands.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type MergeCliCommand = {
|
|
|
8
8
|
readonly verb: 'pair';
|
|
9
9
|
readonly a: string;
|
|
10
10
|
readonly b: string;
|
|
11
|
+
readonly force?: true;
|
|
11
12
|
} | {
|
|
12
13
|
readonly verb: 'status';
|
|
13
14
|
readonly id?: string;
|
|
@@ -23,6 +24,10 @@ export type MergeCliCommand = {
|
|
|
23
24
|
readonly verb: 'doctor';
|
|
24
25
|
} | {
|
|
25
26
|
readonly verb: 'pending';
|
|
27
|
+
readonly expired?: true;
|
|
28
|
+
} | {
|
|
29
|
+
readonly verb: 'candidates';
|
|
30
|
+
readonly retry?: true;
|
|
26
31
|
} | {
|
|
27
32
|
readonly verb: 'approve';
|
|
28
33
|
readonly id: string;
|
|
@@ -33,9 +38,13 @@ export type MergeCliCommand = {
|
|
|
33
38
|
readonly verb: 'resume';
|
|
34
39
|
readonly id?: string;
|
|
35
40
|
readonly clear?: string;
|
|
41
|
+
readonly retry?: true;
|
|
36
42
|
} | {
|
|
37
43
|
readonly verb: 'skipped';
|
|
38
44
|
readonly restore?: string;
|
|
45
|
+
} | {
|
|
46
|
+
readonly verb: 'discard';
|
|
47
|
+
readonly id: string;
|
|
39
48
|
} | {
|
|
40
49
|
readonly verb: 'undo';
|
|
41
50
|
readonly target: string;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const USAGE = 'usage: /evolution merge scan | pair [--force] <a> <b> | status [id] | report [id] | cancel [id] | runs | doctor | pending [--expired] | candidates [--retry] | approve <id> | reject <id> | resume [id] [--retry] [--clear <id>] | skipped [--restore <id>] | discard <id> | undo <target> --run <id>';
|
|
2
|
+
function flags(args, allowed) {
|
|
3
|
+
const values = new Map();
|
|
4
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
5
|
+
const token = args[index];
|
|
6
|
+
if (!token.startsWith('--') || token === '--force' || token === '--expired' || token === '--retry')
|
|
7
|
+
continue;
|
|
8
|
+
const name = token.slice(2);
|
|
9
|
+
if (!allowed.includes(name))
|
|
10
|
+
return { values, error: 'unknown flag: --' + name };
|
|
11
|
+
const value = args[index + 1];
|
|
12
|
+
if (value === undefined || value.startsWith('--'))
|
|
13
|
+
return { values, error: 'missing value for --' + name };
|
|
14
|
+
values.set(name, value);
|
|
15
|
+
index += 1;
|
|
16
|
+
}
|
|
17
|
+
return { values };
|
|
18
|
+
}
|
|
19
|
+
function switches(args) {
|
|
20
|
+
return new Set(args.filter(token => token === '--force' || token === '--expired' || token === '--retry').map(token => token.slice(2)));
|
|
21
|
+
}
|
|
22
|
+
function positional(args) {
|
|
23
|
+
const filtered = args.filter(token => token !== '--force' && token !== '--expired' && token !== '--retry');
|
|
24
|
+
return filtered.filter((token, index) => !token.startsWith('--') && !(index > 0 && filtered[index - 1].startsWith('--')));
|
|
25
|
+
}
|
|
26
|
+
export function parseMergeCommand(argv) {
|
|
27
|
+
const [verb, ...rest] = argv;
|
|
28
|
+
if (verb === undefined || verb === 'help' || verb === '--help' || verb === '-h')
|
|
29
|
+
return { ok: true, command: { verb: 'scan' }, help: true };
|
|
30
|
+
const pos = positional(rest);
|
|
31
|
+
const marked = switches(rest);
|
|
32
|
+
switch (verb) {
|
|
33
|
+
case 'scan': return pos.length === 0 ? { ok: true, command: { verb: 'scan' } } : { ok: false, code: 'usage', message: USAGE };
|
|
34
|
+
case 'pair': return pos.length === 2 ? { ok: true, command: { verb: 'pair', a: pos[0], b: pos[1], ...(marked.has('force') ? { force: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
|
|
35
|
+
case 'status': return pos.length <= 1 ? { ok: true, command: { verb: 'status', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
|
|
36
|
+
case 'report': return pos.length <= 1 ? { ok: true, command: { verb: 'report', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
|
|
37
|
+
case 'cancel': return pos.length <= 1 ? { ok: true, command: { verb: 'cancel', ...(pos[0] === undefined ? {} : { id: pos[0] }) } } : { ok: false, code: 'usage', message: USAGE };
|
|
38
|
+
case 'runs': return pos.length === 0 ? { ok: true, command: { verb: 'runs' } } : { ok: false, code: 'usage', message: USAGE };
|
|
39
|
+
case 'doctor': return pos.length === 0 ? { ok: true, command: { verb: 'doctor' } } : { ok: false, code: 'usage', message: USAGE };
|
|
40
|
+
case 'pending': return pos.length === 0 ? { ok: true, command: { verb: 'pending', ...(marked.has('expired') ? { expired: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
|
|
41
|
+
case 'candidates': return pos.length === 0 ? { ok: true, command: { verb: 'candidates', ...(marked.has('retry') ? { retry: true } : {}) } } : { ok: false, code: 'usage', message: USAGE };
|
|
42
|
+
case 'approve': return pos.length === 1 ? { ok: true, command: { verb: 'approve', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
|
|
43
|
+
case 'reject': return pos.length === 1 ? { ok: true, command: { verb: 'reject', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
|
|
44
|
+
case 'resume': {
|
|
45
|
+
const parsed = flags(rest, ['clear']);
|
|
46
|
+
if (parsed.error !== undefined)
|
|
47
|
+
return { ok: false, code: 'usage', message: parsed.error };
|
|
48
|
+
const clear = parsed.values.get('clear');
|
|
49
|
+
if (pos.length > 1)
|
|
50
|
+
return { ok: false, code: 'usage', message: USAGE };
|
|
51
|
+
return { ok: true, command: { verb: 'resume', ...(pos[0] === undefined ? {} : { id: pos[0] }), ...(clear === undefined ? {} : { clear }), ...(marked.has('retry') ? { retry: true } : {}) } };
|
|
52
|
+
}
|
|
53
|
+
case 'skipped': {
|
|
54
|
+
const parsed = flags(rest, ['restore']);
|
|
55
|
+
if (parsed.error !== undefined)
|
|
56
|
+
return { ok: false, code: 'usage', message: parsed.error };
|
|
57
|
+
if (pos.length > 0)
|
|
58
|
+
return { ok: false, code: 'usage', message: USAGE };
|
|
59
|
+
const restore = parsed.values.get('restore');
|
|
60
|
+
return { ok: true, command: { verb: 'skipped', ...(restore === undefined ? {} : { restore }) } };
|
|
61
|
+
}
|
|
62
|
+
case 'discard': return pos.length === 1 ? { ok: true, command: { verb: 'discard', id: pos[0] } } : { ok: false, code: 'usage', message: USAGE };
|
|
63
|
+
case 'undo': {
|
|
64
|
+
const parsed = flags(rest, ['run']);
|
|
65
|
+
if (parsed.error !== undefined)
|
|
66
|
+
return { ok: false, code: 'usage', message: parsed.error };
|
|
67
|
+
const target = pos[0];
|
|
68
|
+
const run = parsed.values.get('run');
|
|
69
|
+
if (target === undefined || run === undefined || pos.length !== 1)
|
|
70
|
+
return { ok: false, code: 'usage', message: USAGE };
|
|
71
|
+
return { ok: true, command: { verb: 'undo', target, run } };
|
|
72
|
+
}
|
|
73
|
+
default: return { ok: false, code: 'usage', message: 'unknown merge command: ' + verb + '\n' + USAGE };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function mergeUsage() {
|
|
77
|
+
return USAGE;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -9,6 +9,8 @@ import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
|
9
9
|
export interface CommitAssemblyPort {
|
|
10
10
|
/** Current SKILL.md body, or null when the skill is not live. */
|
|
11
11
|
readSkill(name: string): Promise<string | null>;
|
|
12
|
+
/** Whether the name is already occupied by an archived (tombstoned) skill. */
|
|
13
|
+
isArchived?(name: string): Promise<boolean>;
|
|
12
14
|
}
|
|
13
15
|
export interface CommitAssemblyOptions {
|
|
14
16
|
readonly origin: WriteOrigin;
|