@fitlab-ai/agent-infra 0.9.8 → 0.9.9
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +58 -21
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +173 -10
- package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +56 -67
- package/dist/lib/git/workflow.js +10 -1
- package/dist/lib/internal/codex-lifecycle.js +19 -5
- package/dist/lib/internal/codex-sandbox-controller.js +6 -13
- package/dist/lib/internal/git-workflow.js +5 -7
- package/dist/lib/internal/platform-pr.js +20 -7
- package/dist/lib/internal/release-workflow.js +2 -1
- package/dist/lib/internal/sandbox-control.js +79 -2
- package/dist/lib/internal/task-finalization.js +54 -0
- package/dist/lib/internal/task-orchestration.js +156 -291
- package/dist/lib/platform/pr-summary.js +48 -10
- package/dist/lib/platform/pull-requests.js +205 -41
- package/dist/lib/sandbox/commands/rm.js +316 -55
- package/dist/lib/sandbox/control/client.js +190 -6
- package/dist/lib/sandbox/control/controller-registration.js +273 -0
- package/dist/lib/sandbox/control/executor.js +137 -5
- package/dist/lib/sandbox/control/lifecycle.js +3 -1
- package/dist/lib/sandbox/control/protocol.js +67 -6
- package/dist/lib/sandbox/index.js +5 -3
- package/dist/lib/sandbox/managed-fs.js +5 -0
- package/dist/lib/sandbox/task-resolver.js +50 -6
- package/dist/lib/sandbox/workspace-identity.js +27 -0
- package/dist/lib/sandbox/worktree-safety.js +215 -2
- package/dist/lib/task/codex-orchestration.js +158 -33
- package/dist/lib/task/commands/status.js +59 -1
- package/dist/lib/task/commit-operation.js +335 -0
- package/dist/lib/task/commit-policy.js +18 -0
- package/dist/lib/task/delegation-receipts.js +243 -2
- package/dist/lib/task/finalization.js +567 -0
- package/dist/lib/task/operation-outcome.js +33 -0
- package/dist/lib/task/orchestration.js +187 -761
- package/dist/lib/task/short-id.js +59 -1
- package/dist/lib/task/task-execution-lock.js +6 -1
- package/dist/lib/task/verification-engine.js +113 -60
- package/dist/lib/task/verification.js +2 -1
- package/dist/lib/task/workflow-warning-intents.js +50 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +61 -19
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +276 -12
- package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +83 -91
- package/lib/git/workflow.ts +11 -2
- package/lib/internal/codex-lifecycle.ts +18 -5
- package/lib/internal/codex-sandbox-controller.ts +6 -12
- package/lib/internal/git-workflow.ts +5 -6
- package/lib/internal/platform-pr.ts +14 -7
- package/lib/internal/release-workflow.ts +2 -1
- package/lib/internal/sandbox-control.ts +92 -2
- package/lib/internal/task-finalization.ts +68 -0
- package/lib/internal/task-orchestration.ts +20 -103
- package/lib/platform/pr-summary.ts +55 -11
- package/lib/platform/pull-requests.ts +211 -41
- package/lib/sandbox/commands/rm.ts +413 -61
- package/lib/sandbox/control/client.ts +255 -6
- package/lib/sandbox/control/controller-registration.ts +347 -0
- package/lib/sandbox/control/executor.ts +163 -5
- package/lib/sandbox/control/lifecycle.ts +2 -1
- package/lib/sandbox/control/protocol.ts +80 -7
- package/lib/sandbox/index.ts +5 -3
- package/lib/sandbox/managed-fs.ts +5 -0
- package/lib/sandbox/task-resolver.ts +58 -7
- package/lib/sandbox/workspace-identity.ts +50 -2
- package/lib/sandbox/workspace-view.ts +3 -3
- package/lib/sandbox/worktree-safety.ts +236 -2
- package/lib/task/codex-orchestration.ts +167 -33
- package/lib/task/commands/status.ts +78 -1
- package/lib/task/commit-operation.ts +430 -0
- package/lib/task/commit-policy.ts +30 -0
- package/lib/task/delegation-receipts.ts +258 -1
- package/lib/task/finalization.ts +719 -0
- package/lib/task/operation-outcome.ts +48 -0
- package/lib/task/orchestration.ts +217 -982
- package/lib/task/short-id.ts +65 -1
- package/lib/task/task-execution-lock.ts +10 -1
- package/lib/task/verification-engine.ts +118 -63
- package/lib/task/verification.ts +3 -2
- package/lib/task/workflow-warning-intents.ts +57 -2
- package/package.json +2 -2
- package/templates/.agents/QUICKSTART.en.md +2 -2
- package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +1 -21
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +2 -2
- package/templates/.agents/rules/task-management.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
- package/templates/.agents/skills/commit/SKILL.en.md +41 -125
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
- package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
- package/templates/.codex/hooks.json +4 -4
- package/dist/lib/task/commit-finalization.js +0 -305
- package/dist/lib/task/commit-intent.js +0 -165
- package/lib/task/commit-finalization.ts +0 -419
- package/lib/task/commit-intent.ts +0 -230
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
5
|
+
import { applyTaskLifecycle } from './lifecycle.js';
|
|
6
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
|
+
import { inspectShortIdRegistry } from './short-id.js';
|
|
8
|
+
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
|
+
import { verifyTaskEvent } from './verification.js';
|
|
10
|
+
import { projectFinalizationWarning } from './workflow-warning-intents.js';
|
|
11
|
+
import { mergeOperationWarnings } from './operation-outcome.js';
|
|
12
|
+
const RECEIPT_VERSION = 2;
|
|
13
|
+
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification'];
|
|
14
|
+
function finalizationRoot(repoRoot) {
|
|
15
|
+
return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
|
|
16
|
+
}
|
|
17
|
+
function receiptPath(repoRoot, taskId) {
|
|
18
|
+
return path.join(finalizationRoot(repoRoot), `${taskId}.json`);
|
|
19
|
+
}
|
|
20
|
+
function now() {
|
|
21
|
+
return new Date().toISOString();
|
|
22
|
+
}
|
|
23
|
+
function errorOf(error, fallbackCode, retryable = false) {
|
|
24
|
+
const value = error;
|
|
25
|
+
const code = typeof value?.code === 'string' && value.code ? value.code : fallbackCode;
|
|
26
|
+
const message = typeof value?.message === 'string' && value.message ? value.message : String(error);
|
|
27
|
+
return { code, message, retryable: value?.retryable === true || retryable };
|
|
28
|
+
}
|
|
29
|
+
function failed(taskId, error, overrides = {}) {
|
|
30
|
+
return {
|
|
31
|
+
status: error.retryable ? 'blocked' : 'failed',
|
|
32
|
+
changed: false,
|
|
33
|
+
taskId,
|
|
34
|
+
lifecycle: null,
|
|
35
|
+
taskComment: null,
|
|
36
|
+
verification: null,
|
|
37
|
+
completedSteps: [],
|
|
38
|
+
pendingSteps: [...FINALIZATION_STEPS],
|
|
39
|
+
result: error.retryable ? 'blocked' : 'failed',
|
|
40
|
+
warnings: [],
|
|
41
|
+
error,
|
|
42
|
+
...overrides
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function emptyReceipt(taskId) {
|
|
46
|
+
return {
|
|
47
|
+
version: RECEIPT_VERSION,
|
|
48
|
+
taskId,
|
|
49
|
+
intent: 'complete',
|
|
50
|
+
receiptId: randomUUID(),
|
|
51
|
+
revision: 0,
|
|
52
|
+
lifecycle: 'pending',
|
|
53
|
+
taskComment: 'pending',
|
|
54
|
+
verification: 'pending',
|
|
55
|
+
warningProjection: 'done',
|
|
56
|
+
warnings: [],
|
|
57
|
+
updatedAt: now(),
|
|
58
|
+
lastError: null
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function validWarning(value) {
|
|
62
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
63
|
+
return false;
|
|
64
|
+
const warning = value;
|
|
65
|
+
if (Object.keys(warning).sort().join('\0') !== [
|
|
66
|
+
'code', 'message', 'retryable', 'step', 'target', 'severity', 'status', 'resolvedAt'
|
|
67
|
+
].sort().join('\0'))
|
|
68
|
+
return false;
|
|
69
|
+
return typeof warning.code === 'string' && warning.code.length > 0
|
|
70
|
+
&& typeof warning.message === 'string' && warning.message.length > 0
|
|
71
|
+
&& typeof warning.retryable === 'boolean'
|
|
72
|
+
&& typeof warning.step === 'string' && warning.step.length > 0
|
|
73
|
+
&& typeof warning.target === 'string' && warning.target.length > 0
|
|
74
|
+
&& (warning.severity === 'IMPORTANT' || warning.severity === 'ACTION_REQUIRED')
|
|
75
|
+
&& (warning.status === 'open' || warning.status === 'resolved')
|
|
76
|
+
&& (warning.resolvedAt === null || typeof warning.resolvedAt === 'string');
|
|
77
|
+
}
|
|
78
|
+
function validateReceipt(value, taskId) {
|
|
79
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
80
|
+
throw new Error('receipt must be an object');
|
|
81
|
+
const receipt = value;
|
|
82
|
+
const states = ['pending', 'done', 'skipped'];
|
|
83
|
+
if (receipt.version !== RECEIPT_VERSION || receipt.taskId !== taskId || receipt.intent !== 'complete'
|
|
84
|
+
|| typeof receipt.receiptId !== 'string' || receipt.receiptId.length === 0
|
|
85
|
+
|| !Number.isSafeInteger(receipt.revision) || Number(receipt.revision) < 0
|
|
86
|
+
|| !states.includes(String(receipt.lifecycle))
|
|
87
|
+
|| !states.includes(String(receipt.taskComment))
|
|
88
|
+
|| !states.includes(String(receipt.verification))
|
|
89
|
+
|| !['pending', 'done'].includes(String(receipt.warningProjection))
|
|
90
|
+
|| !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
|
|
91
|
+
|| typeof receipt.updatedAt !== 'string'
|
|
92
|
+
|| (receipt.lastError !== null && (typeof receipt.lastError !== 'object' || Array.isArray(receipt.lastError))))
|
|
93
|
+
throw new Error('receipt schema is invalid');
|
|
94
|
+
return receipt;
|
|
95
|
+
}
|
|
96
|
+
function readReceipt(repoRoot, taskId) {
|
|
97
|
+
const file = receiptPath(repoRoot, taskId);
|
|
98
|
+
if (!fs.existsSync(file))
|
|
99
|
+
return null;
|
|
100
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
101
|
+
return validateReceipt(value, taskId);
|
|
102
|
+
}
|
|
103
|
+
function writeReceipt(repoRoot, receipt) {
|
|
104
|
+
const directory = finalizationRoot(repoRoot);
|
|
105
|
+
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
106
|
+
const target = receiptPath(repoRoot, receipt.taskId);
|
|
107
|
+
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(temporary, `${JSON.stringify(receipt)}\n`, { mode: 0o600, flag: 'wx' });
|
|
110
|
+
fs.renameSync(temporary, target);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
try {
|
|
114
|
+
fs.unlinkSync(temporary);
|
|
115
|
+
}
|
|
116
|
+
catch { /* preserve the primary error */ }
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function updateReceipt(repoRoot, receipt, patch) {
|
|
121
|
+
const current = readReceipt(repoRoot, receipt.taskId);
|
|
122
|
+
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
123
|
+
const error = new Error('finalization receipt revision is stale');
|
|
124
|
+
Object.assign(error, { code: 'FINALIZATION_CAPABILITY_STALE' });
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
const next = { ...current, ...patch, revision: current.revision + 1, updatedAt: now() };
|
|
128
|
+
writeReceipt(repoRoot, next);
|
|
129
|
+
return next;
|
|
130
|
+
}
|
|
131
|
+
function completedSteps(receipt) {
|
|
132
|
+
return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] !== 'pending');
|
|
133
|
+
}
|
|
134
|
+
function pendingSteps(receipt) {
|
|
135
|
+
return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] === 'pending');
|
|
136
|
+
}
|
|
137
|
+
function lifecycleStep(result) {
|
|
138
|
+
return {
|
|
139
|
+
status: result.status,
|
|
140
|
+
changed: result.changed,
|
|
141
|
+
error: result.error ? errorOf(result.error, 'LIFECYCLE_FAILED') : null,
|
|
142
|
+
completedSteps: result.completedSteps ?? [],
|
|
143
|
+
pendingSteps: result.pendingSteps ?? []
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function commentStep(result) {
|
|
147
|
+
return {
|
|
148
|
+
status: result.status,
|
|
149
|
+
changed: result.changed,
|
|
150
|
+
error: result.error ? errorOf(result.error, 'COMMENT_SYNC_FAILED') : null
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function verificationStep(result) {
|
|
154
|
+
return {
|
|
155
|
+
status: result.status,
|
|
156
|
+
changed: false,
|
|
157
|
+
error: result.status === 'pass' ? null : verificationFailure(result)
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function verificationFailure(result) {
|
|
161
|
+
if (result.error)
|
|
162
|
+
return errorOf(result.error, 'VERIFY_FAILED', result.status === 'blocked');
|
|
163
|
+
const invocation = [...result.invocations].reverse().find((item) => item.status !== 'pass');
|
|
164
|
+
const payload = invocation?.payload;
|
|
165
|
+
const details = [payload?.message, payload?.summary, payload?.action]
|
|
166
|
+
.filter((value) => typeof value === 'string' && value.length > 0);
|
|
167
|
+
const code = result.status === 'blocked' || invocation?.status === 'blocked'
|
|
168
|
+
? 'CHECK_BLOCKED'
|
|
169
|
+
: result.status === 'fail' ? 'CHECK_FAILED' : 'VERIFY_FAILED';
|
|
170
|
+
return {
|
|
171
|
+
code,
|
|
172
|
+
message: details.length > 0 ? details.join(' - ') : `verification ${result.status}`,
|
|
173
|
+
retryable: code === 'CHECK_BLOCKED'
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function warningFromError(step, error, severity = 'ACTION_REQUIRED') {
|
|
177
|
+
return {
|
|
178
|
+
code: error.code,
|
|
179
|
+
message: error.message,
|
|
180
|
+
retryable: error.retryable,
|
|
181
|
+
step,
|
|
182
|
+
target: step,
|
|
183
|
+
severity
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function openWarnings(receipt) {
|
|
187
|
+
return mergeOperationWarnings(receipt.warnings.filter((warning) => warning.status === 'open').map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning));
|
|
188
|
+
}
|
|
189
|
+
function warningRecord(warning, status) {
|
|
190
|
+
return { ...warning, status, resolvedAt: status === 'resolved' ? now() : null };
|
|
191
|
+
}
|
|
192
|
+
function replaceWarning(receipt, warning, status) {
|
|
193
|
+
const next = warningRecord(warning, status);
|
|
194
|
+
const key = `${warning.step}\0${warning.code}\0${warning.target}`;
|
|
195
|
+
return [
|
|
196
|
+
...receipt.warnings.filter((item) => `${item.step}\0${item.code}\0${item.target}` !== key),
|
|
197
|
+
next
|
|
198
|
+
];
|
|
199
|
+
}
|
|
200
|
+
function resolveStepWarnings(receipt, step) {
|
|
201
|
+
return receipt.warnings.map((warning) => warning.step === step && warning.status === 'open'
|
|
202
|
+
? { ...warning, status: 'resolved', resolvedAt: now() }
|
|
203
|
+
: warning);
|
|
204
|
+
}
|
|
205
|
+
function issueCapability(receipt, scope) {
|
|
206
|
+
return {
|
|
207
|
+
receiptId: receipt.receiptId,
|
|
208
|
+
baseRevision: receipt.revision,
|
|
209
|
+
scope,
|
|
210
|
+
nonce: randomUUID(),
|
|
211
|
+
issuedAt: now()
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function capabilityError(code, message) {
|
|
215
|
+
const error = new Error(message);
|
|
216
|
+
error.name = 'OrchestrationStateError';
|
|
217
|
+
Object.assign(error, { code });
|
|
218
|
+
return error;
|
|
219
|
+
}
|
|
220
|
+
function mutationKeys(value) {
|
|
221
|
+
return Object.keys(value).sort();
|
|
222
|
+
}
|
|
223
|
+
function validateCapabilityMutation(current, capability, mutation, consumed) {
|
|
224
|
+
if (capability.receiptId !== current.receiptId || capability.baseRevision !== current.revision || consumed.has(capability.nonce)) {
|
|
225
|
+
throw capabilityError('FINALIZATION_CAPABILITY_STALE', 'finalization capability is stale or outside its scope');
|
|
226
|
+
}
|
|
227
|
+
if (capability.scope !== mutation.scope) {
|
|
228
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation scope does not match its capability');
|
|
229
|
+
}
|
|
230
|
+
if (mutation.operation !== 'succeeded' && mutation.operation !== 'failed') {
|
|
231
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability operation is invalid');
|
|
232
|
+
}
|
|
233
|
+
if (mutation.scope === 'task-comment') {
|
|
234
|
+
if (current.taskComment !== 'pending')
|
|
235
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability scope task-comment is not pending');
|
|
236
|
+
const expected = mutation.operation === 'succeeded' ? ['operation', 'scope', 'state'] : ['error', 'operation', 'scope'];
|
|
237
|
+
if (mutationKeys(mutation).join('\0') !== expected.join('\0'))
|
|
238
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation shape is invalid');
|
|
239
|
+
if (mutation.operation === 'succeeded' && mutation.state !== 'done' && mutation.state !== 'skipped') {
|
|
240
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability transition is invalid');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else if (mutation.scope === 'verification') {
|
|
244
|
+
if (current.verification !== 'pending')
|
|
245
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability scope verification is not pending');
|
|
246
|
+
const expected = mutation.operation === 'succeeded' ? ['operation', 'scope'] : ['error', 'operation', 'scope'];
|
|
247
|
+
if (mutationKeys(mutation).join('\0') !== expected.join('\0'))
|
|
248
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation shape is invalid');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
if (current.warningProjection !== 'pending')
|
|
252
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'warning projection capability is not pending');
|
|
253
|
+
const expected = mutation.operation === 'succeeded' ? ['operation', 'scope'] : ['error', 'operation', 'scope'];
|
|
254
|
+
if (mutationKeys(mutation).join('\0') !== expected.join('\0'))
|
|
255
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation shape is invalid');
|
|
256
|
+
}
|
|
257
|
+
if (mutation.operation === 'failed' && (!mutation.error.code || !mutation.error.message || typeof mutation.error.retryable !== 'boolean')) {
|
|
258
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability failure detail is invalid');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
function mutationPatch(current, mutation) {
|
|
262
|
+
if (mutation.scope === 'task-comment') {
|
|
263
|
+
if (mutation.operation === 'succeeded') {
|
|
264
|
+
const warnings = resolveStepWarnings(current, 'task-comment');
|
|
265
|
+
return { taskComment: mutation.state, warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
|
|
266
|
+
}
|
|
267
|
+
const warnings = replaceWarning(current, warningFromError('task-comment', mutation.error), 'open');
|
|
268
|
+
return { taskComment: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
|
|
269
|
+
}
|
|
270
|
+
if (mutation.scope === 'verification') {
|
|
271
|
+
if (mutation.operation === 'succeeded') {
|
|
272
|
+
const warnings = resolveStepWarnings(current, 'verification');
|
|
273
|
+
return { verification: 'done', warningProjection: warnings.length > 0 ? 'pending' : 'done', warnings, lastError: null };
|
|
274
|
+
}
|
|
275
|
+
const warnings = replaceWarning(current, warningFromError('verification', mutation.error), 'open');
|
|
276
|
+
return { verification: 'pending', warningProjection: 'pending', warnings, lastError: mutation.error };
|
|
277
|
+
}
|
|
278
|
+
return mutation.operation === 'succeeded'
|
|
279
|
+
? { warningProjection: 'done', lastError: null }
|
|
280
|
+
: { warningProjection: 'pending', lastError: mutation.error };
|
|
281
|
+
}
|
|
282
|
+
function applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, consumed) {
|
|
283
|
+
const resolved = resolveTaskRef(receipt.taskId, { repoRoot });
|
|
284
|
+
if (!resolved.ok || resolved.state !== 'completed') {
|
|
285
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires a completed task');
|
|
286
|
+
}
|
|
287
|
+
const current = readReceipt(repoRoot, receipt.taskId);
|
|
288
|
+
if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
|
|
289
|
+
throw capabilityError('FINALIZATION_CAPABILITY_STALE', 'finalization receipt revision is stale');
|
|
290
|
+
}
|
|
291
|
+
validateCapabilityMutation(current, capability, mutation, consumed);
|
|
292
|
+
const next = updateReceipt(repoRoot, current, mutationPatch(current, mutation));
|
|
293
|
+
consumed.add(capability.nonce);
|
|
294
|
+
return next;
|
|
295
|
+
}
|
|
296
|
+
function applyFinalizationReceiptMutation(repoRoot, receipt, capability, mutation) {
|
|
297
|
+
const resolved = resolveTaskRef(receipt.taskId, { repoRoot });
|
|
298
|
+
if (!resolved.ok || resolved.state !== 'completed') {
|
|
299
|
+
throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability requires a completed task');
|
|
300
|
+
}
|
|
301
|
+
return withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.receipt-mutation', () => applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, mutation, new Set()));
|
|
302
|
+
}
|
|
303
|
+
function warningKey(warning) {
|
|
304
|
+
return `${warning.step}\0${warning.code}\0${warning.target}`;
|
|
305
|
+
}
|
|
306
|
+
function reconcileWarningProjection(repoRoot, taskId, receipt, consumed) {
|
|
307
|
+
if (receipt.warningProjection === 'done')
|
|
308
|
+
return receipt;
|
|
309
|
+
const warnings = [...new Map(receipt.warnings.map((warning) => [warningKey(warning), warning])).values()];
|
|
310
|
+
for (const warning of warnings) {
|
|
311
|
+
try {
|
|
312
|
+
const projected = projectFinalizationWarning(taskId, warning, { repoRoot });
|
|
313
|
+
if (projected.status === 'failed') {
|
|
314
|
+
const detail = {
|
|
315
|
+
code: projected.error?.code || 'FINALIZATION_WARNING_PROJECTION_FAILED',
|
|
316
|
+
message: projected.error?.message || 'workflow warning projection failed',
|
|
317
|
+
retryable: true
|
|
318
|
+
};
|
|
319
|
+
const capability = issueCapability(receipt, 'warning-projection');
|
|
320
|
+
return applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'failed', error: detail }, consumed);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
const detail = errorOf(error, 'FINALIZATION_WARNING_PROJECTION_FAILED', true);
|
|
325
|
+
try {
|
|
326
|
+
const capability = issueCapability(receipt, 'warning-projection');
|
|
327
|
+
return applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'failed', error: detail }, consumed);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
return receipt;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const capability = issueCapability(receipt, 'warning-projection');
|
|
336
|
+
return applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, { scope: 'warning-projection', operation: 'succeeded' }, consumed);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return receipt;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
function terminalResult(taskId, receipt, steps, changed, error = null) {
|
|
343
|
+
const pending = pendingSteps(receipt);
|
|
344
|
+
const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
|
|
345
|
+
const warnings = openWarnings(receipt);
|
|
346
|
+
const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
|
|
347
|
+
const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
|
|
348
|
+
return {
|
|
349
|
+
status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
|
|
350
|
+
changed,
|
|
351
|
+
taskId,
|
|
352
|
+
...steps,
|
|
353
|
+
completedSteps: completedSteps(receipt),
|
|
354
|
+
pendingSteps: pending,
|
|
355
|
+
result: postLifecyclePending && (warnings.length > 0 || receipt.warningProjection === 'pending')
|
|
356
|
+
? 'completed_with_warnings'
|
|
357
|
+
: hardError
|
|
358
|
+
? (error?.retryable ? 'blocked' : 'failed')
|
|
359
|
+
: pending.length === 0 || postLifecyclePending
|
|
360
|
+
? 'completed'
|
|
361
|
+
: blocked
|
|
362
|
+
? 'blocked'
|
|
363
|
+
: 'failed',
|
|
364
|
+
warnings,
|
|
365
|
+
error: hardError ? error : postLifecyclePending ? null : error
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function applyUnderLock(request, taskId, options) {
|
|
369
|
+
const repoRoot = path.resolve(options.repoRoot);
|
|
370
|
+
const lifecycle = options.lifecycle ?? applyTaskLifecycle;
|
|
371
|
+
const commentSync = options.commentSync ?? syncPlatformComment;
|
|
372
|
+
const verify = options.verify ?? verifyTaskEvent;
|
|
373
|
+
const consumedCapabilities = new Set();
|
|
374
|
+
let receipt;
|
|
375
|
+
try {
|
|
376
|
+
const file = receiptPath(repoRoot, taskId);
|
|
377
|
+
const existed = fs.existsSync(file);
|
|
378
|
+
receipt = readReceipt(repoRoot, taskId) ?? emptyReceipt(taskId);
|
|
379
|
+
if (!existed)
|
|
380
|
+
writeReceipt(repoRoot, receipt);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
|
|
384
|
+
}
|
|
385
|
+
const preflightState = resolveTaskRef(taskId, { repoRoot });
|
|
386
|
+
if (options.preflight && preflightState.ok && preflightState.state === 'active') {
|
|
387
|
+
try {
|
|
388
|
+
const preflight = options.preflight({ taskRef: taskId, event: 'complete-task.preflight' }, { repoRoot });
|
|
389
|
+
if (preflight.status !== 'pass') {
|
|
390
|
+
const detail = verificationFailure(preflight);
|
|
391
|
+
return failed(taskId, detail, {
|
|
392
|
+
lifecycle: null,
|
|
393
|
+
taskComment: null,
|
|
394
|
+
verification: null,
|
|
395
|
+
completedSteps: completedSteps(receipt),
|
|
396
|
+
pendingSteps: pendingSteps(receipt)
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
return failed(taskId, errorOf(error, 'TASK_FINALIZATION_PREFLIGHT_FAILED', true));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
let lifecycleResult = null;
|
|
405
|
+
let changed = false;
|
|
406
|
+
try {
|
|
407
|
+
const resolved = resolveTaskRef(taskId, { repoRoot });
|
|
408
|
+
const registry = resolved.ok && resolved.state === 'completed' ? inspectShortIdRegistry(repoRoot) : null;
|
|
409
|
+
if (registry && registry.status !== 'valid') {
|
|
410
|
+
const detail = {
|
|
411
|
+
code: 'TASK_FINALIZATION_SHORT_ID_REGISTRY_UNAVAILABLE',
|
|
412
|
+
message: `cannot verify canonical short-id registry: ${registry.error.code}: ${registry.error.message}`,
|
|
413
|
+
retryable: false
|
|
414
|
+
};
|
|
415
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
416
|
+
return terminalResult(taskId, receipt, {
|
|
417
|
+
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
418
|
+
taskComment: null,
|
|
419
|
+
verification: null
|
|
420
|
+
}, changed, detail);
|
|
421
|
+
}
|
|
422
|
+
const shortIds = registry?.status === 'valid' ? registry.shortIds : null;
|
|
423
|
+
if (shortIds?.has(taskId)) {
|
|
424
|
+
const detail = {
|
|
425
|
+
code: 'TASK_FINALIZATION_CANONICAL_STATE_INVALID',
|
|
426
|
+
message: 'completed task still has an active short-id registry entry',
|
|
427
|
+
retryable: false
|
|
428
|
+
};
|
|
429
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
430
|
+
return terminalResult(taskId, receipt, {
|
|
431
|
+
lifecycle: { status: 'failed', changed: false, error: detail },
|
|
432
|
+
taskComment: null,
|
|
433
|
+
verification: null
|
|
434
|
+
}, changed, detail);
|
|
435
|
+
}
|
|
436
|
+
const lifecycleDone = receipt.lifecycle === 'done' && resolved.ok && resolved.state === 'completed' && shortIds !== null;
|
|
437
|
+
if (lifecycleDone) {
|
|
438
|
+
lifecycleResult = { status: 'no-op', changed: false, error: null };
|
|
439
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
const result = lifecycle({ taskRef: taskId, intent: 'complete', agent: request.agent }, { repoRoot, ...(options.metadataProvider ? { metadataProvider: options.metadataProvider } : {}) });
|
|
443
|
+
lifecycleResult = lifecycleStep(result);
|
|
444
|
+
if (result.status !== 'applied' && result.status !== 'no-op') {
|
|
445
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: lifecycleResult.error });
|
|
446
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: null, verification: null }, result.changed, lifecycleResult.error);
|
|
447
|
+
}
|
|
448
|
+
changed = result.changed;
|
|
449
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'done', lastError: null });
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
const detail = errorOf(error, 'TASK_FINALIZATION_LIFECYCLE_FAILED');
|
|
454
|
+
try {
|
|
455
|
+
receipt = updateReceipt(repoRoot, receipt, { lifecycle: 'pending', lastError: detail });
|
|
456
|
+
}
|
|
457
|
+
catch { /* preserve the primary error */ }
|
|
458
|
+
return failed(taskId, detail, { lifecycle: lifecycleResult });
|
|
459
|
+
}
|
|
460
|
+
let taskComment = null;
|
|
461
|
+
try {
|
|
462
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
463
|
+
if (receipt.taskComment !== 'pending') {
|
|
464
|
+
taskComment = {
|
|
465
|
+
status: receipt.taskComment === 'skipped' ? 'skipped' : 'no-op',
|
|
466
|
+
changed: false,
|
|
467
|
+
error: null
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
const result = commentSync(taskId, { kind: 'task', agent: request.agent, cwd: repoRoot });
|
|
472
|
+
taskComment = commentStep(result);
|
|
473
|
+
if (result.status === 'applied' || result.status === 'no-op') {
|
|
474
|
+
const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
|
|
475
|
+
const capability = issueCapability(receipt, 'task-comment');
|
|
476
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
477
|
+
scope: 'task-comment', operation: 'succeeded', state: skipped ? 'skipped' : 'done'
|
|
478
|
+
}, consumedCapabilities);
|
|
479
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
480
|
+
taskComment = skipped ? { ...taskComment, status: 'skipped' } : taskComment;
|
|
481
|
+
changed = changed || result.changed;
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
const detail = taskComment.error ?? { code: 'COMMENT_SYNC_FAILED', message: 'task comment synchronization failed', retryable: true };
|
|
485
|
+
const capability = issueCapability(receipt, 'task-comment');
|
|
486
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
487
|
+
scope: 'task-comment', operation: 'failed', error: detail
|
|
488
|
+
}, consumedCapabilities);
|
|
489
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
490
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: null }, changed, taskComment.error);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
const detail = errorOf(error, 'COMMENT_SYNC_FAILED', true);
|
|
496
|
+
try {
|
|
497
|
+
const capability = issueCapability(receipt, 'task-comment');
|
|
498
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
499
|
+
scope: 'task-comment', operation: 'failed', error: detail
|
|
500
|
+
}, consumedCapabilities);
|
|
501
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
502
|
+
}
|
|
503
|
+
catch { /* preserve the primary error */ }
|
|
504
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment: { status: 'blocked', changed: false, error: detail }, verification: null }, changed, detail);
|
|
505
|
+
}
|
|
506
|
+
let verification = null;
|
|
507
|
+
try {
|
|
508
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
509
|
+
if (receipt.verification !== 'pending') {
|
|
510
|
+
verification = { status: 'no-op', changed: false, error: null };
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
const result = verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
|
|
514
|
+
verification = verificationStep(result);
|
|
515
|
+
if (result.status === 'pass') {
|
|
516
|
+
const capability = issueCapability(receipt, 'verification');
|
|
517
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
518
|
+
scope: 'verification', operation: 'succeeded'
|
|
519
|
+
}, consumedCapabilities);
|
|
520
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
|
|
524
|
+
const capability = issueCapability(receipt, 'verification');
|
|
525
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
526
|
+
scope: 'verification', operation: 'failed', error: detail
|
|
527
|
+
}, consumedCapabilities);
|
|
528
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
529
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, verification.error);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
const detail = errorOf(error, 'VERIFY_FAILED', true);
|
|
535
|
+
try {
|
|
536
|
+
const capability = issueCapability(receipt, 'verification');
|
|
537
|
+
receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
|
|
538
|
+
scope: 'verification', operation: 'failed', error: detail
|
|
539
|
+
}, consumedCapabilities);
|
|
540
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
541
|
+
}
|
|
542
|
+
catch { /* preserve the primary error */ }
|
|
543
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
|
|
544
|
+
}
|
|
545
|
+
receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
|
|
546
|
+
return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed);
|
|
547
|
+
}
|
|
548
|
+
function applyTaskFinalization(request, options) {
|
|
549
|
+
if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
|
|
550
|
+
return failed(null, { code: 'TASK_FINALIZATION_PAYLOAD_INVALID', message: 'complete finalization requires taskRef and agent', retryable: false });
|
|
551
|
+
}
|
|
552
|
+
const repoRoot = path.resolve(options.repoRoot);
|
|
553
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot });
|
|
554
|
+
if (!resolved.ok)
|
|
555
|
+
return failed(resolved.taskId, { code: resolved.code, message: resolved.message, retryable: false });
|
|
556
|
+
try {
|
|
557
|
+
return withTaskExecutionLock(repoRoot, resolved.taskId, 'task-finalization.complete', () => applyUnderLock(request, resolved.taskId, options));
|
|
558
|
+
}
|
|
559
|
+
catch (error) {
|
|
560
|
+
const detail = error instanceof TaskExecutionLockError
|
|
561
|
+
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
562
|
+
: errorOf(error, 'TASK_FINALIZATION_FAILED');
|
|
563
|
+
return failed(resolved.taskId, detail);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability };
|
|
567
|
+
//# sourceMappingURL=finalization.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
function warningKey(warning) {
|
|
2
|
+
return `${warning.step}\0${warning.code}\0${warning.target}`;
|
|
3
|
+
}
|
|
4
|
+
function mergeOperationWarnings(...groups) {
|
|
5
|
+
const merged = new Map();
|
|
6
|
+
for (const group of groups) {
|
|
7
|
+
for (const warning of group) {
|
|
8
|
+
const canonical = {
|
|
9
|
+
code: warning.code,
|
|
10
|
+
message: warning.message,
|
|
11
|
+
retryable: warning.retryable,
|
|
12
|
+
step: warning.step,
|
|
13
|
+
target: warning.target,
|
|
14
|
+
severity: warning.severity
|
|
15
|
+
};
|
|
16
|
+
merged.set(warningKey(canonical), canonical);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...merged.values()];
|
|
20
|
+
}
|
|
21
|
+
const RETRY_HINTS = {
|
|
22
|
+
COMMIT_AUTOPUSH_PROTECTED_BRANCH: 'Review the protected branch policy; push the local commit manually if delivery is intended.',
|
|
23
|
+
COMMIT_PUSH_FAILED: 'Retry the commit push-only path after fixing the remote push failure.',
|
|
24
|
+
PR_METADATA_SYNC_FAILED: 'Retry create-pr metadata synchronization; do not create another pull request.',
|
|
25
|
+
PR_SUMMARY_SYNC_FAILED: 'Retry create-pr summary synchronization; do not create another pull request.',
|
|
26
|
+
FINALIZATION_WARNING_PENDING: 'Retry complete-task to process the pending finalization step.'
|
|
27
|
+
};
|
|
28
|
+
function retryHintForWarning(warning) {
|
|
29
|
+
return RETRY_HINTS[warning.code]
|
|
30
|
+
?? `Retry the ${warning.step} operation after resolving ${warning.code}.`;
|
|
31
|
+
}
|
|
32
|
+
export { mergeOperationWarnings, retryHintForWarning, warningKey };
|
|
33
|
+
//# sourceMappingURL=operation-outcome.js.map
|