@open-agent-toolkit/cli 0.2.50 → 0.2.52
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/cli-utilities/bootstrap.md +6 -2
- package/assets/docs/cli-utilities/tool-packs.md +87 -33
- package/assets/docs/provider-sync/commands.md +30 -2
- package/assets/docs/provider-sync/config.md +19 -0
- package/assets/docs/provider-sync/manifest-and-drift.md +37 -0
- package/assets/docs/provider-sync/providers.md +69 -2
- package/assets/docs/provider-sync/scope-and-surface.md +44 -0
- package/assets/docs/reference/cli-reference.md +21 -5
- package/assets/docs/reference/file-locations.md +8 -2
- package/assets/docs/reference/oat-directory-structure.md +25 -9
- package/assets/docs/workflows/projects/artifacts.md +9 -4
- package/assets/docs/workflows/projects/evidence-layers.md +110 -1
- package/assets/docs/workflows/projects/implementation-execution.md +13 -0
- package/assets/docs/workflows/projects/lifecycle.md +17 -5
- package/assets/docs/workflows/projects/orchestration-model.md +18 -0
- package/assets/docs/workflows/projects/picking-up-projects.md +26 -9
- package/assets/docs/workflows/projects/pr-flow.md +5 -3
- package/assets/docs/workflows/projects/reviewing-oat-prs.md +12 -8
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-dispatch-subagents/SKILL.md +57 -1
- package/assets/skills/oat-dispatch-subagents/references/record-schema.md +77 -1
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +3 -3
- package/assets/skills/oat-project-complete/SKILL.md +193 -26
- package/assets/skills/oat-project-complete/scripts/execute-synced-archive-entry.mjs +306 -0
- package/assets/skills/oat-project-complete/scripts/finalize-synced-archive.mjs +125 -0
- package/assets/skills/oat-project-complete/scripts/parse-synced-archive-resume-fields.mjs +106 -0
- package/assets/skills/oat-project-complete/scripts/recover-completion-receipts.mjs +110 -0
- package/assets/skills/oat-project-complete/scripts/resolve-synced-archive-entry.mjs +164 -0
- package/assets/skills/oat-project-dispatch-subagents/SKILL.md +24 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +3 -3
- package/assets/skills/oat-project-implement/references/dispatch-and-dry-run.md +11 -0
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +3 -3
- package/assets/skills/oat-project-plan-writing/SKILL.md +12 -1
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +3 -3
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +3 -3
- package/assets/skills/oat-project-review-provide/SKILL.md +12 -1
- package/assets/skills/oat-project-review-provide-remote/SKILL.md +12 -1
- package/dist/agents/canonical/index.d.ts +2 -0
- package/dist/agents/canonical/index.d.ts.map +1 -1
- package/dist/agents/canonical/index.js +1 -0
- package/dist/agents/canonical/resolve.d.ts +45 -0
- package/dist/agents/canonical/resolve.d.ts.map +1 -0
- package/dist/agents/canonical/resolve.js +175 -0
- package/dist/commands/decision/agents-guidance.d.ts +2 -2
- package/dist/commands/decision/agents-guidance.d.ts.map +1 -1
- package/dist/commands/decision/agents-guidance.js +1 -4
- package/dist/commands/decision/index.d.ts.map +1 -1
- package/dist/commands/decision/index.js +25 -6
- package/dist/commands/docs/init/index.d.ts +7 -1
- package/dist/commands/docs/init/index.d.ts.map +1 -1
- package/dist/commands/docs/init/index.js +53 -20
- package/dist/commands/doctor/index.d.ts +8 -2
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +157 -46
- package/dist/commands/init/index.d.ts +8 -3
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +64 -26
- package/dist/commands/init/tools/index.d.ts +12 -12
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +430 -99
- package/dist/commands/init/tools/install-state.d.ts +2 -0
- package/dist/commands/init/tools/install-state.d.ts.map +1 -1
- package/dist/commands/init/tools/install-state.js +11 -0
- package/dist/commands/init/tools/project-guidance.d.ts +49 -0
- package/dist/commands/init/tools/project-guidance.d.ts.map +1 -0
- package/dist/commands/init/tools/project-guidance.js +144 -0
- package/dist/commands/init/tools/workflows/index.d.ts +7 -0
- package/dist/commands/init/tools/workflows/index.d.ts.map +1 -1
- package/dist/commands/init/tools/workflows/index.js +160 -7
- package/dist/commands/pjm/index.d.ts.map +1 -1
- package/dist/commands/pjm/index.js +28 -3
- package/dist/commands/pjm/init.d.ts +5 -0
- package/dist/commands/pjm/init.d.ts.map +1 -1
- package/dist/commands/pjm/init.js +19 -3
- package/dist/commands/project/archive/archive-utils.d.ts +8 -1
- package/dist/commands/project/archive/archive-utils.d.ts.map +1 -1
- package/dist/commands/project/archive/archive-utils.js +147 -50
- package/dist/commands/project/archive/push-runner.d.ts.map +1 -1
- package/dist/commands/project/archive/push-runner.js +15 -2
- package/dist/commands/project/dispatch/index.d.ts +10 -0
- package/dist/commands/project/dispatch/index.d.ts.map +1 -0
- package/dist/commands/project/dispatch/index.js +137 -0
- package/dist/commands/project/dispatch/record.d.ts +91 -0
- package/dist/commands/project/dispatch/record.d.ts.map +1 -0
- package/dist/commands/project/dispatch/record.js +402 -0
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +2 -0
- package/dist/commands/project/links/compute.d.ts +1 -0
- package/dist/commands/project/links/compute.d.ts.map +1 -1
- package/dist/commands/project/links/compute.js +5 -4
- package/dist/commands/project/links/index.d.ts +9 -0
- package/dist/commands/project/links/index.d.ts.map +1 -1
- package/dist/commands/project/links/index.js +71 -2
- package/dist/commands/project/list.d.ts +10 -2
- package/dist/commands/project/list.d.ts.map +1 -1
- package/dist/commands/project/list.js +205 -29
- package/dist/commands/project/open/index.d.ts +2 -0
- package/dist/commands/project/open/index.d.ts.map +1 -1
- package/dist/commands/project/open/index.js +16 -1
- package/dist/commands/project/prune/index.d.ts +10 -4
- package/dist/commands/project/prune/index.d.ts.map +1 -1
- package/dist/commands/project/prune/index.js +85 -15
- package/dist/commands/project/pull/index.d.ts +4 -0
- package/dist/commands/project/pull/index.d.ts.map +1 -1
- package/dist/commands/project/pull/index.js +82 -3
- package/dist/commands/project/sync/git.d.ts +9 -0
- package/dist/commands/project/sync/git.d.ts.map +1 -1
- package/dist/commands/project/sync/git.js +18 -0
- package/dist/commands/project/sync/ref-sync.d.ts +16 -1
- package/dist/commands/project/sync/ref-sync.d.ts.map +1 -1
- package/dist/commands/project/sync/ref-sync.js +288 -17
- package/dist/commands/project/sync/resolve-target.d.ts +10 -0
- package/dist/commands/project/sync/resolve-target.d.ts.map +1 -1
- package/dist/commands/project/sync/resolve-target.js +56 -1
- package/dist/commands/providers/inspect/inspect.d.ts +8 -1
- package/dist/commands/providers/inspect/inspect.d.ts.map +1 -1
- package/dist/commands/providers/inspect/inspect.js +146 -26
- package/dist/commands/providers/list/list.d.ts +8 -1
- package/dist/commands/providers/list/list.d.ts.map +1 -1
- package/dist/commands/providers/list/list.js +139 -23
- package/dist/commands/providers/providers.types.d.ts +25 -1
- package/dist/commands/providers/providers.types.d.ts.map +1 -1
- package/dist/commands/providers/set/index.d.ts +9 -1
- package/dist/commands/providers/set/index.d.ts.map +1 -1
- package/dist/commands/providers/set/index.js +43 -33
- package/dist/commands/remove/skill/remove-skill.d.ts.map +1 -1
- package/dist/commands/remove/skill/remove-skill.js +11 -0
- package/dist/commands/shared/agents-md.d.ts +42 -14
- package/dist/commands/shared/agents-md.d.ts.map +1 -1
- package/dist/commands/shared/agents-md.js +364 -70
- package/dist/commands/shared/project-scope.d.ts +2 -0
- package/dist/commands/shared/project-scope.d.ts.map +1 -1
- package/dist/commands/shared/project-scope.js +5 -0
- package/dist/commands/state/generate.d.ts +2 -0
- package/dist/commands/state/generate.d.ts.map +1 -1
- package/dist/commands/state/generate.js +93 -28
- package/dist/commands/status/index.d.ts +8 -3
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +147 -32
- package/dist/commands/sync/apply.d.ts.map +1 -1
- package/dist/commands/sync/apply.js +220 -29
- package/dist/commands/sync/dry-run.d.ts.map +1 -1
- package/dist/commands/sync/dry-run.js +30 -6
- package/dist/commands/sync/index.d.ts +17 -2
- package/dist/commands/sync/index.d.ts.map +1 -1
- package/dist/commands/sync/index.js +106 -41
- package/dist/commands/sync/sync.types.d.ts +39 -2
- package/dist/commands/sync/sync.types.d.ts.map +1 -1
- package/dist/commands/sync/sync.utils.d.ts +5 -1
- package/dist/commands/sync/sync.utils.d.ts.map +1 -1
- package/dist/commands/sync/sync.utils.js +73 -0
- package/dist/commands/tools/info/info-tool.d.ts +2 -0
- package/dist/commands/tools/info/info-tool.d.ts.map +1 -1
- package/dist/commands/tools/info/info-tool.js +34 -12
- package/dist/commands/tools/install/index.d.ts.map +1 -1
- package/dist/commands/tools/install/index.js +19 -3
- package/dist/commands/tools/list/list-tools.d.ts +2 -0
- package/dist/commands/tools/list/list-tools.d.ts.map +1 -1
- package/dist/commands/tools/list/list-tools.js +36 -7
- package/dist/commands/tools/remove/index.d.ts.map +1 -1
- package/dist/commands/tools/remove/index.js +150 -21
- package/dist/commands/tools/remove/remove-tools.d.ts +20 -0
- package/dist/commands/tools/remove/remove-tools.d.ts.map +1 -1
- package/dist/commands/tools/remove/remove-tools.js +122 -1
- package/dist/commands/tools/shared/format-pack-inventory.d.ts +19 -0
- package/dist/commands/tools/shared/format-pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/format-pack-inventory.js +109 -0
- package/dist/commands/tools/shared/pack-evidence.d.ts +66 -0
- package/dist/commands/tools/shared/pack-evidence.d.ts.map +1 -0
- package/dist/commands/tools/shared/pack-evidence.js +132 -0
- package/dist/commands/tools/shared/pack-inventory.d.ts +1 -0
- package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts +44 -0
- package/dist/commands/tools/shared/pack-lifecycle-outcome.d.ts.map +1 -0
- package/dist/commands/tools/shared/pack-lifecycle-outcome.js +91 -0
- package/dist/commands/tools/update/index.d.ts.map +1 -1
- package/dist/commands/tools/update/index.js +74 -4
- package/dist/commands/tools/update/update-tools.d.ts +5 -0
- package/dist/commands/tools/update/update-tools.d.ts.map +1 -1
- package/dist/commands/tools/update/update-tools.js +64 -2
- package/dist/config/oat-config.d.ts +5 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +17 -5
- package/dist/config/user-sync-config.d.ts +1 -0
- package/dist/config/user-sync-config.d.ts.map +1 -1
- package/dist/config/user-sync-config.js +8 -0
- package/dist/drift/detector.d.ts +2 -2
- package/dist/drift/detector.d.ts.map +1 -1
- package/dist/drift/detector.js +18 -0
- package/dist/drift/strays.d.ts.map +1 -1
- package/dist/drift/strays.js +5 -0
- package/dist/engine/collection-sync.d.ts +14 -0
- package/dist/engine/collection-sync.d.ts.map +1 -0
- package/dist/engine/collection-sync.js +282 -0
- package/dist/engine/compute-plan.d.ts +8 -1
- package/dist/engine/compute-plan.d.ts.map +1 -1
- package/dist/engine/compute-plan.js +303 -12
- package/dist/engine/engine.types.d.ts +61 -0
- package/dist/engine/engine.types.d.ts.map +1 -1
- package/dist/engine/execute-plan.d.ts +22 -2
- package/dist/engine/execute-plan.d.ts.map +1 -1
- package/dist/engine/execute-plan.js +459 -27
- package/dist/engine/provider-path-safety.d.ts +1 -0
- package/dist/engine/provider-path-safety.d.ts.map +1 -1
- package/dist/engine/provider-path-safety.js +15 -0
- package/dist/engine/scanner.d.ts +2 -2
- package/dist/engine/scanner.d.ts.map +1 -1
- package/dist/engine/scanner.js +21 -4
- package/dist/fs/io.d.ts +67 -0
- package/dist/fs/io.d.ts.map +1 -1
- package/dist/fs/io.js +536 -3
- package/dist/manifest/index.d.ts +2 -2
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest/index.js +1 -1
- package/dist/manifest/manager.d.ts +2 -2
- package/dist/manifest/manager.d.ts.map +1 -1
- package/dist/manifest/manager.js +20 -3
- package/dist/manifest/manifest.types.d.ts +704 -13
- package/dist/manifest/manifest.types.d.ts.map +1 -1
- package/dist/manifest/manifest.types.js +131 -15
- package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/codex/codec/sync-extension.js +41 -10
- package/dist/providers/cursor/codec/sync-extension.d.ts.map +1 -1
- package/dist/providers/cursor/codec/sync-extension.js +41 -10
- package/dist/providers/identity/absolute-paths.d.ts +68 -0
- package/dist/providers/identity/absolute-paths.d.ts.map +1 -0
- package/dist/providers/identity/absolute-paths.js +144 -0
- package/dist/providers/identity/claude-runtime-observation.d.ts +105 -0
- package/dist/providers/identity/claude-runtime-observation.d.ts.map +1 -0
- package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts +40 -0
- package/dist/providers/identity/claude-runtime-observation.fixtures.d.ts.map +1 -0
- package/dist/providers/identity/claude-runtime-observation.fixtures.js +146 -0
- package/dist/providers/identity/claude-runtime-observation.js +229 -0
- package/dist/providers/identity/codex-runtime-observation.d.ts +75 -0
- package/dist/providers/identity/codex-runtime-observation.d.ts.map +1 -0
- package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts +37 -0
- package/dist/providers/identity/codex-runtime-observation.fixtures.d.ts.map +1 -0
- package/dist/providers/identity/codex-runtime-observation.fixtures.js +539 -0
- package/dist/providers/identity/codex-runtime-observation.js +255 -0
- package/dist/providers/identity/generic-dispatch-record.d.ts +313 -0
- package/dist/providers/identity/generic-dispatch-record.d.ts.map +1 -0
- package/dist/providers/identity/generic-dispatch-record.js +539 -0
- package/dist/providers/identity/oat-dispatch-record.d.ts +1964 -0
- package/dist/providers/identity/oat-dispatch-record.d.ts.map +1 -0
- package/dist/providers/identity/oat-dispatch-record.js +691 -0
- package/dist/providers/identity/runtime-observation.d.ts +100 -0
- package/dist/providers/identity/runtime-observation.d.ts.map +1 -0
- package/dist/providers/identity/runtime-observation.js +169 -0
- package/dist/providers/shared/index.d.ts +2 -0
- package/dist/providers/shared/index.d.ts.map +1 -1
- package/dist/providers/shared/index.js +1 -0
- package/dist/providers/shared/materialization-extension.d.ts +12 -0
- package/dist/providers/shared/materialization-extension.d.ts.map +1 -1
- package/dist/providers/shared/registry.d.ts +69 -0
- package/dist/providers/shared/registry.d.ts.map +1 -0
- package/dist/providers/shared/registry.js +252 -0
- package/dist/providers/shared/restart-adviser.d.ts +26 -0
- package/dist/providers/shared/restart-adviser.d.ts.map +1 -0
- package/dist/providers/shared/restart-adviser.js +96 -0
- package/dist/shared/types.d.ts +4 -0
- package/dist/shared/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { isAbsolute, posix, relative, sep } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
|
|
7
|
+
import { validateSyncedArchiveTerminalReport } from './finalize-synced-archive.mjs';
|
|
8
|
+
import { recoverArchivedRecapEvidenceReceipt } from './recover-completion-receipts.mjs';
|
|
9
|
+
import { resolveSyncedArchiveEntry } from './resolve-synced-archive-entry.mjs';
|
|
10
|
+
|
|
11
|
+
const execFile = promisify(execFileCallback);
|
|
12
|
+
|
|
13
|
+
function executionError(message) {
|
|
14
|
+
const error = new Error(message);
|
|
15
|
+
error.code = 'E_SYNCED_ARCHIVE_EXECUTION';
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function buildPostArchiveContinuation(
|
|
20
|
+
archiveReport,
|
|
21
|
+
projectPath,
|
|
22
|
+
repoRoot,
|
|
23
|
+
recoverArchiveEvidence,
|
|
24
|
+
) {
|
|
25
|
+
let selectedProjectRecapRun = '';
|
|
26
|
+
let exportedManifestPath = '';
|
|
27
|
+
let exportedBuildRecordPath = '';
|
|
28
|
+
let evidenceCommit = '';
|
|
29
|
+
let evidencePushRequired = false;
|
|
30
|
+
if (archiveReport.projectRecapExport != null) {
|
|
31
|
+
const sourceRunRoot = archiveReport.projectRecapExport?.sourceRunRoot;
|
|
32
|
+
const exportRoot = archiveReport.projectRecapExport?.exportRoot;
|
|
33
|
+
const manifestPath =
|
|
34
|
+
archiveReport.projectRecapExport?.manifest?.relativePath;
|
|
35
|
+
if (
|
|
36
|
+
typeof sourceRunRoot !== 'string' ||
|
|
37
|
+
typeof exportRoot !== 'string' ||
|
|
38
|
+
manifestPath !== 'manifest.json'
|
|
39
|
+
) {
|
|
40
|
+
throw executionError(
|
|
41
|
+
'Archive resume report has an invalid project recap export receipt.',
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
selectedProjectRecapRun = relative(projectPath, sourceRunRoot)
|
|
45
|
+
.split(sep)
|
|
46
|
+
.join('/');
|
|
47
|
+
if (
|
|
48
|
+
selectedProjectRecapRun.length === 0 ||
|
|
49
|
+
/^\.\.(?:[/\\]|$)/.test(selectedProjectRecapRun) ||
|
|
50
|
+
isAbsolute(selectedProjectRecapRun)
|
|
51
|
+
) {
|
|
52
|
+
throw executionError(
|
|
53
|
+
'Archive resume recap source is outside the original project path.',
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
const exportRootRelative = relative(repoRoot, exportRoot)
|
|
57
|
+
.split(sep)
|
|
58
|
+
.join('/');
|
|
59
|
+
if (
|
|
60
|
+
exportRootRelative.length === 0 ||
|
|
61
|
+
/^\.\.(?:[/\\]|$)/.test(exportRootRelative) ||
|
|
62
|
+
isAbsolute(exportRootRelative)
|
|
63
|
+
) {
|
|
64
|
+
throw executionError(
|
|
65
|
+
'Archive resume recap export is outside the repository root.',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
exportedManifestPath = posix.join(exportRootRelative, manifestPath);
|
|
69
|
+
exportedBuildRecordPath = posix.join(
|
|
70
|
+
exportRootRelative,
|
|
71
|
+
'build-record.json',
|
|
72
|
+
);
|
|
73
|
+
const recoveredEvidence = await recoverArchiveEvidence({
|
|
74
|
+
repoRoot,
|
|
75
|
+
lifecycleCommit: archiveReport.lifecycleCommit,
|
|
76
|
+
evidencePaths: [exportedManifestPath, exportedBuildRecordPath],
|
|
77
|
+
});
|
|
78
|
+
if (recoveredEvidence.evidenceCommit !== null) {
|
|
79
|
+
evidenceCommit = recoveredEvidence.evidenceCommit;
|
|
80
|
+
evidencePushRequired = recoveredEvidence.evidencePushRequired;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
required: true,
|
|
85
|
+
rejoinStep: '8.5',
|
|
86
|
+
archivePath: archiveReport.archivePath,
|
|
87
|
+
summaryExportFile: archiveReport.summaryExportFile ?? '',
|
|
88
|
+
lifecycleCommit: archiveReport.lifecycleCommit,
|
|
89
|
+
s3Path: archiveReport.s3Path ?? '',
|
|
90
|
+
selectedProjectRecapRun,
|
|
91
|
+
projectRecapExport: archiveReport.projectRecapExport ?? null,
|
|
92
|
+
exportedManifestPath,
|
|
93
|
+
exportedBuildRecordPath,
|
|
94
|
+
evidenceCommit,
|
|
95
|
+
evidencePushRequired,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function continueSyncedArchiveCompletion({
|
|
100
|
+
executionResult,
|
|
101
|
+
finalizeLinks,
|
|
102
|
+
refreshDashboard,
|
|
103
|
+
attestRecap,
|
|
104
|
+
pushBookkeeping,
|
|
105
|
+
closeoutPr,
|
|
106
|
+
clearPointer,
|
|
107
|
+
confirmCompletion,
|
|
108
|
+
}) {
|
|
109
|
+
if (
|
|
110
|
+
executionResult?.route !== 'archive-resumed' ||
|
|
111
|
+
executionResult?.terminal !== true ||
|
|
112
|
+
executionResult?.terminalReceiptValidated !== true ||
|
|
113
|
+
executionResult?.continuation?.required !== true
|
|
114
|
+
) {
|
|
115
|
+
throw executionError(
|
|
116
|
+
'Post-archive continuation requires a finalized archive-resume result.',
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
await finalizeLinks(executionResult.continuation);
|
|
120
|
+
await refreshDashboard(executionResult.continuation);
|
|
121
|
+
if (
|
|
122
|
+
executionResult.continuation.projectRecapExport !== null &&
|
|
123
|
+
executionResult.continuation.evidenceCommit === ''
|
|
124
|
+
) {
|
|
125
|
+
await attestRecap(executionResult.continuation);
|
|
126
|
+
}
|
|
127
|
+
await pushBookkeeping(executionResult.continuation);
|
|
128
|
+
await closeoutPr(executionResult.continuation);
|
|
129
|
+
await clearPointer(executionResult.archiveReport);
|
|
130
|
+
await confirmCompletion(executionResult.continuation);
|
|
131
|
+
return {
|
|
132
|
+
status: 'ok',
|
|
133
|
+
route: 'completion-confirmed',
|
|
134
|
+
lifecycleCommit: executionResult.continuation.lifecycleCommit,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function executeSyncedArchiveEntry({
|
|
139
|
+
record,
|
|
140
|
+
projectName,
|
|
141
|
+
projectPath,
|
|
142
|
+
repoRoot,
|
|
143
|
+
probeRefs,
|
|
144
|
+
pullProject,
|
|
145
|
+
runActiveWorkflowSteps,
|
|
146
|
+
archiveProject,
|
|
147
|
+
validateArchive,
|
|
148
|
+
recoverArchiveEvidence = recoverArchivedRecapEvidenceReceipt,
|
|
149
|
+
}) {
|
|
150
|
+
const entry = record
|
|
151
|
+
? await resolveSyncedArchiveEntry({
|
|
152
|
+
record,
|
|
153
|
+
projectName,
|
|
154
|
+
repoRoot,
|
|
155
|
+
...(probeRefs ? { probeRefs } : {}),
|
|
156
|
+
})
|
|
157
|
+
: {
|
|
158
|
+
status: 'ok',
|
|
159
|
+
route: 'archive-resume',
|
|
160
|
+
terminal: true,
|
|
161
|
+
archiveSnapshot: null,
|
|
162
|
+
verifiedSourceSha: null,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (entry.route === 'pull') {
|
|
166
|
+
await pullProject(projectPath);
|
|
167
|
+
await runActiveWorkflowSteps();
|
|
168
|
+
return {
|
|
169
|
+
status: 'ok',
|
|
170
|
+
route: 'continue-active',
|
|
171
|
+
terminal: false,
|
|
172
|
+
skippedActiveSteps: false,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const archiveReport = await archiveProject(projectPath, {
|
|
177
|
+
...(entry.archiveSnapshot
|
|
178
|
+
? {
|
|
179
|
+
archiveSnapshot: entry.archiveSnapshot,
|
|
180
|
+
verifiedSourceSha: entry.verifiedSourceSha,
|
|
181
|
+
}
|
|
182
|
+
: { recordless: true }),
|
|
183
|
+
});
|
|
184
|
+
if (entry.archiveSnapshot) {
|
|
185
|
+
if (
|
|
186
|
+
archiveReport?.snapshotId !== entry.archiveSnapshot ||
|
|
187
|
+
archiveReport?.verifiedSourceSha !== entry.verifiedSourceSha
|
|
188
|
+
) {
|
|
189
|
+
throw executionError(
|
|
190
|
+
'Archive resume report does not match the persisted snapshot identity.',
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
} else if (
|
|
194
|
+
typeof archiveReport?.snapshotId !== 'string' ||
|
|
195
|
+
archiveReport.snapshotId.length === 0
|
|
196
|
+
) {
|
|
197
|
+
throw executionError(
|
|
198
|
+
'Recordless archive resume report has no persisted snapshot identity.',
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
await validateArchive({
|
|
202
|
+
archiveReport,
|
|
203
|
+
projectName,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
status: 'ok',
|
|
207
|
+
route: 'archive-resumed',
|
|
208
|
+
terminal: true,
|
|
209
|
+
skippedActiveSteps: true,
|
|
210
|
+
archiveSnapshot: archiveReport.snapshotId,
|
|
211
|
+
archiveReport,
|
|
212
|
+
terminalReceiptValidated: true,
|
|
213
|
+
continuation: await buildPostArchiveContinuation(
|
|
214
|
+
archiveReport,
|
|
215
|
+
projectPath,
|
|
216
|
+
repoRoot,
|
|
217
|
+
recoverArchiveEvidence,
|
|
218
|
+
),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parseArguments(argv) {
|
|
223
|
+
const result = {};
|
|
224
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
225
|
+
const flag = argv[index];
|
|
226
|
+
const value = argv[index + 1];
|
|
227
|
+
if (value === undefined) {
|
|
228
|
+
throw executionError(`Missing value for ${flag ?? 'argument'}.`);
|
|
229
|
+
}
|
|
230
|
+
if (flag === '--repo-root') result.repoRoot = value;
|
|
231
|
+
else if (flag === '--record-path') result.recordPath = value;
|
|
232
|
+
else if (flag === '--project-name') result.projectName = value;
|
|
233
|
+
else if (flag === '--project-path') result.projectPath = value;
|
|
234
|
+
else throw executionError(`Unsupported argument: ${flag}.`);
|
|
235
|
+
}
|
|
236
|
+
for (const field of [
|
|
237
|
+
'repoRoot',
|
|
238
|
+
'recordPath',
|
|
239
|
+
'projectName',
|
|
240
|
+
'projectPath',
|
|
241
|
+
]) {
|
|
242
|
+
if (!result[field]) throw executionError(`Missing required ${field}.`);
|
|
243
|
+
}
|
|
244
|
+
return result;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function readRecord(recordPath) {
|
|
248
|
+
try {
|
|
249
|
+
return JSON.parse(await readFile(recordPath, 'utf8'));
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error?.code === 'ENOENT') return null;
|
|
252
|
+
throw executionError(
|
|
253
|
+
`Unable to read synced discovery record: ${error.message}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function runOat(args, cwd) {
|
|
259
|
+
try {
|
|
260
|
+
return await execFile('oat', args, { cwd });
|
|
261
|
+
} catch (error) {
|
|
262
|
+
throw executionError(`oat ${args.join(' ')} failed: ${error.message}`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function main(argv) {
|
|
267
|
+
const options = parseArguments(argv);
|
|
268
|
+
const record = await readRecord(options.recordPath);
|
|
269
|
+
return executeSyncedArchiveEntry({
|
|
270
|
+
...options,
|
|
271
|
+
record,
|
|
272
|
+
pullProject: async (projectPath) => {
|
|
273
|
+
await runOat(['project', 'pull', projectPath], options.repoRoot);
|
|
274
|
+
},
|
|
275
|
+
runActiveWorkflowSteps: async () => undefined,
|
|
276
|
+
archiveProject: async (projectPath) => {
|
|
277
|
+
const { stdout } = await runOat(
|
|
278
|
+
['project', 'archive', projectPath, '--json'],
|
|
279
|
+
options.repoRoot,
|
|
280
|
+
);
|
|
281
|
+
try {
|
|
282
|
+
return JSON.parse(stdout);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw executionError(
|
|
285
|
+
`Unable to parse synced archive report: ${error.message}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
validateArchive: async ({ archiveReport, projectName }) =>
|
|
290
|
+
validateSyncedArchiveTerminalReport(archiveReport, projectName),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (
|
|
295
|
+
process.argv[1] &&
|
|
296
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
297
|
+
) {
|
|
298
|
+
main(process.argv.slice(2))
|
|
299
|
+
.then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
|
|
300
|
+
.catch((error) => {
|
|
301
|
+
process.stderr.write(
|
|
302
|
+
`${JSON.stringify({ ok: false, code: error.code, message: error.message })}\n`,
|
|
303
|
+
);
|
|
304
|
+
process.exitCode = 1;
|
|
305
|
+
});
|
|
306
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const execFile = promisify(execFileCallback);
|
|
7
|
+
|
|
8
|
+
function finalizationError(message) {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.code = 'E_SYNCED_ARCHIVE_FINALIZATION';
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function requireFullSha(value, field) {
|
|
15
|
+
if (typeof value !== 'string' || !/^[0-9a-f]{40}$/.test(value)) {
|
|
16
|
+
throw finalizationError(
|
|
17
|
+
`Synced archive terminal report requires a full ${field} SHA.`,
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validateSyncedArchiveTerminalReport(report, projectName) {
|
|
23
|
+
if (!report || typeof report !== 'object' || Array.isArray(report)) {
|
|
24
|
+
throw finalizationError(
|
|
25
|
+
'Synced archive finalization requires a structured terminal report.',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
if (
|
|
29
|
+
report.status !== 'ok' ||
|
|
30
|
+
report.mode !== 'apply' ||
|
|
31
|
+
typeof report.archivePath !== 'string' ||
|
|
32
|
+
report.archivePath.length === 0
|
|
33
|
+
) {
|
|
34
|
+
throw finalizationError(
|
|
35
|
+
'Synced archive terminal report is not a successful durable apply result.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
requireFullSha(report.lifecycleCommit, 'lifecycleCommit');
|
|
39
|
+
requireFullSha(report.verifiedSourceSha, 'verifiedSourceSha');
|
|
40
|
+
if (report.completedRef !== `refs/oat/completed/${projectName}`) {
|
|
41
|
+
throw finalizationError(
|
|
42
|
+
'Synced archive terminal report names an unexpected completed ref.',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (!['removed', 'retained'].includes(report.activeAliasDisposition)) {
|
|
46
|
+
throw finalizationError(
|
|
47
|
+
'Synced archive terminal report has no verified active-alias disposition.',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (report.recordRetired !== true) {
|
|
51
|
+
throw finalizationError(
|
|
52
|
+
'Synced archive terminal report does not verify discovery-record retirement.',
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
return report;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function finalizeSyncedArchive({
|
|
59
|
+
getArchiveReport,
|
|
60
|
+
projectName,
|
|
61
|
+
clearActiveProject,
|
|
62
|
+
}) {
|
|
63
|
+
const report = validateSyncedArchiveTerminalReport(
|
|
64
|
+
await getArchiveReport(),
|
|
65
|
+
projectName,
|
|
66
|
+
);
|
|
67
|
+
await clearActiveProject();
|
|
68
|
+
return {
|
|
69
|
+
status: 'ok',
|
|
70
|
+
pointerCleared: true,
|
|
71
|
+
lifecycleCommit: report.lifecycleCommit,
|
|
72
|
+
completedRef: report.completedRef,
|
|
73
|
+
verifiedSourceSha: report.verifiedSourceSha,
|
|
74
|
+
activeAliasDisposition: report.activeAliasDisposition,
|
|
75
|
+
recordRetired: report.recordRetired,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseArguments(argv) {
|
|
80
|
+
if (argv.length !== 2 || argv[0] !== '--project-name' || !argv[1]) {
|
|
81
|
+
throw finalizationError(
|
|
82
|
+
'Usage: finalize-synced-archive.mjs --project-name <name>',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return { projectName: argv[1] };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main(argv) {
|
|
89
|
+
const { projectName } = parseArguments(argv);
|
|
90
|
+
return finalizeSyncedArchive({
|
|
91
|
+
projectName,
|
|
92
|
+
getArchiveReport: async () => {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(await readFile(0, 'utf8'));
|
|
95
|
+
} catch (error) {
|
|
96
|
+
throw finalizationError(
|
|
97
|
+
`Unable to parse synced archive terminal report: ${error.message}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
clearActiveProject: async () => {
|
|
102
|
+
try {
|
|
103
|
+
await execFile('oat', ['config', 'set', 'activeProject', '']);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw finalizationError(
|
|
106
|
+
`Unable to clear the active project pointer: ${error.message}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
process.argv[1] &&
|
|
115
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
116
|
+
) {
|
|
117
|
+
main(process.argv.slice(2))
|
|
118
|
+
.then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
|
|
119
|
+
.catch((error) => {
|
|
120
|
+
process.stderr.write(
|
|
121
|
+
`${JSON.stringify({ ok: false, code: error.code, message: error.message })}\n`,
|
|
122
|
+
);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
|
|
3
|
+
function fieldsError(message) {
|
|
4
|
+
const error = new Error(message);
|
|
5
|
+
error.code = 'E_SYNCED_ARCHIVE_RESUME_FIELDS';
|
|
6
|
+
return error;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function shellQuote(value) {
|
|
10
|
+
return `'${String(value).split("'").join("'\\''")}'`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseSyncedArchiveResumeFields(result) {
|
|
14
|
+
const continuation = result?.continuation;
|
|
15
|
+
const hasRecapExport = continuation?.projectRecapExport !== null;
|
|
16
|
+
if (
|
|
17
|
+
result?.status !== 'ok' ||
|
|
18
|
+
result?.route !== 'archive-resumed' ||
|
|
19
|
+
result?.terminal !== true ||
|
|
20
|
+
result?.skippedActiveSteps !== true ||
|
|
21
|
+
result?.terminalReceiptValidated !== true ||
|
|
22
|
+
continuation?.required !== true ||
|
|
23
|
+
continuation?.rejoinStep !== '8.5' ||
|
|
24
|
+
typeof continuation.archivePath !== 'string' ||
|
|
25
|
+
continuation.archivePath.length === 0 ||
|
|
26
|
+
typeof continuation.lifecycleCommit !== 'string' ||
|
|
27
|
+
!/^[0-9a-f]{40}$/.test(continuation.lifecycleCommit) ||
|
|
28
|
+
typeof continuation.evidenceCommit !== 'string' ||
|
|
29
|
+
(continuation.evidenceCommit !== '' &&
|
|
30
|
+
!/^[0-9a-f]{40}$/.test(continuation.evidenceCommit)) ||
|
|
31
|
+
typeof continuation.evidencePushRequired !== 'boolean' ||
|
|
32
|
+
(continuation.evidenceCommit === '' &&
|
|
33
|
+
continuation.evidencePushRequired !== false) ||
|
|
34
|
+
(hasRecapExport &&
|
|
35
|
+
(typeof continuation.exportedManifestPath !== 'string' ||
|
|
36
|
+
continuation.exportedManifestPath.length === 0 ||
|
|
37
|
+
typeof continuation.exportedBuildRecordPath !== 'string' ||
|
|
38
|
+
continuation.exportedBuildRecordPath.length === 0)) ||
|
|
39
|
+
(!hasRecapExport &&
|
|
40
|
+
(continuation.exportedManifestPath !== '' ||
|
|
41
|
+
continuation.exportedBuildRecordPath !== '' ||
|
|
42
|
+
continuation.evidenceCommit !== ''))
|
|
43
|
+
) {
|
|
44
|
+
throw fieldsError(
|
|
45
|
+
'Synced archive resume result has no verified post-archive continuation.',
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
SYNCED_ARCHIVE_RESUME: 'true',
|
|
50
|
+
SHOULD_ARCHIVE: 'true',
|
|
51
|
+
IS_DURABLE_PROJECT: 'true',
|
|
52
|
+
ARCHIVE_OUTPUT: JSON.stringify(result.archiveReport),
|
|
53
|
+
ARCHIVE_PATH: continuation.archivePath,
|
|
54
|
+
PROJECT_PATH: continuation.archivePath,
|
|
55
|
+
SUMMARY_EXPORT_FILE: continuation.summaryExportFile ?? '',
|
|
56
|
+
LIFECYCLE_COMMIT: continuation.lifecycleCommit,
|
|
57
|
+
ARCHIVE_S3_PATH: continuation.s3Path ?? '',
|
|
58
|
+
SELECTED_PROJECT_RECAP_RUN: continuation.selectedProjectRecapRun ?? '',
|
|
59
|
+
PROJECT_RECAP_EXPORT_JSON: JSON.stringify(
|
|
60
|
+
continuation.projectRecapExport ?? null,
|
|
61
|
+
),
|
|
62
|
+
EVIDENCE_COMMIT: continuation.evidenceCommit,
|
|
63
|
+
EVIDENCE_PUSH_REQUIRED: String(continuation.evidencePushRequired),
|
|
64
|
+
EXPORTED_MANIFEST_PATH: continuation.exportedManifestPath,
|
|
65
|
+
EXPORTED_BUILD_RECORD_PATH: continuation.exportedBuildRecordPath,
|
|
66
|
+
PROJECT_REF_COMMIT: '',
|
|
67
|
+
SHOULD_OPEN_PR: 'false',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatShellAssignments(fields) {
|
|
72
|
+
return Object.entries(fields)
|
|
73
|
+
.map(([name, value]) => `${name}=${shellQuote(value)}`)
|
|
74
|
+
.join('\n');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function main(argv) {
|
|
78
|
+
if (argv.length !== 1) {
|
|
79
|
+
throw fieldsError(
|
|
80
|
+
'Usage: parse-synced-archive-resume-fields.mjs <executor-json>',
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
let result;
|
|
84
|
+
try {
|
|
85
|
+
result = JSON.parse(argv[0]);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw fieldsError(
|
|
88
|
+
`Unable to parse archive resume result: ${error.message}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
return formatShellAssignments(parseSyncedArchiveResumeFields(result));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (
|
|
95
|
+
process.argv[1] &&
|
|
96
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
97
|
+
) {
|
|
98
|
+
try {
|
|
99
|
+
process.stdout.write(`${main(process.argv.slice(2))}\n`);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
process.stderr.write(
|
|
102
|
+
`${JSON.stringify({ ok: false, code: error.code, message: error.message })}\n`,
|
|
103
|
+
);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -91,6 +91,23 @@ async function changedPaths(projectPath, commit) {
|
|
|
91
91
|
return output === '' ? [] : output.split('\n').sort();
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
async function isAncestor(projectPath, ancestor, descendant) {
|
|
95
|
+
try {
|
|
96
|
+
await execFileAsync(
|
|
97
|
+
'git',
|
|
98
|
+
['merge-base', '--is-ancestor', ancestor, descendant],
|
|
99
|
+
{ cwd: projectPath, encoding: 'utf8' },
|
|
100
|
+
);
|
|
101
|
+
return true;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error?.code === 1) return false;
|
|
104
|
+
const detail = error?.stderr?.trim() || error?.message || String(error);
|
|
105
|
+
throw completionReceiptError(
|
|
106
|
+
`git merge-base failed while recovering completion receipts: ${detail}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
94
111
|
async function committedFile(projectPath, commit, path, label) {
|
|
95
112
|
const content = await git(projectPath, ['show', `${commit}:${path}`], {
|
|
96
113
|
allowFailure: true,
|
|
@@ -698,6 +715,99 @@ export async function recoverCompletionReceipts({
|
|
|
698
715
|
};
|
|
699
716
|
}
|
|
700
717
|
|
|
718
|
+
export async function recoverArchivedRecapEvidenceReceipt({
|
|
719
|
+
repoRoot,
|
|
720
|
+
lifecycleCommit,
|
|
721
|
+
evidencePaths,
|
|
722
|
+
}) {
|
|
723
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
724
|
+
throw completionReceiptError(
|
|
725
|
+
'Archived recap evidence recovery requires the repository root.',
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
if (!FULL_SHA.test(lifecycleCommit ?? '')) {
|
|
729
|
+
throw completionReceiptError(
|
|
730
|
+
'Archived recap evidence recovery requires a full lifecycle commit SHA.',
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
if (!Array.isArray(evidencePaths) || evidencePaths.length !== 2) {
|
|
734
|
+
throw completionReceiptError(
|
|
735
|
+
'Archived recap evidence recovery requires exactly two evidence paths.',
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
const exactEvidencePaths = evidencePaths.map((path, index) =>
|
|
739
|
+
requireRelativeGitPath(path, `Archived evidence path ${index + 1}`),
|
|
740
|
+
);
|
|
741
|
+
if (new Set(exactEvidencePaths).size !== exactEvidencePaths.length) {
|
|
742
|
+
throw completionReceiptError('Archived evidence paths must be unique.');
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const resolvedLifecycle = await git(repoRoot, [
|
|
746
|
+
'rev-parse',
|
|
747
|
+
'--verify',
|
|
748
|
+
`${lifecycleCommit}^{commit}`,
|
|
749
|
+
]);
|
|
750
|
+
if (resolvedLifecycle !== lifecycleCommit) {
|
|
751
|
+
throw completionReceiptError(
|
|
752
|
+
'Archived recap lifecycle receipt does not resolve to the exact commit.',
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
const head = await git(repoRoot, ['rev-parse', 'HEAD']);
|
|
756
|
+
if (head === lifecycleCommit) {
|
|
757
|
+
return {
|
|
758
|
+
status: 'none',
|
|
759
|
+
evidenceCommit: null,
|
|
760
|
+
evidencePushRequired: false,
|
|
761
|
+
evidencePaths: exactEvidencePaths,
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
if ((await commitSubject(repoRoot, head)) !== EVIDENCE_MESSAGE) {
|
|
765
|
+
throw completionReceiptError(
|
|
766
|
+
'Archived recap retry HEAD is neither the lifecycle commit nor the exact evidence commit.',
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
if (
|
|
770
|
+
(await singleParent(repoRoot, head, 'Archived recap evidence commit')) !==
|
|
771
|
+
lifecycleCommit
|
|
772
|
+
) {
|
|
773
|
+
throw completionReceiptError(
|
|
774
|
+
'Archived recap evidence commit must be the immediate child of the lifecycle commit.',
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
requireExactPaths(
|
|
778
|
+
await changedPaths(repoRoot, head),
|
|
779
|
+
exactEvidencePaths,
|
|
780
|
+
'Archived recap evidence commit',
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
const upstream = await git(
|
|
784
|
+
repoRoot,
|
|
785
|
+
['rev-parse', '--verify', '@{u}^{commit}'],
|
|
786
|
+
{ allowFailure: true },
|
|
787
|
+
);
|
|
788
|
+
let evidencePushRequired;
|
|
789
|
+
if (upstream === head) {
|
|
790
|
+
evidencePushRequired = false;
|
|
791
|
+
} else if (
|
|
792
|
+
upstream === null ||
|
|
793
|
+
(FULL_SHA.test(upstream) &&
|
|
794
|
+
(await isAncestor(repoRoot, upstream, lifecycleCommit)))
|
|
795
|
+
) {
|
|
796
|
+
evidencePushRequired = true;
|
|
797
|
+
} else {
|
|
798
|
+
throw completionReceiptError(
|
|
799
|
+
'Archived recap evidence retry requires upstream to equal the evidence commit or remain an ancestor of its lifecycle parent.',
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
status: 'recovered',
|
|
805
|
+
evidenceCommit: head,
|
|
806
|
+
evidencePushRequired,
|
|
807
|
+
evidencePaths: exactEvidencePaths,
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
|
|
701
811
|
function parseBoolean(value, label) {
|
|
702
812
|
if (value === 'true') return true;
|
|
703
813
|
if (value === 'false') return false;
|