@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
|
@@ -1,120 +1,97 @@
|
|
|
1
|
-
/** Startup replay for non-terminal commit journals. */
|
|
1
|
+
/** Startup replay for non-terminal commit journals: report only, never write skills. */
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { recoveryFor } from "./domain/commit-journal.js";
|
|
4
3
|
import { writeJournal } from "./domain/commit-journal-store.js";
|
|
5
|
-
import {
|
|
4
|
+
import { snapshotPath } from "./commit-snapshot.js";
|
|
6
5
|
import { readCommitReceipt } from "./commit-receipt.js";
|
|
7
|
-
|
|
6
|
+
import { readUnitEvidence } from '@lmzhen/dsh-evolution-core';
|
|
7
|
+
export async function recoverCommitJournals(io, home, now, isActive) {
|
|
8
8
|
const dir = join(home, 'merge', 'journals');
|
|
9
|
-
if (!(await io.exists(dir)))
|
|
10
|
-
return [];
|
|
11
|
-
const files = (await io.list(dir)).filter(name => name.endsWith('.json'));
|
|
12
9
|
const reports = [];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
journal = JSON.parse(await io.readText(join(dir, file)) ?? '');
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (isActive?.(journal.runId) === true) {
|
|
22
|
-
reports.push({ runId: journal.runId, phase: journal.phase, action: 'skipped-active-run', ok: true });
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
const recovery = recoveryFor(journal);
|
|
26
|
-
if (recovery === 'none')
|
|
27
|
-
continue;
|
|
28
|
-
if (recovery === 'refuse') {
|
|
29
|
-
reports.push({ runId: journal.runId, phase: journal.phase, action: 'unknown-phase-refused', ok: false });
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
let receipt = null;
|
|
10
|
+
let files = [];
|
|
11
|
+
if (await io.exists(dir)) {
|
|
33
12
|
try {
|
|
34
|
-
|
|
13
|
+
files = (await io.list(dir)).filter(name => name.endsWith('.json'));
|
|
35
14
|
}
|
|
36
15
|
catch {
|
|
37
|
-
reports.push({ runId:
|
|
38
|
-
continue;
|
|
16
|
+
reports.push({ runId: '(list)', phase: 'unknown', action: 'journal-list-failed', ok: false, journalPath: dir });
|
|
39
17
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
let snapshot;
|
|
18
|
+
}
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const path = join(dir, file);
|
|
46
21
|
try {
|
|
47
|
-
|
|
22
|
+
let journal;
|
|
23
|
+
try {
|
|
24
|
+
journal = JSON.parse(await io.readText(path) ?? '');
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
reports.push({ runId: file.replace(/\.json$/, ''), phase: 'unknown', action: 'journal-unreadable', ok: false, journalPath: path });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (isActive?.(journal.runId) === true) {
|
|
31
|
+
reports.push({ runId: journal.runId, phase: journal.phase, action: 'skipped-active-run', ok: true, target: journal.target, sources: journal.sources, journalPath: path });
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let receipt = null;
|
|
35
|
+
try {
|
|
36
|
+
receipt = await readCommitReceipt(io, home, journal.runId, journal.target);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
reports.push({ runId: journal.runId, phase: journal.phase, action: 'receipt-read-failed', ok: false, target: journal.target, sources: journal.sources, journalPath: path });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (receipt !== null) {
|
|
43
|
+
await writeJournal(io, home, { ...journal, phase: 'committed', unitId: receipt.unitId, updatedAt: new Date(now).toISOString() });
|
|
44
|
+
reports.push({ runId: journal.runId, phase: journal.phase, action: 'receipt-committed', ok: true, target: journal.target, sources: journal.sources, journalPath: path, unitId: receipt.unitId });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (journal.phase === 'committed') {
|
|
48
|
+
reports.push({ runId: journal.runId, phase: journal.phase, action: 'receipt-missing', ok: false, target: journal.target, sources: journal.sources, journalPath: path });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (journal.phase === 'failed' || journal.phase === 'rolled-back')
|
|
52
|
+
continue;
|
|
53
|
+
reports.push({
|
|
54
|
+
runId: journal.runId, phase: journal.phase, action: 'manual-required', ok: false,
|
|
55
|
+
target: journal.target, sources: journal.sources, journalPath: path, snapshotPath: snapshotPath(home, journal.runId),
|
|
56
|
+
});
|
|
48
57
|
}
|
|
49
58
|
catch (error) {
|
|
50
|
-
reports.push({ runId:
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
let action = 'report-only';
|
|
54
|
-
let ok = true;
|
|
55
|
-
if (recovery === 'retry-commit') {
|
|
56
|
-
action = 'no-host-write';
|
|
59
|
+
reports.push({ runId: file.replace(/\.json$/, ''), phase: 'unknown', action: 'recovery-error: ' + (error instanceof Error ? error.message : String(error)), ok: false, journalPath: path });
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
}
|
|
62
|
+
const unitDir = join(home, 'merge', 'unit-journals');
|
|
63
|
+
if (await io.exists(unitDir)) {
|
|
64
|
+
let unitFiles = [];
|
|
65
|
+
try {
|
|
66
|
+
unitFiles = (await io.list(unitDir)).filter(name => name.endsWith('.json'));
|
|
61
67
|
}
|
|
62
|
-
|
|
68
|
+
catch { /* no unit journals */ }
|
|
69
|
+
for (const file of unitFiles) {
|
|
70
|
+
const path = join(unitDir, file);
|
|
63
71
|
try {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
restoredArchived = false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (!restoredArchived) {
|
|
73
|
-
for (const source of snapshot.sourceBodies)
|
|
74
|
-
await io.writeText(skillBodyPath(skillRoot, source.name), source.body);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
if (snapshot.create && snapshot.targetBody === null) {
|
|
78
|
-
try {
|
|
79
|
-
await io.remove(skillBodyPath(skillRoot, snapshot.target));
|
|
80
|
-
}
|
|
81
|
-
catch { /* already gone */ }
|
|
82
|
-
try {
|
|
83
|
-
const dir = join(skillRoot, snapshot.target);
|
|
84
|
-
if ((await io.list(dir)).length === 0)
|
|
85
|
-
await io.remove(dir);
|
|
86
|
-
}
|
|
87
|
-
catch { /* absent or non-empty */ }
|
|
72
|
+
const journal = JSON.parse(await io.readText(path) ?? '');
|
|
73
|
+
const evidence = await readUnitEvidence(io, home, journal.unitId);
|
|
74
|
+
if (evidence !== null) {
|
|
75
|
+
reports.push({ runId: journal.runId, phase: 'committed', action: 'unit-committed', ok: true, target: journal.target, sources: journal.sources, journalPath: path, unitId: journal.unitId });
|
|
76
|
+
continue;
|
|
88
77
|
}
|
|
89
|
-
|
|
90
|
-
|
|
78
|
+
if (journal.phase === 'committed') {
|
|
79
|
+
reports.push({ runId: journal.runId, phase: 'committed', action: 'unit-evidence-missing', ok: false, target: journal.target, sources: journal.sources, journalPath: path, unitId: journal.unitId });
|
|
80
|
+
continue;
|
|
91
81
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
for (const file of snapshot.targetSupportAfter ?? []) {
|
|
97
|
-
if (!oldPaths.has(file.path)) {
|
|
98
|
-
try {
|
|
99
|
-
await io.remove(skillFilePath(skillRoot, snapshot.target, file.path));
|
|
100
|
-
}
|
|
101
|
-
catch { /* already gone */ }
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
for (const file of snapshot.sourceSupport ?? [])
|
|
105
|
-
await io.writeText(skillFilePath(skillRoot, file.name, file.path), file.content);
|
|
106
|
-
action = recovery === 'rollback-sources' ? 'restored-sources-and-target' : 'restored-target';
|
|
82
|
+
if (journal.phase === 'failed' || journal.phase === 'rolled-back')
|
|
83
|
+
continue;
|
|
84
|
+
reports.push({ runId: journal.runId, phase: journal.phase, action: 'unit-manual-required', ok: false, target: journal.target, sources: journal.sources, journalPath: path, unitId: journal.unitId });
|
|
107
85
|
}
|
|
108
|
-
catch
|
|
109
|
-
|
|
110
|
-
ok = false;
|
|
86
|
+
catch {
|
|
87
|
+
reports.push({ runId: '(unit)', phase: 'unknown', action: 'unit-journal-unreadable', ok: false, journalPath: path });
|
|
111
88
|
}
|
|
112
89
|
}
|
|
113
|
-
if (ok)
|
|
114
|
-
await writeJournal(io, home, { ...journal, phase: 'rolled-back', updatedAt: new Date(now).toISOString() });
|
|
115
|
-
reports.push({ runId: journal.runId, phase: journal.phase, action, ok });
|
|
116
90
|
}
|
|
117
|
-
|
|
91
|
+
try {
|
|
92
|
+
await io.writeText(join(home, 'merge', 'recovery.json'), JSON.stringify(reports, null, 2));
|
|
93
|
+
}
|
|
94
|
+
catch { /* report file is best-effort; reports are still returned */ }
|
|
118
95
|
return reports;
|
|
119
96
|
}
|
|
120
97
|
//# sourceMappingURL=commit-recovery.js.map
|
package/lib/types/contracts.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The data layer keeps four states; the user face collapses verify/fuse into one retry
|
|
5
5
|
* queue and keeps the stage as a chip. The API queue must never expose the raw state.
|
|
6
6
|
*/
|
|
7
|
-
export declare const MERGE_STATE_VERSION =
|
|
7
|
+
export declare const MERGE_STATE_VERSION = 3;
|
|
8
8
|
export type MergePairState = 'verify' | 'pending' | 'approved' | 'fuse' | 'skipped';
|
|
9
9
|
export type MergeQueue = 'verify' | 'pending' | 'fuse' | 'skipped';
|
|
10
10
|
export type MergeStage = 'verify' | 'decide' | 'author' | 'commit';
|
|
@@ -25,6 +25,7 @@ export interface MergeFailure {
|
|
|
25
25
|
readonly message: string;
|
|
26
26
|
readonly retryable: boolean;
|
|
27
27
|
readonly retryFrom: 'agent1' | 'agent2' | 'none';
|
|
28
|
+
readonly lengths?: Readonly<Record<string, number>>;
|
|
28
29
|
}
|
|
29
30
|
/** One persisted pair. The id is stable for the UI; the pairKey is the natural key. */
|
|
30
31
|
export interface MergeGateFact {
|
|
@@ -43,6 +44,12 @@ export interface MergeCandidate {
|
|
|
43
44
|
readonly gate?: MergeGateFact;
|
|
44
45
|
readonly source?: 'lexical' | 'proposed' | 'engine';
|
|
45
46
|
}
|
|
47
|
+
export type RevisionRef = {
|
|
48
|
+
readonly kind: 'absent';
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: 'present';
|
|
51
|
+
readonly sha256: string;
|
|
52
|
+
};
|
|
46
53
|
export interface MergeDecision {
|
|
47
54
|
readonly shouldMerge: boolean;
|
|
48
55
|
readonly mode: 'merge-into-existing' | 'create-umbrella' | 'keep';
|
|
@@ -50,9 +57,21 @@ export interface MergeDecision {
|
|
|
50
57
|
readonly sources: readonly string[];
|
|
51
58
|
readonly rationale: string;
|
|
52
59
|
readonly confidence?: number;
|
|
60
|
+
readonly promptVersion?: string;
|
|
61
|
+
readonly promptHash?: string;
|
|
53
62
|
readonly aSummary?: string;
|
|
54
63
|
readonly bSummary?: string;
|
|
55
64
|
readonly cacheRead?: number;
|
|
65
|
+
readonly targetRevisionRef?: RevisionRef;
|
|
66
|
+
readonly sourceRevisionRef?: RevisionRef;
|
|
67
|
+
/** Per-source anchors recorded with the decision; a commit re-checks each one. */
|
|
68
|
+
readonly sourceRevisions?: Readonly<Record<string, RevisionRef>>;
|
|
69
|
+
readonly supportDigest?: string;
|
|
70
|
+
}
|
|
71
|
+
export interface MergeValidationPlanRef {
|
|
72
|
+
readonly planVersion: 1;
|
|
73
|
+
readonly stageDigest: string;
|
|
74
|
+
readonly revision: string;
|
|
56
75
|
}
|
|
57
76
|
export interface MergeUsage {
|
|
58
77
|
readonly inputTokens?: number;
|
|
@@ -86,7 +105,18 @@ export interface MergeReportPair {
|
|
|
86
105
|
readonly unitId?: string;
|
|
87
106
|
readonly failure?: MergeFailure;
|
|
88
107
|
readonly usage?: MergeUsage;
|
|
108
|
+
readonly lengths?: Readonly<Record<string, number>>;
|
|
89
109
|
readonly timing?: MergeTiming;
|
|
110
|
+
readonly validationPlan?: MergeValidationPlanRef;
|
|
111
|
+
readonly criteria?: MergeCriteria;
|
|
112
|
+
readonly reportOnly?: boolean;
|
|
113
|
+
}
|
|
114
|
+
export interface MergeCriteria {
|
|
115
|
+
readonly decision: 'keep' | 'merge' | 'candidate';
|
|
116
|
+
readonly validation: 'not-run' | 'report-only' | 'required';
|
|
117
|
+
readonly commit: 'never' | 'if-approved' | 'required';
|
|
118
|
+
readonly archive: 'none' | 'sources';
|
|
119
|
+
readonly evidence: 'none' | 'receipt';
|
|
90
120
|
}
|
|
91
121
|
export interface MergeReport {
|
|
92
122
|
readonly schemaVersion: 2;
|
|
@@ -111,6 +141,17 @@ export interface MergeReport {
|
|
|
111
141
|
readonly pairKey: string;
|
|
112
142
|
readonly queue: MergeQueue;
|
|
113
143
|
}[];
|
|
144
|
+
readonly reportOnly?: readonly MergeReportPair[];
|
|
145
|
+
readonly validationFailures?: readonly MergeReportPair[];
|
|
146
|
+
readonly archived?: readonly string[];
|
|
147
|
+
readonly commit?: {
|
|
148
|
+
readonly unitId?: string;
|
|
149
|
+
readonly target?: string;
|
|
150
|
+
readonly created?: boolean;
|
|
151
|
+
};
|
|
152
|
+
readonly usage?: MergeUsage;
|
|
153
|
+
readonly timing?: MergeTiming;
|
|
154
|
+
readonly criteria?: readonly MergeCriteria[];
|
|
114
155
|
}
|
|
115
156
|
export interface MergeRow {
|
|
116
157
|
readonly id: string;
|
|
@@ -131,6 +172,8 @@ export interface MergeRow {
|
|
|
131
172
|
readonly decision?: MergeDecision;
|
|
132
173
|
readonly sourceRevision?: string;
|
|
133
174
|
readonly targetRevision?: string;
|
|
175
|
+
readonly sourceRevisionRef?: RevisionRef;
|
|
176
|
+
readonly targetRevisionRef?: RevisionRef;
|
|
134
177
|
readonly runId?: string;
|
|
135
178
|
readonly unitId?: string;
|
|
136
179
|
readonly origin?: 'agent' | 'human' | 'system';
|
package/lib/types/contracts.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The data layer keeps four states; the user face collapses verify/fuse into one retry
|
|
5
5
|
* queue and keeps the stage as a chip. The API queue must never expose the raw state.
|
|
6
6
|
*/
|
|
7
|
-
export const MERGE_STATE_VERSION =
|
|
7
|
+
export const MERGE_STATE_VERSION = 3;
|
|
8
8
|
export const MERGE_PAIR_STATES = ['verify', 'pending', 'approved', 'fuse', 'skipped'];
|
|
9
9
|
export const MERGE_QUEUES = ['verify', 'pending', 'fuse', 'skipped'];
|
|
10
10
|
/** User-facing queue of one data state. verify/fuse share retry, not two tabs. */
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Pure merge transition table. No io, no run shell, no subagents.
|
|
3
3
|
*
|
|
4
4
|
* Every transition returns the whole next row (or a drop), so the caller can persist it
|
|
5
|
-
* through
|
|
5
|
+
* through one state transaction. Invalid transitions are refused by name;
|
|
6
6
|
* a caller never "repairs" a row by guessing which state the operator meant.
|
|
7
7
|
*/
|
|
8
8
|
import { type MergeDecision, type MergeFailure, type MergeRow } from './contracts.ts';
|
|
@@ -72,5 +72,6 @@ export type TransitionResult = {
|
|
|
72
72
|
readonly code: 'invalid-transition';
|
|
73
73
|
readonly message: string;
|
|
74
74
|
};
|
|
75
|
-
|
|
75
|
+
/** Legacy v2 projection only; runtime writes must use transitionV3/RowTx. */
|
|
76
|
+
export declare function transitionLegacyForProjection(current: MergeRow | undefined, event: MergeEvent): TransitionResult;
|
|
76
77
|
//# sourceMappingURL=coordinator.d.ts.map
|
package/lib/types/coordinator.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Pure merge transition table. No io, no run shell, no subagents.
|
|
3
3
|
*
|
|
4
4
|
* Every transition returns the whole next row (or a drop), so the caller can persist it
|
|
5
|
-
* through
|
|
5
|
+
* through one state transaction. Invalid transitions are refused by name;
|
|
6
6
|
* a caller never "repairs" a row by guessing which state the operator meant.
|
|
7
7
|
*/
|
|
8
8
|
import { pairKey, queueOf, rowIdOf, } from "./contracts.js";
|
|
@@ -49,7 +49,8 @@ function next(row, at, state, patch = {}) {
|
|
|
49
49
|
},
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
52
|
+
/** Legacy v2 projection only; runtime writes must use transitionV3/RowTx. */
|
|
53
|
+
export function transitionLegacyForProjection(current, event) {
|
|
53
54
|
if (event.type === 'candidate') {
|
|
54
55
|
if (current !== undefined)
|
|
55
56
|
return { ok: true, row: current };
|
|
@@ -106,7 +107,7 @@ export function transition(current, event) {
|
|
|
106
107
|
: invalid(event, current.state);
|
|
107
108
|
case 'approve':
|
|
108
109
|
return current.state === 'pending'
|
|
109
|
-
? next(current, event.at, 'approved',
|
|
110
|
+
? next(current, event.at, 'approved', (event.runId === undefined ? {} : { runId: event.runId }))
|
|
110
111
|
: invalid(event, current.state);
|
|
111
112
|
case 'reject':
|
|
112
113
|
return current.state === 'pending' ? next(current, event.at, 'skipped', { origin: 'human' }) : invalid(event, current.state);
|
package/lib/types/decision.js
CHANGED
package/lib/types/doctor.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
|
|
2
|
+
import { type ReconcileReport } from './domain/reconcile.ts';
|
|
3
|
+
import type { DoctorView } from './wire.ts';
|
|
2
4
|
export declare const MERGE_DOCTOR_SCHEMA_VERSION = 3;
|
|
5
|
+
/** The global doctor's merge line; built from the DoctorView envelope, never from raw files. */
|
|
6
|
+
export interface MergeDoctorFact {
|
|
7
|
+
readonly status: 'ok' | 'warn' | 'fail' | 'unknown';
|
|
8
|
+
readonly detail: string;
|
|
9
|
+
readonly remedy?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Compose the one-line global-doctor fact from buildDoctorView + the last reconcile report. */
|
|
12
|
+
export declare function mergeDoctorFact(view: DoctorView, report: ReconcileReport | null): MergeDoctorFact;
|
|
3
13
|
export interface MergeDoctorRow {
|
|
4
14
|
readonly key: string;
|
|
5
15
|
readonly label: string;
|
|
6
|
-
readonly status: 'ok' | 'warn' | 'unknown';
|
|
16
|
+
readonly status: 'ok' | 'warn' | 'blocked' | 'unknown';
|
|
7
17
|
readonly detail: string;
|
|
8
18
|
}
|
|
19
|
+
/** Global doctor host section: a missing merge mount must fail with E-325, not render empty. */
|
|
20
|
+
export declare function mergeDoctorAvailability(mounted: boolean): {
|
|
21
|
+
readonly ok: true;
|
|
22
|
+
} | {
|
|
23
|
+
readonly ok: false;
|
|
24
|
+
readonly code: 'E-325';
|
|
25
|
+
};
|
|
9
26
|
export declare function mergeDoctor(io: EvolutionIoLike, home: string, configuredRoot?: string): Promise<readonly MergeDoctorRow[]>;
|
|
10
27
|
//# sourceMappingURL=doctor.d.ts.map
|
package/lib/types/doctor.js
CHANGED
|
@@ -1,27 +1,163 @@
|
|
|
1
|
+
import { readTextOrNull } from "./domain/read-probe.js";
|
|
1
2
|
/**
|
|
2
3
|
* Merge doctor rows: state, pending, gate cache and staging are each probed read-only.
|
|
3
4
|
* A missing file is an empty feature, not a failure; an unreadable file is a warning the
|
|
4
5
|
* operator can act on. This module never repairs or writes.
|
|
5
6
|
*/
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { stat } from 'node:fs/promises';
|
|
6
9
|
import { join } from 'node:path';
|
|
7
|
-
import { probeList } from '@lmzhen/dsh-evolution-core';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
+
import { probeList, readTreeLock, runsFile } from '@lmzhen/dsh-evolution-core';
|
|
11
|
+
import { migrationJournalPath, migrationReportPath, v2BackupPath } from "./domain/state-bootstrap.js";
|
|
12
|
+
import { readMergeSnapshot } from "./domain/merge-snapshot.js";
|
|
13
|
+
import { validateArtifacts } from "./domain/consistency.js";
|
|
14
|
+
import { readJournal } from "./domain/commit-journal-store.js";
|
|
10
15
|
import { stagingRoot } from "./staging.js";
|
|
16
|
+
import { readReconcileReport } from "./domain/reconcile.js";
|
|
11
17
|
export const MERGE_DOCTOR_SCHEMA_VERSION = 3;
|
|
18
|
+
/** Compose the one-line global-doctor fact from buildDoctorView + the last reconcile report. */
|
|
19
|
+
export function mergeDoctorFact(view, report) {
|
|
20
|
+
const counts = view.counts;
|
|
21
|
+
const reconcileText = report === null
|
|
22
|
+
? 'reconcile 尚未运行'
|
|
23
|
+
: report.state === 'blocked'
|
|
24
|
+
? 'reconcile blocked ' + (report.code ?? 'merge-state-blocked')
|
|
25
|
+
: 'reconcile ' + String(report.actions.length) + ' 项动作';
|
|
26
|
+
const problems = [];
|
|
27
|
+
if (view.blocked !== undefined)
|
|
28
|
+
problems.push('state blocked ' + view.blocked.code);
|
|
29
|
+
if (view.hostOnly !== undefined)
|
|
30
|
+
problems.push('宿主数据面不可用 ' + view.hostOnly.code);
|
|
31
|
+
if (view.staging.state === 'unreadable')
|
|
32
|
+
problems.push('staging 索引不可读' + (view.staging.detail === undefined ? '' : ':' + view.staging.detail));
|
|
33
|
+
if (counts.expired > 0)
|
|
34
|
+
problems.push('expired ' + String(counts.expired));
|
|
35
|
+
const unstable = view.rows.filter(row => row.status === 'warn' || row.status === 'fail');
|
|
36
|
+
if (unstable.length > 0)
|
|
37
|
+
problems.push(unstable.map(row => row.key + '=' + row.status).join(','));
|
|
38
|
+
if (report?.state === 'blocked')
|
|
39
|
+
problems.push('reconcile blocked');
|
|
40
|
+
const latest = view.latestRun;
|
|
41
|
+
const latestText = latest === undefined ? '' : ';最近 run ' + latest.id + ' [' + latest.state + ']';
|
|
42
|
+
const detail = 'verify ' + String(counts.active.verify) + ' / pending ' + String(counts.active.pending) + ' / fuse ' + String(counts.active.fuse) + ' / skipped ' + String(counts.active.skipped) + ' / expired ' + String(counts.expired)
|
|
43
|
+
+ ';staging ' + view.staging.state + ' dirs=' + String(view.staging.dirs)
|
|
44
|
+
+ ';审批 ' + (view.approval.mergeApproval ? '开' : '关') + ';' + reconcileText + latestText
|
|
45
|
+
+ (problems.length === 0 ? '' : ';问题:' + problems.join(';'));
|
|
46
|
+
const status = problems.length === 0
|
|
47
|
+
? 'ok'
|
|
48
|
+
: view.blocked !== undefined || view.hostOnly !== undefined || report?.state === 'blocked' ? 'fail' : 'warn';
|
|
49
|
+
return problems.length === 0 ? { status, detail } : { status, detail, remedy: '运行 /evolution merge doctor 查看逐项;必要时 /evolution merge reconcile 收敛' };
|
|
50
|
+
}
|
|
51
|
+
/** Global doctor host section: a missing merge mount must fail with E-325, not render empty. */
|
|
52
|
+
export function mergeDoctorAvailability(mounted) {
|
|
53
|
+
return mounted ? { ok: true } : { ok: false, code: 'E-325' };
|
|
54
|
+
}
|
|
12
55
|
export async function mergeDoctor(io, home, configuredRoot) {
|
|
13
56
|
const rows = [];
|
|
14
|
-
const
|
|
15
|
-
const
|
|
57
|
+
const snapshot = await readMergeSnapshot({ io, home, runs: [], approval: false });
|
|
58
|
+
const hasState = await io.exists(join(home, 'merge', 'merge-state.json'));
|
|
59
|
+
const counts = snapshot.doctor.counts.active;
|
|
16
60
|
rows.push({
|
|
17
61
|
key: 'state',
|
|
18
62
|
label: '合并状态',
|
|
19
|
-
status:
|
|
20
|
-
detail:
|
|
63
|
+
status: snapshot.blocked === undefined || !hasState ? 'ok' : 'warn',
|
|
64
|
+
detail: snapshot.blocked === undefined ? 'pairs ' + String(snapshot.rows.length) + ';verify ' + String(counts.verify) + ' / pending ' + String(counts.pending) + ' / fuse ' + String(counts.fuse) + ' / skipped ' + String(counts.skipped) : snapshot.blocked.code + ': ' + snapshot.blocked.message,
|
|
65
|
+
});
|
|
66
|
+
const recoveryRuns = new Set();
|
|
67
|
+
try {
|
|
68
|
+
const raw = await io.readText(join(home, 'merge', 'recovery.json'));
|
|
69
|
+
if (raw !== null)
|
|
70
|
+
for (const report of JSON.parse(raw))
|
|
71
|
+
if (report.ok === true && report.runId !== undefined)
|
|
72
|
+
recoveryRuns.add(report.runId);
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
const receiptRuns = new Set();
|
|
76
|
+
const receiptDir = await probeList(io, join(home, 'merge', 'receipts'));
|
|
77
|
+
if (receiptDir.kind === 'present')
|
|
78
|
+
for (const name of receiptDir.value)
|
|
79
|
+
receiptRuns.add(name);
|
|
80
|
+
const invalid = [];
|
|
81
|
+
if (snapshot.blocked !== undefined)
|
|
82
|
+
invalid.push({ id: 'state', reason: snapshot.blocked.code });
|
|
83
|
+
for (const item of snapshot.rows) {
|
|
84
|
+
const row = item.row;
|
|
85
|
+
const runId = row.runId ?? (item.stage?.ref.split('/')[0]);
|
|
86
|
+
const artifactRow = row.state === 'approved' ? 'approved' : row.state === 'fuse' ? 'fuse' : row.state;
|
|
87
|
+
const run = runId === undefined ? 'none' : row.state === 'pending' ? 'awaiting-approval' : 'running';
|
|
88
|
+
const staging = item.stage === null ? (row.state === 'fuse' ? 'missing' : 'none') : item.stage.state === 'present' || item.stage.state === 'reserved' ? 'kept' : row.state === 'fuse' ? 'missing' : 'none';
|
|
89
|
+
const phase = runId === undefined ? null : await readJournal(io, home, runId);
|
|
90
|
+
const journal = phase === null ? 'none' : phase.phase === 'prepared' ? 'prepared' : phase.phase === 'target-written' || phase.phase === 'sources-archived' ? 'committing' : phase.phase === 'committed' ? 'committed' : phase.phase === 'rolled-back' ? 'rolled-back' : 'failed';
|
|
91
|
+
const evidence = runId !== undefined && await readTextOrNull(io, join(home, 'merge', 'receipts', runId + '.json')) !== null ? 'receipt' : 'none';
|
|
92
|
+
const result = validateArtifacts({ row: artifactRow, run, staging, journal, evidence, activeRuns: run === 'running' || run === 'awaiting-approval' ? 1 : 0, ...(runId !== undefined && recoveryRuns.has(runId) ? { recovery: true } : {}) });
|
|
93
|
+
if (!result.ok)
|
|
94
|
+
invalid.push({ id: row.id, reason: result.reason ?? 'not-allowed' });
|
|
95
|
+
}
|
|
96
|
+
rows.push({ key: 'merge-state-v3', label: 'state schema', status: snapshot.blocked === undefined ? 'ok' : 'warn', detail: snapshot.blocked === undefined ? 'schemaVersion 4' : snapshot.blocked.code });
|
|
97
|
+
let quarantineCount = 0;
|
|
98
|
+
try {
|
|
99
|
+
const qraw = await io.readText(join(home, 'merge', 'merge-state.quarantine.json'));
|
|
100
|
+
if (qraw !== null) {
|
|
101
|
+
const parsed = JSON.parse(qraw);
|
|
102
|
+
quarantineCount = Array.isArray(parsed) ? parsed.length : 0;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
quarantineCount = -1;
|
|
107
|
+
}
|
|
108
|
+
rows.push({ key: 'quarantine', label: 'quarantine', status: quarantineCount < 0 ? 'unknown' : quarantineCount === 0 ? 'ok' : 'warn', detail: quarantineCount < 0 ? 'quarantine 不可读' : quarantineCount + ' 行待人工检查' });
|
|
109
|
+
const holder = await readTreeLock(io, home).catch(() => null);
|
|
110
|
+
rows.push({ key: 'tree-lock', label: 'tree lock', status: holder == null ? 'ok' : 'warn', detail: holder == null ? 'free' : 'holder pid=' + String(holder.pid) + ' epoch=' + String(holder.epoch) + ' token=' + holder.token.slice(0, 8) });
|
|
111
|
+
const backupPath = v2BackupPath(home);
|
|
112
|
+
const backupRaw = await readTextOrNull(io, backupPath);
|
|
113
|
+
const backupMode = await stat(backupPath).then(value => value.mode).catch(() => null);
|
|
114
|
+
const backupReadOnly = backupMode === null ? null : (backupMode & 0o200) === 0;
|
|
115
|
+
rows.push({ key: 'backup', label: 'v2 backup', status: backupRaw === null ? 'unknown' : backupReadOnly === false ? 'warn' : 'ok', detail: backupRaw === null ? 'no v2 backup (not migrated or already cleaned)' : 'merge-state.v2.bak.json sha256=' + createHash('sha256').update(backupRaw).digest('hex').slice(0, 16) + ' readonly=' + String(backupReadOnly) });
|
|
116
|
+
const journal = await readTextOrNull(io, migrationJournalPath(home));
|
|
117
|
+
const report = await readTextOrNull(io, migrationReportPath(home));
|
|
118
|
+
rows.push({ key: 'migration', label: 'state migration', status: snapshot.blocked === undefined ? (journal === null ? 'ok' : 'warn') : 'warn', detail: snapshot.blocked === undefined ? (journal === null ? (report === null ? 'ready (no migration report)' : 'ready; report present') : 'ready; migration journal pending') : snapshot.blocked.code + ': ' + snapshot.blocked.message });
|
|
119
|
+
rows.push({ key: 'staging-index', label: 'staging index', status: 'ok', detail: String(snapshot.stages.length) + ' index entries' });
|
|
120
|
+
const claims = await probeList(io, join(home, 'merge', 'claims'));
|
|
121
|
+
rows.push({ key: 'target-claim', label: 'target claim', status: claims.kind === 'unknown' ? 'unknown' : 'ok', detail: claims.kind === 'unknown' ? 'claims unreadable' : claims.kind === 'absent' ? 'no claims' : String(claims.value.length) + ' claim files' });
|
|
122
|
+
let orphanRuns = 0;
|
|
123
|
+
let runCount = 0;
|
|
124
|
+
try {
|
|
125
|
+
const raw = await io.readText(runsFile(home));
|
|
126
|
+
if (raw !== null) {
|
|
127
|
+
const parsed = JSON.parse(raw);
|
|
128
|
+
runCount = parsed.runs?.length ?? 0;
|
|
129
|
+
for (const run of parsed.runs ?? []) {
|
|
130
|
+
if (run.state !== 'running' || typeof run.pid !== 'number')
|
|
131
|
+
continue;
|
|
132
|
+
try {
|
|
133
|
+
process.kill(run.pid, 0);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
orphanRuns += 1;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
orphanRuns = -1;
|
|
143
|
+
}
|
|
144
|
+
rows.push({ key: 'runs', label: 'run owner', status: orphanRuns < 0 ? 'unknown' : orphanRuns === 0 ? 'ok' : 'warn', detail: orphanRuns < 0 ? 'runs.json unreadable' : String(runCount) + ' runs; ' + String(orphanRuns) + ' dead-owner running' });
|
|
145
|
+
rows.push({ key: 'install', label: 'install form', status: io.exists === undefined ? 'unknown' : 'ok', detail: 'merge plugin loaded' });
|
|
146
|
+
const reconcile = await readReconcileReport(io, home);
|
|
147
|
+
rows.push({
|
|
148
|
+
key: 'reconcile',
|
|
149
|
+
label: '状态收敛',
|
|
150
|
+
status: reconcile === null ? 'unknown' : reconcile.state === 'blocked' ? 'warn' : reconcile.actions.length > 0 ? 'warn' : 'ok',
|
|
151
|
+
detail: reconcile === null
|
|
152
|
+
? '尚无 reconcile 报告(启动或 /evolution merge reconcile 会生成)'
|
|
153
|
+
: reconcile.state === 'blocked'
|
|
154
|
+
? 'blocked:' + (reconcile.code ?? 'merge-state-blocked') + (reconcile.message === undefined ? '' : ' — ' + reconcile.message)
|
|
155
|
+
: String(reconcile.actions.length) + ' 项动作(' + new Date(reconcile.at).toISOString() + ')' + (reconcile.actions.length === 0 ? '' : ':' + reconcile.actions.map(action => action.rowId + '=' + action.code).join(', ')),
|
|
21
156
|
});
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
157
|
+
rows.push({ key: 'consistency', label: '一致性', status: snapshot.blocked !== undefined ? 'blocked' : invalid.length === 0 ? 'ok' : 'warn', detail: snapshot.blocked !== undefined ? 'blocked: ' + snapshot.blocked.code : invalid.length === 0 ? '无非法组合' : String(invalid.length) + ' 行需人工检查: ' + invalid.map(row => row.id + '=' + row.reason).join(', ') });
|
|
158
|
+
const modelRoot = configuredRoot?.trim() ?? '';
|
|
159
|
+
if (modelRoot === '') {
|
|
160
|
+
rows.push({ key: 'models', label: '合并模型', status: 'warn', detail: '未配置 mergeModelsRoot;合并扫描会 fail loud' });
|
|
25
161
|
}
|
|
26
162
|
else {
|
|
27
163
|
const int8 = join(modelRoot, 'potion-multilingual-128M-int8');
|
|
@@ -45,6 +181,24 @@ export async function mergeDoctor(io, home, configuredRoot) {
|
|
|
45
181
|
status: 'ok',
|
|
46
182
|
detail: stagingRoot(home, '<runId>'),
|
|
47
183
|
});
|
|
184
|
+
try {
|
|
185
|
+
const raw = await io.readText(join(home, 'merge', 'recovery.json'));
|
|
186
|
+
if (raw !== null) {
|
|
187
|
+
const reports = JSON.parse(raw);
|
|
188
|
+
const failed = reports.filter(report => report.ok !== true);
|
|
189
|
+
rows.push({
|
|
190
|
+
key: 'recovery',
|
|
191
|
+
label: '提交恢复',
|
|
192
|
+
status: failed.length === 0 ? 'ok' : 'warn',
|
|
193
|
+
detail: failed.length === 0
|
|
194
|
+
? String(reports.length) + ' 条恢复报告,无待处理'
|
|
195
|
+
: String(failed.length) + ' 条待处理:' + failed.map(report => (report.runId ?? '?') + '=' + (report.action ?? '?')).join(', '),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
rows.push({ key: 'recovery', label: '提交恢复', status: 'unknown', detail: '无法读取 merge/recovery.json' });
|
|
201
|
+
}
|
|
48
202
|
return rows;
|
|
49
203
|
}
|
|
50
204
|
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The round-2 JSON envelope a child writes after authoring: which files the merge left
|
|
3
|
+
* behind. Both authoring paths (same-child scan round2 and the new-child approve/resume
|
|
4
|
+
* path) parse it identically, so a declared-but-missing file fails the same way on both.
|
|
5
|
+
*/
|
|
6
|
+
export interface AuthoringManifest {
|
|
7
|
+
readonly target?: string;
|
|
8
|
+
readonly files: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Parse `{"target":..., "files":[...]}` from a child's final turn text.
|
|
12
|
+
* @param text - the child's answer (JSON is expected; prose is tolerated).
|
|
13
|
+
* @returns the manifest, or null when the turn carried no well-formed envelope.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseAuthoringManifest(text: string): AuthoringManifest | null;
|
|
16
|
+
/** Files a manifest promises for a staged tree (declared list, or the tree when undeclared). */
|
|
17
|
+
export declare function manifestFilesOf(manifest: AuthoringManifest | null, stagedPaths: readonly string[]): readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* The first file the manifest promises but the staged tree does not contain.
|
|
20
|
+
* SKILL.md itself is always required, whether or not the manifest lists it.
|
|
21
|
+
* @param files - manifest files (see {@link manifestFilesOf}).
|
|
22
|
+
* @param stagedPaths - support-file paths actually present in the staged tree.
|
|
23
|
+
* @returns the missing path, or null when the declaration is satisfied.
|
|
24
|
+
*/
|
|
25
|
+
export declare function missingManifestFile(files: readonly string[], stagedPaths: readonly string[]): string | null;
|
|
26
|
+
//# sourceMappingURL=authoring-manifest.d.ts.map
|