@open-agent-toolkit/cli 0.2.49 → 0.2.51
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/tool-packs.md +35 -24
- package/assets/docs/reference/cli-reference.md +20 -5
- package/assets/docs/reference/file-locations.md +8 -2
- package/assets/docs/reference/oat-directory-structure.md +14 -5
- package/assets/docs/reference/troubleshooting.md +22 -4
- package/assets/docs/workflows/projects/artifacts.md +9 -4
- package/assets/docs/workflows/projects/lifecycle.md +17 -5
- 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-project-autonomous/references/gate-inventory.md +2 -2
- 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-document/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +2 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +2 -2
- package/dist/commands/doctor/index.d.ts +7 -0
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +44 -23
- package/dist/commands/pjm/index.d.ts +0 -2
- package/dist/commands/pjm/index.d.ts.map +1 -1
- package/dist/commands/pjm/index.js +5 -4
- package/dist/commands/pjm/migrate.d.ts +2 -1
- package/dist/commands/pjm/migrate.d.ts.map +1 -1
- package/dist/commands/pjm/migrate.js +23 -17
- 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/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/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 +3 -1
- package/dist/commands/status/index.d.ts.map +1 -1
- package/dist/commands/status/index.js +77 -10
- package/dist/commands/tools/shared/pack-inventory.d.ts +10 -0
- package/dist/commands/tools/shared/pack-inventory.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-inventory.js +71 -21
- package/package.json +2 -2
|
@@ -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;
|
|
@@ -0,0 +1,164 @@
|
|
|
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 entryError(message) {
|
|
9
|
+
const error = new Error(message);
|
|
10
|
+
error.code = 'E_SYNCED_ARCHIVE_ENTRY';
|
|
11
|
+
return error;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseArguments(argv) {
|
|
15
|
+
const result = {};
|
|
16
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
17
|
+
const flag = argv[index];
|
|
18
|
+
const value = argv[index + 1];
|
|
19
|
+
if (value === undefined) {
|
|
20
|
+
throw entryError(`Missing value for ${flag ?? 'argument'}.`);
|
|
21
|
+
}
|
|
22
|
+
if (flag === '--repo-root') result.repoRoot = value;
|
|
23
|
+
else if (flag === '--record-path') result.recordPath = value;
|
|
24
|
+
else if (flag === '--project-name') result.projectName = value;
|
|
25
|
+
else throw entryError(`Unsupported argument: ${flag}.`);
|
|
26
|
+
}
|
|
27
|
+
for (const field of ['repoRoot', 'recordPath', 'projectName']) {
|
|
28
|
+
if (!result[field]) throw entryError(`Missing required ${field}.`);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseAdvertisedRefs(stdout, requestedRefs) {
|
|
34
|
+
const refs = new Map();
|
|
35
|
+
for (const row of stdout.split('\n').filter(Boolean)) {
|
|
36
|
+
const fields = row.trim().split(/\s+/);
|
|
37
|
+
if (
|
|
38
|
+
fields.length !== 2 ||
|
|
39
|
+
!/^[0-9a-f]{40}$/i.test(fields[0]) ||
|
|
40
|
+
!requestedRefs.has(fields[1]) ||
|
|
41
|
+
refs.has(fields[1])
|
|
42
|
+
) {
|
|
43
|
+
throw entryError(
|
|
44
|
+
'Unable to validate synced terminal refs: malformed Git advertisement.',
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
refs.set(fields[1], fields[0]);
|
|
48
|
+
}
|
|
49
|
+
return refs;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function probeRemoteRefs({ repoRoot, remote, activeRef, completedRef }) {
|
|
53
|
+
let stdout;
|
|
54
|
+
try {
|
|
55
|
+
({ stdout } = await execFile(
|
|
56
|
+
'git',
|
|
57
|
+
['ls-remote', remote, activeRef, completedRef],
|
|
58
|
+
{ cwd: repoRoot },
|
|
59
|
+
));
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw entryError(
|
|
62
|
+
`Unable to validate synced terminal refs from ${remote}: ${error.message}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const refs = parseAdvertisedRefs(stdout, new Set([activeRef, completedRef]));
|
|
66
|
+
return {
|
|
67
|
+
activeSha: refs.get(activeRef) ?? null,
|
|
68
|
+
completedSha: refs.get(completedRef) ?? null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function resolveSyncedArchiveEntry({
|
|
73
|
+
record,
|
|
74
|
+
projectName,
|
|
75
|
+
probeRefs = probeRemoteRefs,
|
|
76
|
+
repoRoot,
|
|
77
|
+
}) {
|
|
78
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
79
|
+
throw entryError('Synced archive entry requires a valid discovery record.');
|
|
80
|
+
}
|
|
81
|
+
const activeRef = `refs/oat/projects/${projectName}`;
|
|
82
|
+
const completedRef = `refs/oat/completed/${projectName}`;
|
|
83
|
+
if (
|
|
84
|
+
record.slug !== projectName ||
|
|
85
|
+
record.scope !== 'synced' ||
|
|
86
|
+
record.ref !== activeRef ||
|
|
87
|
+
typeof record.remote !== 'string' ||
|
|
88
|
+
record.remote.length === 0
|
|
89
|
+
) {
|
|
90
|
+
throw entryError('Synced archive entry record identity is not canonical.');
|
|
91
|
+
}
|
|
92
|
+
const hasSnapshot = typeof record.archiveSnapshot === 'string';
|
|
93
|
+
const hasSourceSha = typeof record.archiveSourceRefSha === 'string';
|
|
94
|
+
if (hasSnapshot !== hasSourceSha) {
|
|
95
|
+
throw entryError('Synced archive retry identity is incomplete.');
|
|
96
|
+
}
|
|
97
|
+
if (!hasSnapshot) {
|
|
98
|
+
return { status: 'ok', route: 'pull', terminal: false };
|
|
99
|
+
}
|
|
100
|
+
if (
|
|
101
|
+
record.archiveSnapshot.length === 0 ||
|
|
102
|
+
!/^[0-9a-f]{40}$/.test(record.archiveSourceRefSha)
|
|
103
|
+
) {
|
|
104
|
+
throw entryError('Synced archive retry identity is malformed.');
|
|
105
|
+
}
|
|
106
|
+
const { activeSha, completedSha } = await probeRefs({
|
|
107
|
+
repoRoot,
|
|
108
|
+
remote: record.remote,
|
|
109
|
+
activeRef,
|
|
110
|
+
completedRef,
|
|
111
|
+
});
|
|
112
|
+
if (completedSha === null) {
|
|
113
|
+
if (activeSha === record.archiveSourceRefSha) {
|
|
114
|
+
return { status: 'ok', route: 'pull', terminal: false };
|
|
115
|
+
}
|
|
116
|
+
throw entryError(
|
|
117
|
+
'Persisted archive retry identity has no matching active or completed ref.',
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
completedSha !== record.archiveSourceRefSha ||
|
|
122
|
+
(activeSha !== null && activeSha !== record.archiveSourceRefSha)
|
|
123
|
+
) {
|
|
124
|
+
throw entryError(
|
|
125
|
+
'Persisted archive retry identity does not match the authoritative terminal refs.',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
status: 'ok',
|
|
130
|
+
route: 'archive-resume',
|
|
131
|
+
terminal: true,
|
|
132
|
+
archiveSnapshot: record.archiveSnapshot,
|
|
133
|
+
verifiedSourceSha: record.archiveSourceRefSha,
|
|
134
|
+
completedRef,
|
|
135
|
+
activeAliasDisposition: activeSha === null ? 'removed' : 'retained',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function main(argv) {
|
|
140
|
+
const options = parseArguments(argv);
|
|
141
|
+
let record;
|
|
142
|
+
try {
|
|
143
|
+
record = JSON.parse(await readFile(options.recordPath, 'utf8'));
|
|
144
|
+
} catch (error) {
|
|
145
|
+
throw entryError(
|
|
146
|
+
`Unable to read synced discovery record: ${error.message}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return resolveSyncedArchiveEntry({ ...options, record });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (
|
|
153
|
+
process.argv[1] &&
|
|
154
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
155
|
+
) {
|
|
156
|
+
main(process.argv.slice(2))
|
|
157
|
+
.then((result) => process.stdout.write(`${JSON.stringify(result)}\n`))
|
|
158
|
+
.catch((error) => {
|
|
159
|
+
process.stderr.write(
|
|
160
|
+
`${JSON.stringify({ ok: false, code: error.code, message: error.message })}\n`,
|
|
161
|
+
);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
@@ -244,7 +244,7 @@ remains immutable baseline evidence for the original p01-t01 verification.
|
|
|
244
244
|
| `oat-project-summary/SKILL.md` | `68c197c44c18 -> NG`; `35cb2ea1d677 -> NG`; `a167d03559fa -> NG`; `cf317c17c586 -> SUMMARY-01`; `f3e49b727752 -> NG`; `f979b2342d28 -> NG`; `803bf40ec423 -> NG`; `c8fced47ef8e -> NG`; `d7ea1e2a4f5f -> NG`; `5f315ab87cb1 -> NG` |
|
|
245
245
|
| `oat-project-pr-final/SKILL.md` | `601ac2b890f2 -> NG`; `c264a64398f0 -> NG`; `9efcbcee42ea -> NG`; `655d61df0a8f -> NG`; `cf317c17c586 -> PRFINAL-01`; `2de348a391c0 -> PRFINAL-03` |
|
|
246
246
|
| `oat-project-pr-final/references/docs/autonomy-contract.md` | `* -> NG` |
|
|
247
|
-
| `oat-project-complete/SKILL.md` | `6ed459f9e462 -> NG`; `a167d03559fa -> NG`; `79c2eeed668c -> COMPLETE-01`; `2c1514b9d9af -> COMPLETE-01`; `31c750b4fea7 -> COMPLETE-01`; `cfba413b1293 -> COMPLETE-01`; `7fcfce0423ac -> COMPLETE-01`; `7bcbf58b54e9 -> NG`; `4c7f88a7f942 -> COMPLETE-02`; `6a1a95acc8cc -> COMPLETE-02`; `3b559956b3eb -> COMPLETE-03`; `14d161cdf99b -> COMPLETE-03`; `
|
|
247
|
+
| `oat-project-complete/SKILL.md` | `6ed459f9e462 -> NG`; `a167d03559fa -> NG`; `79c2eeed668c -> COMPLETE-01`; `2c1514b9d9af -> COMPLETE-01`; `31c750b4fea7 -> COMPLETE-01`; `cfba413b1293 -> COMPLETE-01`; `7fcfce0423ac -> COMPLETE-01`; `7bcbf58b54e9 -> NG`; `4c7f88a7f942 -> COMPLETE-02`; `6a1a95acc8cc -> COMPLETE-02`; `3b559956b3eb -> COMPLETE-03`; `14d161cdf99b -> COMPLETE-03`; `fffe5eea5ebd -> NG`; `e7dc6fdc4eb3 -> COMPLETE-01`; `e5460e437299 -> NG`; `882b61c9096c -> NG`; `9ffa1c752505 -> NG` |
|
|
248
248
|
| `oat-project-review-provide/SKILL.md` | `1f4b981cac8b -> NG`; `adaeca152bb2 -> NG`; `2f7c201c3c06 -> NG`; `88f79700afdc -> NG`; `655d61df0a8f -> NG`; `efafde9ef5a0 -> NG`; `d2a3861d6323 -> REVIEWPROVIDE-02`; `65f5b9cf49cd -> REVIEWPROVIDE-04`; `962cb38d246a -> REVIEWPROVIDE-04`; `1a568483ba0b -> REVIEWPROVIDE-04`; `610e27edfe85 -> REVIEWPROVIDE-04`; `bb0096e95c47 -> REVIEWPROVIDE-06`; `55f6357a987d -> REVIEWPROVIDE-06`; `1bee61f8531c -> REVIEWPROVIDE-07`; `3d35091607a1 -> REVIEWPROVIDE-08`; `fb40c4341c4c -> NG`; `b22d16885150 -> NG`; `d2b25906242e -> REVIEWPROVIDE-08`; `fd8ebf72b3c4 -> NG`; `fcb9de611ff3 -> NG`; `d2a838dfb18d -> NG`; `c3d50ea7cf69 -> NG`; `09b1b8dd445c -> NG`; `7acc8f3718d2 -> REVIEWPROVIDE-09`; `b2fa883fc4a2 -> NG`; `fe4421531b4c -> NG` |
|
|
249
249
|
| `oat-project-review-receive/SKILL.md` | `9b8a3743d626 -> NG`; `df49c8c6f192 -> NG`; `4ee8730382d3 -> NG`; `655d61df0a8f -> NG`; `765f897d67ac -> NG`; `53b58d16dd32 -> NG`; `fb8755a3bdf4 -> NG`; `c8ab5dd90075 -> NG`; `1f7dd98dff2f -> NG`; `56f78c6740e6 -> NG`; `76f5fb5a88cd -> NG`; `085abfa3599f -> REVIEWRECEIVE-01`; `3ad3aadb8c60 -> NG`; `a7d114059bb6 -> NG`; `ac88cfaa83d9 -> NG`; `594bbf70346c -> REVIEWRECEIVE-08`; `f31f07284718 -> REVIEWRECEIVE-02`; `aba178a21441 -> REVIEWRECEIVE-04`; `a37e601cd731 -> REVIEWRECEIVE-05`; `20e05e1a4381 -> REVIEWRECEIVE-06`; `90001dadf75f -> REVIEWRECEIVE-07`; `f8fd58180ecf -> NG`; `c3abcdc02d9f -> NG`; `f2ebdd6abb9b -> NG` |
|
|
250
250
|
| `oat-project-review-provide/SKILL.md` | `2e794bf4fc4a -> REVIEWPROVIDE-08`; `5441ab977b46 -> REVIEWPROVIDE-08` |
|
|
@@ -260,7 +260,7 @@ remains immutable baseline evidence for the original p01-t01 verification.
|
|
|
260
260
|
| `oat-project-dispatch-subagents/SKILL.md` | `eca03ad1ca12 -> PDISPATCH-01`; `59fbcdc79cea -> PDISPATCH-01` |
|
|
261
261
|
| `oat-project-plan/SKILL.md` | `f99e44b39932 -> PLAN-12`; `7e6a9854504b -> NG` |
|
|
262
262
|
| `oat-project-implement/references/completion-and-closeout.md` | `6909a5a5a1fa -> IMPLEMENT-19`; `a7c02ef670fd -> IMPLEMENT-19`; `9dde56d89a71 -> IMPLEMENT-16`; `13c41b2b2590 -> IMPLEMENT-16` |
|
|
263
|
-
| `oat-project-complete/SKILL.md` | `0177956707aa -> NG`; `8305b5bb65ef -> NG`; `eb3e75663ac0 -> NG`; `8cc1fa5aab1e -> COMPLETE-01`; `4c209fbb4716 -> COMPLETE-01`; `02e4175e1171 -> COMPLETE-01`; `d698d4f98e76 -> COMPLETE-01`; `9f86a7218d93 -> COMPLETE-01`; `024713698d9a -> COMPLETE-01`; `c327995053fa -> COMPLETE-01`; `61f81d4ec7c1 -> COMPLETE-01`; `14fb2e56809f -> COMPLETE-01`; `7d4ff668744d -> COMPLETE-01`; `502a6c141784 -> COMPLETE-01`; `fb7e2db356af -> COMPLETE-01`
|
|
263
|
+
| `oat-project-complete/SKILL.md` | `0177956707aa -> NG`; `8305b5bb65ef -> NG`; `eb3e75663ac0 -> NG`; `8cc1fa5aab1e -> COMPLETE-01`; `4c209fbb4716 -> COMPLETE-01`; `02e4175e1171 -> COMPLETE-01`; `d698d4f98e76 -> COMPLETE-01`; `9f86a7218d93 -> COMPLETE-01`; `024713698d9a -> COMPLETE-01`; `c327995053fa -> COMPLETE-01`; `61f81d4ec7c1 -> COMPLETE-01`; `14fb2e56809f -> COMPLETE-01`; `7d4ff668744d -> COMPLETE-01`; `502a6c141784 -> COMPLETE-01`; `fb7e2db356af -> COMPLETE-01`; `f73f8476360a -> NG`; `1e15f420039f -> NG`; `cc234032ddd7 -> NG`; `c3d25f1398bb -> NG`; `ca13a6645df2 -> NG` |
|
|
264
264
|
|
|
265
265
|
## Prompt-scan comparison
|
|
266
266
|
|