@fitlab-ai/agent-infra 0.12.0 → 0.12.1
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/dist/lib/internal/manual-validation.js +11 -17
- package/dist/lib/internal/platform-comment.js +56 -7
- package/dist/lib/platform/issue-comments.js +373 -267
- package/dist/lib/platform/issue-summary-content.js +112 -0
- package/dist/lib/platform/pr-change-report.js +175 -26
- package/dist/lib/platform/pr-summary-content.js +54 -0
- package/dist/lib/platform/pr-summary.js +318 -284
- package/dist/lib/platform/summary-content-pipeline.js +18 -0
- package/dist/lib/sandbox/removal.js +59 -2
- package/dist/lib/task/finalization.js +16 -7
- package/dist/lib/task/platform-operation-recovery.js +87 -87
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/manual-validation.ts +12 -18
- package/lib/internal/platform-comment.ts +46 -4
- package/lib/platform/issue-comments.ts +478 -280
- package/lib/platform/issue-summary-content.ts +132 -0
- package/lib/platform/pr-change-report.ts +165 -20
- package/lib/platform/pr-summary-content.ts +60 -0
- package/lib/platform/pr-summary.ts +364 -257
- package/lib/platform/summary-content-pipeline.ts +23 -0
- package/lib/sandbox/removal.ts +57 -1
- package/lib/task/finalization.ts +11 -6
- package/lib/task/platform-operation-recovery.ts +70 -81
- package/package.json +1 -1
- package/templates/.agents/rules/pr-sync.en.md +4 -2
- package/templates/.agents/rules/pr-sync.zh-CN.md +6 -9
- package/templates/.agents/skills/cancel-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
- 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/complete-manual-validation/reference/summary-update.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +10 -25
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +11 -26
- package/templates/.agents/skills/create-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -2
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -2
- 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/create-task/SKILL.en.md +0 -18
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +0 -18
- package/templates/.agents/skills/watch-pr/SKILL.en.md +1 -1
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type SummaryContentOperations<T> = Readonly<{
|
|
2
|
+
validate: (value: unknown) => T;
|
|
3
|
+
correct: (value: unknown) => unknown;
|
|
4
|
+
render: (value: T) => string;
|
|
5
|
+
}>;
|
|
6
|
+
|
|
7
|
+
function parse<T>(value: unknown, parser: (value: unknown) => T): T {
|
|
8
|
+
try {
|
|
9
|
+
return parser(value);
|
|
10
|
+
} catch (error) {
|
|
11
|
+
throw error instanceof Error ? error : new Error(String(error));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function runSummaryContentPipeline<T>(
|
|
16
|
+
value: unknown,
|
|
17
|
+
operations: SummaryContentOperations<T>
|
|
18
|
+
): Readonly<{ content: T; rendered: string }> {
|
|
19
|
+
try { parse(value, operations.validate); } catch { /* Source correction gets a chance to repair invalid structure. */ }
|
|
20
|
+
const corrected = operations.correct(value);
|
|
21
|
+
const content = parse(corrected, operations.validate);
|
|
22
|
+
return { content, rendered: operations.render(content) };
|
|
23
|
+
}
|
package/lib/sandbox/removal.ts
CHANGED
|
@@ -73,6 +73,41 @@ function isMissingPathError(error: unknown): boolean {
|
|
|
73
73
|
&& (error as { code?: unknown }).code === 'ENOENT');
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function removalErrorCode(error: unknown): string | null {
|
|
77
|
+
return error && typeof error === 'object' && 'code' in error
|
|
78
|
+
&& typeof (error as { code?: unknown }).code === 'string'
|
|
79
|
+
? (error as { code: string }).code
|
|
80
|
+
: null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function removalErrorMessage(error: unknown): string {
|
|
84
|
+
const code = removalErrorCode(error);
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
return code ? `${code}: ${message}` : message;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function addOwnerWriteToDirectories(root: string): void {
|
|
90
|
+
const pending = [root];
|
|
91
|
+
while (pending.length > 0) {
|
|
92
|
+
const current = pending.pop()!;
|
|
93
|
+
let stat: fs.Stats;
|
|
94
|
+
try {
|
|
95
|
+
stat = fs.lstatSync(current);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (isMissingPathError(error)) continue;
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (!stat.isDirectory()) continue;
|
|
101
|
+
|
|
102
|
+
fs.chmodSync(current, stat.mode | 0o200);
|
|
103
|
+
for (const entry of fs.readdirSync(current)) pending.push(path.join(current, entry));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isPermissionRemovalError(error: unknown): boolean {
|
|
108
|
+
return ['EACCES', 'EPERM', 'ENOTEMPTY'].includes(removalErrorCode(error) ?? '');
|
|
109
|
+
}
|
|
110
|
+
|
|
76
111
|
function canonicalPath(input: string): string {
|
|
77
112
|
try { return fs.realpathSync.native(input); }
|
|
78
113
|
catch { return path.resolve(input); }
|
|
@@ -1011,7 +1046,25 @@ async function removeUncheckedSandbox(
|
|
|
1011
1046
|
fs.rmSync(root, { recursive: true, force: true });
|
|
1012
1047
|
removeEmptyManagedParent(path.join(config.workspaceViewBase, config.project), root);
|
|
1013
1048
|
}
|
|
1014
|
-
|
|
1049
|
+
const toolRemovalFailures: string[] = [];
|
|
1050
|
+
for (const candidate of toolCandidates.flatMap(({ candidates }) => candidates)) {
|
|
1051
|
+
try {
|
|
1052
|
+
fs.rmSync(candidate, { recursive: true, force: true });
|
|
1053
|
+
continue;
|
|
1054
|
+
} catch (initialError) {
|
|
1055
|
+
if (isPermissionRemovalError(initialError)) {
|
|
1056
|
+
try {
|
|
1057
|
+
addOwnerWriteToDirectories(candidate);
|
|
1058
|
+
fs.rmSync(candidate, { recursive: true, force: true });
|
|
1059
|
+
continue;
|
|
1060
|
+
} catch (retryError) {
|
|
1061
|
+
toolRemovalFailures.push(`${candidate}: ${removalErrorMessage(initialError)}; retry failed: ${removalErrorMessage(retryError)}`);
|
|
1062
|
+
}
|
|
1063
|
+
} else {
|
|
1064
|
+
toolRemovalFailures.push(`${candidate}: ${removalErrorMessage(initialError)}`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1015
1068
|
for (const shell of shellConfigDirCandidates(config, effectiveBranch)) fs.rmSync(shell, { recursive: true, force: true });
|
|
1016
1069
|
if (shouldRemoveShare) fs.rmSync(sharePath, { recursive: true, force: true });
|
|
1017
1070
|
if (shouldRemoveWorktree) {
|
|
@@ -1025,6 +1078,9 @@ async function removeUncheckedSandbox(
|
|
|
1025
1078
|
assertBranchRemovalIdentity(config, effectiveBranch, worktreePermits);
|
|
1026
1079
|
runSafe('git', ['-C', config.repoRoot, 'branch', '-D', effectiveBranch]);
|
|
1027
1080
|
}
|
|
1081
|
+
if (toolRemovalFailures.length > 0) {
|
|
1082
|
+
throw new Error(`SANDBOX_TOOL_CLEANUP_INCOMPLETE:\n${toolRemovalFailures.map((failure) => ` - ${failure}`).join('\n')}`);
|
|
1083
|
+
}
|
|
1028
1084
|
releaseTaskBoundSandboxIdentity(config.repoRoot, target.workspace);
|
|
1029
1085
|
|
|
1030
1086
|
if (!options.quiet) p.outro('Sandbox removed');
|
package/lib/task/finalization.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from './operation-outcome.ts';
|
|
34
34
|
import { readSandboxControlManifest } from '../sandbox/control/lifecycle.ts';
|
|
35
35
|
import { validateSandboxControlIdentity } from '../sandbox/control/identity-sentinel.ts';
|
|
36
|
+
import { parseDeliverySummaryContent, renderDeliverySummaryContent } from '../platform/issue-summary-content.ts';
|
|
36
37
|
|
|
37
38
|
const RECEIPT_VERSION = 4 as const;
|
|
38
39
|
const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary'] as const;
|
|
@@ -730,16 +731,20 @@ function readDeliverySummary(taskDir: string, taskId: string): { body: string; s
|
|
|
730
731
|
} catch {
|
|
731
732
|
throw Object.assign(new Error('delivery summary staging record is unavailable'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
732
733
|
}
|
|
733
|
-
if (value.taskId !== taskId ||
|
|
734
|
+
if (value.taskId !== taskId || !value.content || typeof value.contentSha256 !== 'string') {
|
|
734
735
|
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
735
736
|
}
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
737
|
+
let content;
|
|
738
|
+
try { content = parseDeliverySummaryContent(value.content); }
|
|
739
|
+
catch { throw Object.assign(new Error('delivery summary staging payload is invalid'), { code: 'SUMMARY_STAGING_INVALID' }); }
|
|
740
|
+
if (content.taskId !== taskId || createHash('sha256').update(JSON.stringify(content)).digest('hex') !== value.contentSha256) {
|
|
741
|
+
throw Object.assign(new Error('delivery summary staging digest does not match payload'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
739
742
|
}
|
|
743
|
+
const body = renderDeliverySummaryContent(content);
|
|
744
|
+
const canonical = canonicalizeSummaryBody(body);
|
|
745
|
+
if (!canonical.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
740
746
|
const digest = createHash('sha256').update(canonical.value).digest('hex');
|
|
741
|
-
|
|
742
|
-
return { body: canonical.value, sha256: value.sha256 };
|
|
747
|
+
return { body: canonical.value, sha256: digest };
|
|
743
748
|
}
|
|
744
749
|
|
|
745
750
|
async function syncPendingSummary(input: {
|
|
@@ -3,13 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
|
|
6
|
-
import { inspectPlatformCommentOperation,
|
|
6
|
+
import { inspectPlatformCommentOperation, syncPlatformComment } from '../platform/issue-comments.ts';
|
|
7
|
+
import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
|
|
8
|
+
import { enumerateArtifacts } from './artifacts.ts';
|
|
7
9
|
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
|
|
8
10
|
import { resolvePlatformProviderContext } from '../platform/context.ts';
|
|
9
11
|
import { providerOperationContext, providerError, unsupportedProviderOperation } from '../platform/provider-bridge.ts';
|
|
10
12
|
import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
|
|
11
|
-
import { bindPlatformPullRequest,
|
|
12
|
-
import {
|
|
13
|
+
import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
14
|
+
import { replayPullRequestSummary } from '../platform/pr-summary.ts';
|
|
13
15
|
import { publishPrReview, readReviewBodyFile } from '../platform/pr-review.ts';
|
|
14
16
|
import type { PlatformClient } from '../platform/context.ts';
|
|
15
17
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
@@ -53,33 +55,57 @@ function result(status: RecoveryResult['status'], recovered: string[], pending:
|
|
|
53
55
|
function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
|
|
54
56
|
const file = path.join(taskDir, '.delivery-summary.json');
|
|
55
57
|
if (!fs.existsSync(file)) return null;
|
|
56
|
-
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8')) as { taskId?: unknown; content?: unknown; contentSha256?: unknown };
|
|
59
|
+
let content;
|
|
60
|
+
try { content = parseDeliverySummaryContent(data.content); }
|
|
61
|
+
catch { content = null; }
|
|
62
|
+
if (data.taskId !== taskId || !content || content.taskId !== taskId || typeof data.contentSha256 !== 'string'
|
|
63
|
+
|| createHash('sha256').update(JSON.stringify(content)).digest('hex') !== data.contentSha256) {
|
|
61
64
|
throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
62
65
|
}
|
|
63
|
-
|
|
66
|
+
const canonical = canonicalizeSummaryBody(renderDeliverySummaryContent(content));
|
|
67
|
+
if (!canonical?.ok) throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
|
|
68
|
+
return { body: canonical.value, sha256: createHash('sha256').update(canonical.value).digest('hex') };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cancellationPayload(taskDir: string, taskId: string): string | null {
|
|
72
|
+
const taskPath = path.join(taskDir, 'task.md');
|
|
73
|
+
if (!fs.existsSync(taskPath)) return null;
|
|
74
|
+
const taskContent = fs.readFileSync(taskPath, 'utf8');
|
|
75
|
+
const frontmatter = parseTaskFrontmatter(taskContent);
|
|
76
|
+
const title = /^#\s+(.+)$/mu.exec(taskContent)?.[1]?.trim() || taskId;
|
|
77
|
+
const artifacts = enumerateArtifacts(taskDir).filter((artifact) => artifact.name !== 'task.md').map((artifact) => artifact.name);
|
|
78
|
+
const content = parseCancelSummaryContent({
|
|
79
|
+
taskId,
|
|
80
|
+
title,
|
|
81
|
+
outcome: 'cancelled',
|
|
82
|
+
reason: frontmatter.cancel_reason,
|
|
83
|
+
cancelledAt: frontmatter.cancelled_at,
|
|
84
|
+
lastCompletedArtifact: artifacts.at(-1) ?? null
|
|
85
|
+
});
|
|
86
|
+
return renderCancelSummaryContent(content);
|
|
64
87
|
}
|
|
65
88
|
|
|
66
89
|
async function replayComment(taskId: string, taskDir: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
67
90
|
if (operation.kind === 'task-comment') {
|
|
68
|
-
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency,
|
|
91
|
+
return syncPlatformComment(taskId, { kind: 'task', agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
69
92
|
}
|
|
70
93
|
if (operation.kind === 'artifact-comment') {
|
|
71
|
-
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency,
|
|
94
|
+
return syncPlatformComment(taskId, { kind: 'artifact', artifact: operation.target, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
72
95
|
}
|
|
73
96
|
if (operation.kind === 'summary-comment') {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
});
|
|
97
|
+
let summary;
|
|
98
|
+
try { summary = summaryPayload(taskDir, taskId); }
|
|
99
|
+
catch (error) { return platformResult('failed', { error: { code: 'SUMMARY_STAGING_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
100
|
+
if (!summary) return platformResult('blocked', { error: { code: 'SUMMARY_STAGING_MISSING', message: 'delivery summary staging record is unavailable', retryable: true } });
|
|
101
|
+
return syncPlatformComment(taskId, { kind: 'summary', body: summary.body, agent, cwd, client, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency, queuedOperation: operation });
|
|
80
102
|
}
|
|
81
103
|
if (operation.kind === 'cancel-comment') {
|
|
82
|
-
|
|
104
|
+
let body: string | null;
|
|
105
|
+
try { body = cancellationPayload(taskDir, taskId); }
|
|
106
|
+
catch (error) { return platformResult('failed', { error: { code: 'CANCEL_SUMMARY_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } }); }
|
|
107
|
+
if (!body) return platformResult('blocked', { error: { code: 'CANCEL_SUMMARY_INVALID', message: 'Cancellation facts are unavailable for recovery', retryable: true } });
|
|
108
|
+
return syncPlatformComment(taskId, { kind: 'cancel', body, agent, cwd, client, dependency: operation.dependency, queuedOperation: operation });
|
|
83
109
|
}
|
|
84
110
|
return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_UNSUPPORTED', message: `operation kind '${operation.kind}' has no registered recovery handler`, retryable: false } });
|
|
85
111
|
}
|
|
@@ -183,55 +209,6 @@ async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof re
|
|
|
183
209
|
return platformResult('no-op');
|
|
184
210
|
}
|
|
185
211
|
|
|
186
|
-
async function replayPullRequestSummary(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
187
|
-
let identity;
|
|
188
|
-
try { identity = parseResourceIdentity(JSON.parse(operation.target), 'queued pull-request summary identity'); }
|
|
189
|
-
catch { return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Queued pull-request summary identity is invalid', retryable: false } }); }
|
|
190
|
-
const summary = operation.pullRequestSummary;
|
|
191
|
-
if (!summary) return platformResult('blocked', { error: {
|
|
192
|
-
code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request summary recovery content is missing', retryable: false
|
|
193
|
-
} });
|
|
194
|
-
const manualValidationHeading = /^###\s+(?:⏳\s+(?:Manual Validation Pending|人工验证待收尾)|✅\s+(?:Manual Validation Passed|人工验证已通过))\s*$/mu.test(summary.body);
|
|
195
|
-
if (manualValidationHeading && !summary.manualValidation) return platformResult('blocked', { error: {
|
|
196
|
-
code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'Queued manual-validation summary has no persisted transaction context', retryable: false
|
|
197
|
-
} });
|
|
198
|
-
const resolved = await resolvePlatformProviderContext({ cwd, client });
|
|
199
|
-
if (!resolved.ok) return resolved.context;
|
|
200
|
-
const listed = resolved.value.provider.comments?.list
|
|
201
|
-
? await resolved.value.provider.comments.list({ context: providerOperationContext(resolved.value), parent: identity })
|
|
202
|
-
: unsupportedProviderOperation(resolved.value.provider, 'comments.list');
|
|
203
|
-
if (!listed.ok) return platformResult(providerErrorRetryable(listed.error.code) ? 'blocked' : 'failed', { error: providerError(listed.error, 'PLATFORM_PROVIDER_OPERATION_FAILED') });
|
|
204
|
-
const marker = `<!-- sync-pr:${taskId}:summary -->`;
|
|
205
|
-
const summaries = listed.value.filter((comment) => normalizeCommentContent(comment.body).split('\n', 1)[0] === marker);
|
|
206
|
-
if (summaries.length > 1) return platformResult('blocked', { error: {
|
|
207
|
-
code: 'PR_SUMMARY_MARKER_AMBIGUOUS', message: 'Multiple pull-request comments contain the queued summary marker', retryable: false
|
|
208
|
-
} });
|
|
209
|
-
if (!summary.manualValidation && summaries.length === 1 && createHash('sha256').update(normalizeCommentContent(summaries[0]!.body)).digest('hex') === operation.expectedDigest) {
|
|
210
|
-
return platformResult('no-op');
|
|
211
|
-
}
|
|
212
|
-
const bound = await inspectPlatformPullRequest(taskId, { cwd, client });
|
|
213
|
-
if (!bound.pullRequest || !resourceIdentityEquals(bound.pullRequest.identity, identity)) return platformResult('blocked', { error: bound.error ?? {
|
|
214
|
-
code: 'PLATFORM_OPERATION_IDENTITY_MISMATCH', message: 'Bound pull request differs from the queued summary target', retryable: false
|
|
215
|
-
} });
|
|
216
|
-
const replayed = await syncPullRequestSummary(taskId, {
|
|
217
|
-
agent,
|
|
218
|
-
cwd,
|
|
219
|
-
client,
|
|
220
|
-
body: summary.body,
|
|
221
|
-
changeReportFile: summary.changeReportFile,
|
|
222
|
-
...(summary.manualValidation ? { manualValidation: summary.manualValidation } : {}),
|
|
223
|
-
primaryResult: 'no_op',
|
|
224
|
-
strict: true,
|
|
225
|
-
skipQueue: true
|
|
226
|
-
});
|
|
227
|
-
if ((replayed.status === 'applied' || replayed.status === 'no-op') && !replayed.error && replayed.warnings.length === 0) {
|
|
228
|
-
return platformResult(replayed.status, { changed: replayed.changed });
|
|
229
|
-
}
|
|
230
|
-
return platformResult('blocked', { error: replayed.error ?? {
|
|
231
|
-
code: 'PR_SUMMARY_RECOVERY_UNCONFIRMED', message: 'Pull-request summary replay did not confirm the requested remote state', retryable: true
|
|
232
|
-
} });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
212
|
async function replayPullRequestReview(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], agent: string, cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
236
213
|
const intent = operation.pullRequestReview;
|
|
237
214
|
if (!intent) return platformResult('failed', { error: { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID', message: 'Pull-request review recovery parameters are missing', retryable: false } });
|
|
@@ -280,6 +257,14 @@ async function replayPullRequest(taskId: string, operation: ReturnType<typeof re
|
|
|
280
257
|
return recoverCreatedPullRequest(taskId, { agent, cwd, base: intent.baseRef || '', head: intent.headRef || '', skipQueue: true });
|
|
281
258
|
}
|
|
282
259
|
|
|
260
|
+
function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], excludeId?: string): boolean {
|
|
261
|
+
if (operation.id === excludeId) return false;
|
|
262
|
+
if (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown') return true;
|
|
263
|
+
return operation.state === 'failed'
|
|
264
|
+
&& operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'
|
|
265
|
+
;
|
|
266
|
+
}
|
|
267
|
+
|
|
283
268
|
async function recoverPlatformOperationsOnce(
|
|
284
269
|
taskRef: string,
|
|
285
270
|
selection: 'required' | 'deferred' | 'all',
|
|
@@ -293,9 +278,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
293
278
|
const value = error as { code?: string; message?: string };
|
|
294
279
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
295
280
|
}
|
|
296
|
-
const candidates = journal.operations.filter((operation) => operation
|
|
297
|
-
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
298
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED')));
|
|
281
|
+
const candidates = journal.operations.filter((operation) => isRecoveryEligible(operation, options.excludeId));
|
|
299
282
|
if (!candidates.length) return result('no-op', [], []);
|
|
300
283
|
const head = candidates[0]!;
|
|
301
284
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
@@ -357,11 +340,18 @@ async function recoverPlatformOperationsOnce(
|
|
|
357
340
|
const commentOptions = operation.kind === 'task-comment'
|
|
358
341
|
? { kind: 'task' as const, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
359
342
|
: operation.kind === 'artifact-comment'
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
343
|
+
? { kind: 'artifact' as const, artifact: operation.target, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency }
|
|
344
|
+
: operation.kind === 'summary-comment' ? (() => {
|
|
345
|
+
try {
|
|
346
|
+
const summary = summaryPayload(resolved.taskDir, resolved.taskId);
|
|
347
|
+
return summary ? { kind: 'summary' as const, body: summary.body, agent: options.agent, cwd: resolved.repoRoot, summaryAuthorization: { sha256: summary.sha256 }, dependency: operation.dependency } : null;
|
|
348
|
+
} catch { return null; }
|
|
349
|
+
})() : (() => {
|
|
350
|
+
try {
|
|
351
|
+
const body = cancellationPayload(resolved.taskDir, resolved.taskId);
|
|
352
|
+
return body ? { kind: 'cancel' as const, body, agent: options.agent, cwd: resolved.repoRoot, dependency: operation.dependency } : null;
|
|
353
|
+
} catch { return null; }
|
|
354
|
+
})();
|
|
365
355
|
const current = commentOptions ? inspectPlatformCommentOperation(resolved.taskId, commentOptions) : null;
|
|
366
356
|
if (!current) {
|
|
367
357
|
pending.push(operation.id);
|
|
@@ -383,20 +373,21 @@ async function recoverPlatformOperationsOnce(
|
|
|
383
373
|
remote = operation.kind === 'issue-create'
|
|
384
374
|
? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
|
|
385
375
|
: operation.kind === 'pull-request-summary'
|
|
386
|
-
? await replayPullRequestSummary(resolved.taskId,
|
|
376
|
+
? await replayPullRequestSummary(resolved.taskId, targetOperation, { agent: options.agent, cwd: resolved.repoRoot, client: options.client })
|
|
387
377
|
: operation.kind === 'pull-request-review'
|
|
388
378
|
? await replayPullRequestReview(operation, options.agent, resolved.repoRoot, options.client)
|
|
389
379
|
: operation.kind === 'issue-metadata'
|
|
390
380
|
? await replayIssueMetadata(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
391
381
|
: operation.kind === 'pull-request'
|
|
392
382
|
? await replayPullRequest(resolved.taskId, targetOperation, options.agent, resolved.repoRoot)
|
|
393
|
-
: await replayComment(resolved.taskId, resolved.taskDir,
|
|
383
|
+
: await replayComment(resolved.taskId, resolved.taskDir, targetOperation, options.agent, resolved.repoRoot, options.client);
|
|
394
384
|
}
|
|
395
385
|
catch (error) {
|
|
396
386
|
const value = error as { code?: string; message?: string };
|
|
397
387
|
remote = platformResult('blocked', { error: { code: value.code || 'PLATFORM_OPERATION_RECOVERY_FAILED', message: value.message || String(error), retryable: true } });
|
|
398
388
|
}
|
|
399
389
|
const succeeded = (remote.status === 'applied' || remote.status === 'no-op') && !remote.error;
|
|
390
|
+
const latestOperation = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.find((item) => item.id === operation.id);
|
|
400
391
|
try {
|
|
401
392
|
recordPlatformOperation({
|
|
402
393
|
taskRef: resolved.taskId,
|
|
@@ -411,7 +402,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
411
402
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
412
403
|
dependency: operation.dependency,
|
|
413
404
|
state: succeeded ? 'succeeded' : 'unknown',
|
|
414
|
-
lastCode: remote.error?.code ?? null
|
|
405
|
+
lastCode: remote.error?.code ?? latestOperation?.lastCode ?? null
|
|
415
406
|
});
|
|
416
407
|
} catch (error) {
|
|
417
408
|
const value = error as { code?: string; message?: string };
|
|
@@ -441,9 +432,7 @@ async function recoverPlatformOperations(
|
|
|
441
432
|
const value = error as { code?: string; message?: string };
|
|
442
433
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
443
434
|
}
|
|
444
|
-
const eligible = (operation: typeof initial.operations[number]) => operation
|
|
445
|
-
&& (operation.state === 'queued' || operation.state === 'pending' || operation.state === 'unknown'
|
|
446
|
-
|| (operation.state === 'failed' && operation.lastCode !== 'PLATFORM_OPERATION_SUPERSEDED'));
|
|
435
|
+
const eligible = (operation: typeof initial.operations[number]) => isRecoveryEligible(operation, options.excludeId);
|
|
447
436
|
const first = initial.operations.filter(eligible);
|
|
448
437
|
if (!first.length) return result('no-op', [], []);
|
|
449
438
|
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@ agent-infra-internal platform-pr summary-sync {task-id} \
|
|
|
25
25
|
--result {primary-result}
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Use only canonical inputs returned by the context command. The precheck must contain file evidence for six fixed checks; `formalReview` is always false and `needs-review` routes to `review-code`.
|
|
28
|
+
Use only canonical inputs returned by the context command. The precheck must contain file evidence for six fixed checks; `formalReview` is always false and `needs-review` routes to `review-code`. Generate a typed JSON payload with `overview:string`, `tests:{state,detail}`, `review:{state,detail}`, `manualValidation:{state,detail}`, `humanDecisions:{open,resolved,detail}`, and `risks:string`. Detail fields are non-empty, counts are non-negative integers, and structure checks do not assess semantic quality. Use `not-started`/`pending` for tests that have not started, `none`/`not-observed`/`not-published` for absent or unobserved remote Reviews, and `pending` for manual validation that has not run. Human-decision counts come from the task ledger. The renderer supplies fixed sections and the canonical report placeholder. The core validates the task-intent digest, bound PR identity, complete patch SHA, and mechanical totals before rendering the report, then owns markers, HEAD metadata, and create/update/no-op behavior.
|
|
29
29
|
|
|
30
30
|
The canonical comment envelope produced by the core is:
|
|
31
31
|
|
|
@@ -40,7 +40,9 @@ The canonical comment envelope produced by the core is:
|
|
|
40
40
|
### PR Code Changes
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Canonical inputs include `manual-validation.md`. The manual-validation
|
|
43
|
+
Canonical inputs include `manual-validation.md`. The manual-validation `state` can be `required`, `pending`, `passed`, `none`/`not-required`, or `not-observed`. English headings such as `### ⚠️ Manual Verification Required`, `### ✅ Manual Validation Passed`, and `### ✅ No Manual Verification Needed` are documentation labels; the current renderer emits Chinese headings. `complete-manual-validation` refreshes the same summary in place. The PR code-change section uses authoritative base/head facts from `platform-pr inspect` and the complete `git diff --find-renames --numstat base...head`, reconciles runtime, test, skill/rule, template, documentation, and other categories, and explains renames, mechanical mirrors, and potentially unnecessary changes. The core renderer supplies the canonical `### PR Code Changes` section. After publishing, the core reads comments back and requires one summary marker with a body that exactly matches the canonical rendered body; mismatch or read failure uses the existing coordinator/recovery path.
|
|
44
|
+
|
|
45
|
+
Examples include `required`, `pending`, `passed`, `none`/`not-required`, and `not-observed`; this list is illustrative, not exhaustive. The renderer also accepts `unavailable` and other non-empty states through its existing fallback. English headings below are documentation labels; the current renderer emits the stated Chinese headings. `required` retains each item, location, and reason under `### ⚠️ 需人工校验`. `pending` means validation has not started or remains incomplete and renders as `### ⏳ 人工验证待收尾`. `passed` (`### ✅ Manual Validation Passed` in this documentation; `### ✅ 人工验证已通过` from the renderer) is publishable only through the final `complete-manual-validation` coordinator after it writes a valid receipt. Preserve the receipt marker bound to the transaction, receipt, evidence, and current PR head; never infer passed status from summary text or another state. `none`/`not-required` renders as `### ✅ 无需人工校验` only when no manual item is required and carries no passed receipt. `not-observed` means the state has not been read and renders as `### ℹ️ 尚未读取人工校验状态`.
|
|
44
46
|
|
|
45
47
|
```bash
|
|
46
48
|
agent-infra-internal task-warning {task-id} add --step {step} --severity WARNING \
|
|
@@ -18,17 +18,14 @@ PR 摘要的语义聚合由模型完成;任务意图 digest、完整三点 dif
|
|
|
18
18
|
agent-infra-internal platform-pr summary-context {task-id}
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
只使用返回的 canonical latest `plan*`、`review-plan*`、`code*`、`review-code*`、`manual-validation*`(包括 `manual-validation.md
|
|
21
|
+
只使用返回的 canonical latest `plan*`、`review-plan*`、`code*`、`review-code*`、`manual-validation*`(包括 `manual-validation.md`)。模型生成 PR 专属 JSON payload:`overview:string`、`tests:{state,detail}`、`review:{state,detail}`、`manualValidation:{state,detail}`、`humanDecisions:{open,resolved,detail}`、`risks:string`。说明字段为非空字符串,计数为非负整数;结构校验不评价内容语义。
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
- 测试结果
|
|
25
|
-
- 审查历程与当前结论
|
|
26
|
-
- 人工校验三态:`### ⚠️ 需人工校验`、`### ✅ 人工验证已通过` 或 `### ✅ 无需人工校验`
|
|
27
|
-
- 变更摘要、实现范围、测试结果、审查历程和人工校验三态
|
|
28
|
-
- 一个 `<!-- canonical-pr-change-report -->` 占位符;`### PR 代码增减` 由 core renderer 基于权威 diff 生成,调用方不自行写标题或统计
|
|
23
|
+
测试尚未启动用 `not-started`/`pending`,暂无远端 Review 用 `none`/`not-observed`/`not-published`,待人工校验用 `pending`,不得渲染为通过或零值。决策计数来自当前任务账本。renderer 固定生成完整章节并加入一个 canonical 报告占位符;代码增减仍由现有 report renderer 输出。
|
|
29
24
|
|
|
30
25
|
保留人工校验项时,每条写明校验内容、定位和只能人工完成的原因。
|
|
31
26
|
|
|
27
|
+
例如可见状态包括 `required`、`pending`、`passed`、`none`/`not-required` 和 `not-observed`;这些是示例而非穷举。renderer 也接受 `unavailable` 及其他非空状态,并按现有 fallback 渲染。英文标题是文档标签;当前 renderer 输出下列中文标题:`required` 使用 `### ⚠️ 需人工校验` 并保留每项内容、定位和原因;`pending` 使用 `### ⏳ 人工验证待收尾`;`passed` 使用 `### ✅ 人工验证已通过`,但仅允许 `complete-manual-validation` 的 final coordinator 写入有效 receipt 后发布,并保留绑定 transaction、receipt、evidence 和当前 PR head 的凭据标记;不得根据摘要文字或其他状态推断通过。`none`/`not-required` 仅在无需人工校验时使用 `### ✅ 无需人工校验`,不附加通过凭据;`not-observed` 使用 `### ℹ️ 尚未读取人工校验状态`。
|
|
28
|
+
|
|
32
29
|
core 最终包装出的 canonical 评论结构为:
|
|
33
30
|
|
|
34
31
|
```markdown
|
|
@@ -54,7 +51,7 @@ precheck 必须覆盖六项固定检查并提供文件证据;`formalReview`
|
|
|
54
51
|
|
|
55
52
|
## 发布
|
|
56
53
|
|
|
57
|
-
|
|
54
|
+
把聚合后的 JSON payload 写入 `{summary-body-file}`,不要自行添加 marker 或 commit SHA:
|
|
58
55
|
|
|
59
56
|
```bash
|
|
60
57
|
agent-infra-internal platform-pr summary-sync {task-id} \
|
|
@@ -63,7 +60,7 @@ agent-infra-internal platform-pr summary-sync {task-id} \
|
|
|
63
60
|
--result {primary-result}
|
|
64
61
|
```
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
core 校验结构后插入报告占位符,重新校验任务意图 digest、PR identity、完整 patch SHA 和机械统计,生成报告段,再包装 marker 与权威 HEAD。发布后使用 `comments.list` 回读并要求唯一 marker 和正文完全匹配;不符或读取失败沿用现有 coordinator/recovery。payload 通过文件输入,调用方不得拼 shell/heredoc。
|
|
67
64
|
|
|
68
65
|
## 结果回传
|
|
69
66
|
|
|
@@ -83,7 +83,7 @@ If a valid `platform_issue_identity` exists, require `applied` / `no-op` without
|
|
|
83
83
|
- Write the cancellation body to a temporary file and run `agent-infra-internal platform-comment sync {task-id} --kind cancel --body-file {path} --agent {standard-agent-token}`
|
|
84
84
|
- Run `agent-infra-internal platform-comment sync {task-id} --kind task --agent {standard-agent-token}`
|
|
85
85
|
|
|
86
|
-
The cancellation comment must include at least:
|
|
86
|
+
The cancellation comment JSON fields are `taskId`, `title`, `outcome:"cancelled"`, `reason`, `cancelledAt`, and `lastCompletedArtifact`, sourced from task identity, task.md, the recorded cancellation reason/time, and the current artifact registry (`null` when no completed artifact exists). It must include at least:
|
|
87
87
|
- the cancellation reason
|
|
88
88
|
- the selected `status:` label
|
|
89
89
|
|
|
@@ -82,7 +82,7 @@ ls <task-workspace>/completed/{task-id}/task.md
|
|
|
82
82
|
- 将取消正文写入临时文件,调用 `agent-infra-internal platform-comment sync {task-id} --kind cancel --body-file {path} --agent {standard-agent-token}`
|
|
83
83
|
- 调用 `agent-infra-internal platform-comment sync {task-id} --kind task --agent {standard-agent-token}`
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
取消评论 JSON 字段为 `taskId`、`title`、`outcome:"cancelled"`、`reason`、`cancelledAt`、`lastCompletedArtifact`,值来自任务身份、task.md、已记录取消原因与时间和当前 artifact registry;无已完成产物时用 `null`。取消评论至少包含:
|
|
86
86
|
- 取消原因
|
|
87
87
|
- 选定的 `status:` label
|
|
88
88
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Run only when `{task-id}` is valid and task.md has a `bound/verified` `pr_delivery_fact`.
|
|
4
4
|
|
|
5
5
|
1. Run `agent-infra-internal platform-pr summary-context {task-id}` to obtain canonical inputs and report status; rebuild a stale/missing sidecar through the `change-report` flow for the current head.
|
|
6
|
-
2. Generate the
|
|
6
|
+
2. Generate the complete PR summary JSON payload per `.agents/rules/pr-sync.md` and write it to a temporary file.
|
|
7
7
|
3. Run `agent-infra-internal platform-pr summary-sync {task-id} --agent {standard-agent-token} --body-file {summary-body-file} --change-report-file <task-workspace>/active/{task-id}/pr-change-report.json --result no_op`. The commit path only synchronizes an existing PR summary and neither creates nor reuses a PR, so `no_op` explicitly represents the unchanged PR identity.
|
|
8
8
|
|
|
9
9
|
Failure records a warning but never blocks or rolls back the completed commit. `no-op` maps to `summary skipped (no diff)`.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
仅当 `{task-id}` 有效且 `task.md` 存在 `bound/verified` 的 `pr_delivery_fact` 时执行;否则跳过。
|
|
4
4
|
|
|
5
5
|
1. 调用 `agent-infra-internal platform-pr summary-context {task-id}` 获取 canonical 聚合输入和报告状态;sidecar stale/missing 时先按 `change-report` 流程重建当前 head 报告。
|
|
6
|
-
2. 按 `.agents/rules/pr-sync.md`
|
|
6
|
+
2. 按 `.agents/rules/pr-sync.md` 生成完整 PR 摘要 JSON payload 并写入临时文件;字段和零值状态由内容合同规定。
|
|
7
7
|
3. 调用 `agent-infra-internal platform-pr summary-sync {task-id} --agent {standard-agent-token} --body-file {summary-body-file} --change-report-file <task-workspace>/active/{task-id}/pr-change-report.json --result no_op`。commit 路径只同步已有 PR 摘要,不创建或复用 PR,因此固定使用 `no_op` 表示 PR 身份未变化。
|
|
8
8
|
|
|
9
9
|
同步失败只记录 warning,不阻塞或回滚已经完成的 commit。`no-op` 回传 `summary skipped (no diff)`。
|
|
@@ -4,7 +4,7 @@ Run only for a valid `{task-id}` whose task.md binds a verified `pr_delivery_fac
|
|
|
4
4
|
|
|
5
5
|
Follow `.agents/rules/pr-sync.md` for the shared summary structure and failure semantics.
|
|
6
6
|
|
|
7
|
-
1. Run `agent-infra-internal platform-pr summary-context {task-id}` to obtain the
|
|
7
|
+
1. Run `agent-infra-internal platform-pr summary-context {task-id}` to obtain canonical summary facts. `--summary-file` uses the complete PR summary JSON payload; the maintainer's PR validation comment and validation summary are the human-validation authority.
|
|
8
8
|
2. Before creating the canonical `manual-validation*` artifact, call the transaction coordinator in prepare mode to record `started` before writing the prepared transaction:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
@@ -13,7 +13,7 @@ agent-infra-internal manual-validation transaction {task-id} --prepare \
|
|
|
13
13
|
--summary-file {summary-body-file} --agent {standard-agent-token}
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
3. After prepare succeeds, write the canonical `manual-validation*` artifact,
|
|
16
|
+
3. After prepare succeeds, write the canonical `manual-validation*` artifact, set the JSON payload's `manualValidation.state` to `pending` with a non-empty explanation, and run the transaction coordinator:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
agent-infra-internal manual-validation transaction {task-id} \
|
package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
摘要结构与失败语义统一遵循 `.agents/rules/pr-sync.md`。
|
|
6
6
|
|
|
7
|
-
1. 调用 `agent-infra-internal platform-pr summary-context {task-id}`,取得 canonical
|
|
7
|
+
1. 调用 `agent-infra-internal platform-pr summary-context {task-id}`,取得 canonical 摘要事实;`--summary-file` 使用完整 PR 摘要 JSON payload,维护者提供的 PR 人工验证留言和验证说明作为完成人工校验依据。
|
|
8
8
|
2. 在创建 canonical `manual-validation*` artifact 前,调用 transaction coordinator 的 prepare 模式,先登记 started,再写 prepared transaction:
|
|
9
9
|
|
|
10
10
|
```bash
|
|
@@ -13,7 +13,7 @@ agent-infra-internal manual-validation transaction {task-id} --prepare \
|
|
|
13
13
|
--summary-file {summary-body-file} --agent {standard-agent-token}
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
3. prepare 成功后写入 canonical `manual-validation*` artifact
|
|
16
|
+
3. prepare 成功后写入 canonical `manual-validation*` artifact,并将 JSON payload 中 `manualValidation` 设置为 `pending`、提供非空说明后写入临时文件,调用 transaction coordinator:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
agent-infra-internal manual-validation transaction {task-id} \
|
|
@@ -18,8 +18,7 @@ Host finalization uses receipt v4 with an immutable `receiptId`, monotonic `revi
|
|
|
18
18
|
|
|
19
19
|
Before generating a completion report or synchronized content, read `.agents/rules/evidence-reporting.md`. Successful checks record the command, scope, status or structured result, actual result, and uncovered parts; failures, blocking conditions, or disputes retain a reproducible entry point, exact location, and decisive excerpt.
|
|
20
20
|
|
|
21
|
-
-
|
|
22
|
-
- Do not move a task that has incomplete workflow steps unless forced
|
|
21
|
+
- Host finalization updates task metadata and atomically moves the task directory
|
|
23
22
|
- The entry point accepts optional `--external-pr <N>` only to select among ambiguous external-delivery candidates; it never bypasses identity or platform gates
|
|
24
23
|
|
|
25
24
|
Version stamp rule: when creating or updating `task.md` frontmatter, read `.agents/rules/version-stamp.md` first and write or refresh `agent_infra_version`.
|
|
@@ -142,7 +141,7 @@ Check whether task.md has a valid `platform_issue_identity`. If it does not, ski
|
|
|
142
141
|
When a valid `platform_issue_identity` exists, execute in this exact order:
|
|
143
142
|
|
|
144
143
|
1. Run `agent-infra-internal platform-issue sync {task-id} --agent {standard-agent-token} --requirements --fields`.
|
|
145
|
-
2. Write the
|
|
144
|
+
2. Write the completion summary as JSON fields `taskId`, `title`, `summary`, `artifacts`, and `verification:{state,detail}`, then run `agent-infra-internal platform-comment stage-summary {task-id} --body-file {path}`. Use the current artifact registry (`[]` when empty). If final verification has not run, use `state:"not-run"` with a non-empty explanation. The command persists the normalized payload; do not publish the summary comment in this step.
|
|
146
145
|
|
|
147
146
|
The summary body records only delivery results, the associated PR and actual merge facts, verification commands with their scope/results/uncovered areas, and known todo items supported by current task evidence. Include the related commit when it helps explain verification scope. Do not infer unsupported facts; list unfinished work as a todo. This is an Issue closeout summary, not a PR comment.
|
|
148
147
|
|
|
@@ -225,32 +224,18 @@ ai sandbox rm {task-id} # or use the task's retained short id
|
|
|
225
224
|
|
|
226
225
|
## Completion Checklist
|
|
227
226
|
|
|
228
|
-
- [ ]
|
|
229
|
-
- [ ]
|
|
230
|
-
- [ ]
|
|
231
|
-
- [ ]
|
|
232
|
-
- [ ] Informed user of completion
|
|
227
|
+
- [ ] Hard prerequisites are satisfied and the pre-completion gate passes
|
|
228
|
+
- [ ] Host finalization returns `result=completed` or `result=completed_with_warnings`
|
|
229
|
+
- [ ] Completion verification passes
|
|
230
|
+
- [ ] The structured completion result and applicable warnings are reported to the user
|
|
233
231
|
|
|
234
232
|
## Notes
|
|
235
233
|
|
|
236
|
-
|
|
237
|
-
- Code is written but not committed
|
|
238
|
-
- Code is committed but not reviewed
|
|
239
|
-
- Review found blockers that haven't been fixed
|
|
240
|
-
- PR is created but not merged
|
|
241
|
-
- Manual validation items are incomplete
|
|
242
|
-
|
|
243
|
-
2. **Rollback**: If a task was incorrectly moved:
|
|
244
|
-
```bash
|
|
245
|
-
mv <task-workspace>/completed/{task-id} <task-workspace>/active/{task-id}
|
|
246
|
-
```
|
|
247
|
-
Then update task.md status back to `active`.
|
|
248
|
-
|
|
249
|
-
3. **Multiple contributors**: If multiple AI agents worked on the task, ensure all contributions are committed before completing.
|
|
234
|
+
- When multiple AI agents contributed, ensure all contributions are included in the local checkpoint.
|
|
250
235
|
|
|
251
236
|
## Error Handling
|
|
252
237
|
|
|
253
238
|
- Task not found: Prompt "Task {task-id} not found in active directory"
|
|
254
|
-
- Already completed:
|
|
255
|
-
- Task is blocked:
|
|
256
|
-
-
|
|
239
|
+
- Already completed: Enter `finalization-retry` only when a matching Complete Task Activity Log exists; otherwise report incomplete terminal identity and stop
|
|
240
|
+
- Task is blocked: Satisfy the recorded unblock condition, then reactivate it through `task-lifecycle activate` before retrying
|
|
241
|
+
- Finalization failed or blocked: Preserve source data and the receipt, then recover through the same finalization entry point using its structured error; do not manually rewrite terminal state or move the task directory
|