@fitlab-ai/agent-infra 0.9.14 → 0.9.15
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 +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
package/lib/platform/issues.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PlatformIssueSnapshot as IssueSnapshot } from './snapshots.ts';
|
|
2
|
+
import type { IssueFieldSchema } from './github-data.ts';
|
|
1
3
|
import fs from 'node:fs';
|
|
2
4
|
import path from 'node:path';
|
|
3
5
|
import { execFileSync } from 'node:child_process';
|
|
@@ -38,26 +40,7 @@ import {
|
|
|
38
40
|
import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.ts';
|
|
39
41
|
import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.ts';
|
|
40
42
|
import type { ResourceIdentity } from './resource-identity.ts';
|
|
41
|
-
|
|
42
|
-
type GitHubClient = PlatformClient;
|
|
43
43
|
import type { IssueSnapshot as ProviderIssueSnapshot, RepositoryMetadataSnapshot } from './provider-contract.ts';
|
|
44
|
-
|
|
45
|
-
type IssueSnapshot = {
|
|
46
|
-
repository: string;
|
|
47
|
-
number: number;
|
|
48
|
-
identity?: ResourceIdentity;
|
|
49
|
-
databaseId: number | null;
|
|
50
|
-
nodeId: string;
|
|
51
|
-
url: string;
|
|
52
|
-
state: 'open' | 'closed';
|
|
53
|
-
title: string;
|
|
54
|
-
body: string;
|
|
55
|
-
labels: string[];
|
|
56
|
-
assignees: string[];
|
|
57
|
-
milestone: string | null;
|
|
58
|
-
issueType: string | null;
|
|
59
|
-
fields: Record<string, string | number | null>;
|
|
60
|
-
};
|
|
61
44
|
type IssueResult = PlatformResult & {
|
|
62
45
|
task: { id: string | null; issueNumber: number | null };
|
|
63
46
|
issue: IssueSnapshot | null;
|
|
@@ -80,33 +63,6 @@ type SyncOptions = SharedOptions & {
|
|
|
80
63
|
dryRun?: boolean;
|
|
81
64
|
};
|
|
82
65
|
|
|
83
|
-
type RemoteIssue = {
|
|
84
|
-
number?: number;
|
|
85
|
-
id?: number;
|
|
86
|
-
node_id?: string;
|
|
87
|
-
html_url?: string;
|
|
88
|
-
state?: string;
|
|
89
|
-
title?: string;
|
|
90
|
-
body?: string | null;
|
|
91
|
-
labels?: Array<string | { name?: string }>;
|
|
92
|
-
assignees?: Array<{ login?: string }>;
|
|
93
|
-
milestone?: { title?: string } | null;
|
|
94
|
-
type?: { name?: string } | null;
|
|
95
|
-
pull_request?: unknown;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
type IssueFieldSchema = {
|
|
99
|
-
id: string;
|
|
100
|
-
name: string;
|
|
101
|
-
kind: 'single-select' | 'date' | 'text' | 'number';
|
|
102
|
-
options: Array<{ id: string; name: string }>;
|
|
103
|
-
};
|
|
104
|
-
type IssueTypeSchema = { id: string; name: string; fields: IssueFieldSchema[] };
|
|
105
|
-
type CurrentField = { id: string; name: string; kind: IssueFieldSchema['kind']; value: string | number | null };
|
|
106
|
-
|
|
107
|
-
const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
|
|
108
|
-
const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){id issueType{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{id name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{id name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{id name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{id name}}}}}}}}`;
|
|
109
|
-
|
|
110
66
|
function result(
|
|
111
67
|
status: PlatformResult['status'],
|
|
112
68
|
taskId: string | null,
|
|
@@ -235,118 +191,6 @@ async function inspectExternalIssue(
|
|
|
235
191
|
});
|
|
236
192
|
}
|
|
237
193
|
|
|
238
|
-
function normalizeIssue(remote: RemoteIssue, repository: string, fallbackNumber?: number): IssueSnapshot | null {
|
|
239
|
-
const number = Number.isInteger(remote.number) && Number(remote.number) > 0
|
|
240
|
-
? Number(remote.number)
|
|
241
|
-
: fallbackNumber;
|
|
242
|
-
if (!number || !Number.isSafeInteger(number) || remote.pull_request) return null;
|
|
243
|
-
return {
|
|
244
|
-
repository,
|
|
245
|
-
number,
|
|
246
|
-
databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
|
|
247
|
-
nodeId: remote.node_id || `issue-${number}`,
|
|
248
|
-
url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
|
|
249
|
-
state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
|
|
250
|
-
title: remote.title || '',
|
|
251
|
-
body: remote.body || '',
|
|
252
|
-
labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
253
|
-
assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
|
|
254
|
-
milestone: remote.milestone?.title || null,
|
|
255
|
-
issueType: remote.type?.name || null,
|
|
256
|
-
fields: {}
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function fieldKind(value: { __typename?: string }): IssueFieldSchema['kind'] | null {
|
|
261
|
-
if (value.__typename === 'IssueFieldSingleSelect' || value.__typename === 'IssueFieldSingleSelectValue') return 'single-select';
|
|
262
|
-
if (value.__typename === 'IssueFieldDate' || value.__typename === 'IssueFieldDateValue') return 'date';
|
|
263
|
-
if (value.__typename === 'IssueFieldText' || value.__typename === 'IssueFieldTextValue') return 'text';
|
|
264
|
-
if (value.__typename === 'IssueFieldNumber' || value.__typename === 'IssueFieldNumberValue') return 'number';
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function normalizeFieldSchemas(value: unknown): IssueFieldSchema[] {
|
|
269
|
-
if (!Array.isArray(value)) return [];
|
|
270
|
-
return value.flatMap((raw) => {
|
|
271
|
-
const item = raw as { id?: string; name?: string; __typename?: string; options?: Array<{ id?: string; name?: string }> };
|
|
272
|
-
const kind = fieldKind(item);
|
|
273
|
-
return item.id && item.name && kind ? [{
|
|
274
|
-
id: item.id,
|
|
275
|
-
name: item.name,
|
|
276
|
-
kind,
|
|
277
|
-
options: (item.options || []).flatMap((option) => option.id && option.name ? [{ id: option.id, name: option.name }] : [])
|
|
278
|
-
}] : [];
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function normalizeIssueTypes(value: unknown): IssueTypeSchema[] {
|
|
283
|
-
const nodes = (value as { data?: { organization?: { issueTypes?: { nodes?: unknown[] } } } })?.data?.organization?.issueTypes?.nodes;
|
|
284
|
-
return (nodes || []).flatMap((raw) => {
|
|
285
|
-
const item = raw as { id?: string; name?: string; pinnedFields?: unknown[] };
|
|
286
|
-
return item.id && item.name ? [{ id: item.id, name: item.name, fields: normalizeFieldSchemas(item.pinnedFields) }] : [];
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function normalizeCurrentFields(value: unknown): { issueId: string | null; type: IssueTypeSchema | null; values: CurrentField[] } {
|
|
291
|
-
const issue = (value as { data?: { repository?: { issue?: {
|
|
292
|
-
id?: string;
|
|
293
|
-
issueType?: { id?: string; name?: string; pinnedFields?: unknown[] } | null;
|
|
294
|
-
issueFieldValues?: { nodes?: unknown[] };
|
|
295
|
-
} } } })?.data?.repository?.issue;
|
|
296
|
-
const type = issue?.issueType?.name ? {
|
|
297
|
-
id: issue.issueType.id || issue.issueType.name,
|
|
298
|
-
name: issue.issueType.name,
|
|
299
|
-
fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
|
|
300
|
-
} : null;
|
|
301
|
-
const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
|
|
302
|
-
const item = raw as { __typename?: string; name?: string; value?: string | number; field?: { id?: string; name?: string } };
|
|
303
|
-
const kind = fieldKind(item);
|
|
304
|
-
const value = kind === 'single-select' ? item.name : item.value;
|
|
305
|
-
return item.field?.name && kind && value !== undefined
|
|
306
|
-
? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
|
|
307
|
-
: [];
|
|
308
|
-
});
|
|
309
|
-
return { issueId: issue?.id || null, type, values };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function graphState(client: GitHubClient, repository: string, issue: number, cwd: string) {
|
|
313
|
-
const [owner, name] = repository.split('/');
|
|
314
|
-
if (!owner || !name) return null;
|
|
315
|
-
const current = client.json<unknown>([
|
|
316
|
-
'api', 'graphql', '-f', `query=${ISSUE_FIELDS_QUERY}`, '-F', `owner=${owner}`, '-F', `name=${name}`, '-F', `number=${issue}`
|
|
317
|
-
], { cwd });
|
|
318
|
-
if (!current.ok) return null;
|
|
319
|
-
return normalizeCurrentFields(current.value);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function fetchIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
|
|
323
|
-
return client.json<RemoteIssue>(['api', `repos/${repository}/issues/${issue}`], { cwd });
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function inspectGitHubIssue(client: GitHubClient, repository: string, issue: number, cwd: string) {
|
|
327
|
-
const fetched = fetchIssue(client, repository, issue, cwd);
|
|
328
|
-
if (!fetched.ok) return fetched;
|
|
329
|
-
const snapshot = normalizeIssue(fetched.value, repository, issue);
|
|
330
|
-
return snapshot
|
|
331
|
-
? { ok: true as const, value: snapshot }
|
|
332
|
-
: { ok: false as const, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function inspectGitHubIssueMetadata(client: GitHubClient, repository: string, issue: number, cwd: string) {
|
|
336
|
-
const fetched = fetchIssue(client, repository, issue, cwd);
|
|
337
|
-
if (!fetched.ok) return fetched;
|
|
338
|
-
const remote = fetched.value;
|
|
339
|
-
return {
|
|
340
|
-
ok: true as const,
|
|
341
|
-
value: {
|
|
342
|
-
state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' as const : 'open' as const,
|
|
343
|
-
labels: (remote.labels || []).map((label: string | { name?: string }) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
344
|
-
body: remote.body || '',
|
|
345
|
-
milestone: remote.milestone?.title || null
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
194
|
async function inspectPlatformIssue(taskRef: string, options: SharedOptions = {}): Promise<IssueResult> {
|
|
351
195
|
const base = await resolvedContext(taskRef, options);
|
|
352
196
|
if (!base.ok) return base.output;
|
|
@@ -543,14 +387,6 @@ function requirementsFromTask(content: string): Requirement[] {
|
|
|
543
387
|
}));
|
|
544
388
|
}
|
|
545
389
|
|
|
546
|
-
function flattenPages(value: unknown): unknown[] {
|
|
547
|
-
return Array.isArray(value) ? value.flatMap((item) => Array.isArray(item) ? item : [item]) : [];
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function listNames(value: unknown): string[] {
|
|
551
|
-
return flattenPages(value).map((item) => typeof item === 'string' ? item : String((item as { name?: string }).name || '')).filter(Boolean);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
390
|
function milestoneBasis(repoRoot: string, current: string | null): string | null {
|
|
555
391
|
if (!current || !/^\d+\.\d+\.x$/.test(current)) return current;
|
|
556
392
|
try {
|
|
@@ -642,73 +478,6 @@ function desiredFieldValues(frontmatter: Record<string, string>, fields: IssueFi
|
|
|
642
478
|
return desired;
|
|
643
479
|
}
|
|
644
480
|
|
|
645
|
-
function planGraphMetadata(
|
|
646
|
-
client: GitHubClient,
|
|
647
|
-
repository: string,
|
|
648
|
-
issue: number,
|
|
649
|
-
cwd: string,
|
|
650
|
-
frontmatter: Record<string, string>,
|
|
651
|
-
options: Pick<SyncOptions, 'issueType' | 'fields'>,
|
|
652
|
-
capabilities: PlatformResult['capabilities']
|
|
653
|
-
): { operations: PlannedOperation[]; issueId: string | null } {
|
|
654
|
-
if (!options.issueType && !options.fields) return { operations: [], issueId: null };
|
|
655
|
-
if (!capabilities.push) return {
|
|
656
|
-
operations: [
|
|
657
|
-
...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : []),
|
|
658
|
-
...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'PUSH_REQUIRED' }] : [])
|
|
659
|
-
], issueId: null
|
|
660
|
-
};
|
|
661
|
-
const [owner] = repository.split('/');
|
|
662
|
-
const typesResult = client.json<unknown>(['api', 'graphql', '-f', `query=${ISSUE_TYPES_QUERY}`, '-F', `owner=${owner}`], { cwd });
|
|
663
|
-
if (!typesResult.ok) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: typesResult.error.code }], issueId: null };
|
|
664
|
-
const types = normalizeIssueTypes(typesResult.value);
|
|
665
|
-
if (types.length === 0) return {
|
|
666
|
-
operations: [
|
|
667
|
-
...(options.issueType ? [{ name: 'issue-type', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
|
|
668
|
-
...(options.fields ? [{ name: 'fields', status: 'skipped' as const, reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
|
|
669
|
-
], issueId: null
|
|
670
|
-
};
|
|
671
|
-
const current = graphState(client, repository, issue, cwd);
|
|
672
|
-
if (!current?.issueId) return { operations: [{ name: 'issue-schema', status: 'failed', reasonCode: 'ISSUE_GRAPH_IDENTITY_INVALID' }], issueId: null };
|
|
673
|
-
const target = types.find((type) => type.name === desiredIssueType(frontmatter.type || ''));
|
|
674
|
-
if (!target) return { operations: [{ name: 'issue-type', status: 'failed', reasonCode: 'ISSUE_TYPE_NOT_FOUND' }], issueId: current.issueId };
|
|
675
|
-
const operations: PlannedOperation[] = [];
|
|
676
|
-
if (options.issueType) operations.push(current.type?.id === target.id
|
|
677
|
-
? { name: 'issue-type', status: 'no-op', reasonCode: null }
|
|
678
|
-
: { name: 'issue-type', status: 'planned', reasonCode: null, value: { issueTypeId: target.id } });
|
|
679
|
-
if (options.fields) {
|
|
680
|
-
const desired = desiredFieldValues(frontmatter, target.fields);
|
|
681
|
-
const inputs = desired.filter((field) => current.values.find((value) => value.name === field.name)?.value !== field.value).map((field) => field.input);
|
|
682
|
-
if (current.type?.id !== target.id) {
|
|
683
|
-
const targetNames = new Set(target.fields.map((field) => field.name));
|
|
684
|
-
for (const old of current.values) if (!targetNames.has(old.name)) inputs.push({ fieldId: old.id, delete: true });
|
|
685
|
-
}
|
|
686
|
-
operations.push(inputs.length === 0
|
|
687
|
-
? { name: 'fields', status: 'no-op', reasonCode: null }
|
|
688
|
-
: { name: 'fields', status: 'planned', reasonCode: null, value: inputs });
|
|
689
|
-
}
|
|
690
|
-
return { operations, issueId: current.issueId };
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
function executeGraphOperation(
|
|
694
|
-
client: GitHubClient,
|
|
695
|
-
cwd: string,
|
|
696
|
-
issueId: string,
|
|
697
|
-
operation: PlannedOperation
|
|
698
|
-
) {
|
|
699
|
-
if (operation.name === 'issue-type') {
|
|
700
|
-
const query = 'mutation($issueId:ID!,$issueTypeId:ID){updateIssueIssueType(input:{issueId:$issueId,issueTypeId:$issueTypeId}){issue{id}}}';
|
|
701
|
-
const typeId = (operation.value as { issueTypeId: string }).issueTypeId;
|
|
702
|
-
return client.json(['api', 'graphql', '--input', '-'], {
|
|
703
|
-
cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueTypeId: typeId } })
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
const query = 'mutation($issueId:ID!,$issueFields:[IssueFieldCreateOrUpdateInput!]!){setIssueFieldValue(input:{issueId:$issueId,issueFields:$issueFields}){issue{id}}}';
|
|
707
|
-
return client.json(['api', 'graphql', '--input', '-'], {
|
|
708
|
-
cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueFields: operation.value } })
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
|
|
712
481
|
async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise<IssueResult> {
|
|
713
482
|
const base = await resolvedContext(taskRef, options);
|
|
714
483
|
if (!base.ok) return base.output;
|
|
@@ -849,15 +618,5 @@ async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise
|
|
|
849
618
|
});
|
|
850
619
|
}
|
|
851
620
|
|
|
852
|
-
export {
|
|
853
|
-
|
|
854
|
-
createPlatformIssue,
|
|
855
|
-
graphState,
|
|
856
|
-
inspectGitHubIssue,
|
|
857
|
-
inspectGitHubIssueMetadata,
|
|
858
|
-
inspectPlatformIssue,
|
|
859
|
-
normalizeIssue,
|
|
860
|
-
requirementSectionAnchors,
|
|
861
|
-
syncPlatformIssue
|
|
862
|
-
};
|
|
863
|
-
export type { BindOptions, CreateOptions, IssueResult, IssueSnapshot, SyncOptions };
|
|
621
|
+
export { bindPlatformIssue, createPlatformIssue, inspectPlatformIssue, requirementSectionAnchors, syncPlatformIssue };
|
|
622
|
+
export type { BindOptions, CreateOptions, IssueResult, SyncOptions };
|
|
@@ -2,7 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { parseTypedTaskFrontmatter } from '../task/frontmatter.ts';
|
|
4
4
|
import { resolveTaskRef } from '../task/resolve-ref.ts';
|
|
5
|
-
import type { PlatformCheckSnapshot } from './
|
|
5
|
+
import type { PlatformCheckSnapshot } from './snapshots.ts';
|
|
6
|
+
import { checkStatusBucket } from './check-status.ts';
|
|
6
7
|
import { resolvePlatformProviderContext } from './context.ts';
|
|
7
8
|
import type { PlatformClient } from './context.ts';
|
|
8
9
|
import { inspectPlatformPullRequest } from './pull-requests.ts';
|
|
@@ -33,7 +34,6 @@ type ChecksResult = PlatformResult & {
|
|
|
33
34
|
resolution?: { status: 'resolved' | 'missing' | 'ambiguous'; runId: number | null; jobId: number | null };
|
|
34
35
|
logs?: { runId: number; jobId?: number; text: string };
|
|
35
36
|
};
|
|
36
|
-
type GitHubClient = PlatformClient;
|
|
37
37
|
type InspectionOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
|
|
38
38
|
type SharedOptions = { cwd?: string; client?: PlatformClient };
|
|
39
39
|
|
|
@@ -56,18 +56,6 @@ function classifyPullRequestReadiness(input: {
|
|
|
56
56
|
return { state: 'ready', headSha: input.headSha };
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function parseRunJobIdentity(detailsUrl: string): { runId: number; jobId: number | null } | null {
|
|
60
|
-
try {
|
|
61
|
-
const url = new URL(detailsUrl);
|
|
62
|
-
if (url.hostname !== 'github.com') return null;
|
|
63
|
-
const match = url.pathname.match(/\/actions\/runs\/(\d+)(?:\/job\/(\d+))?/);
|
|
64
|
-
if (!match) return null;
|
|
65
|
-
return { runId: Number(match[1]), jobId: match[2] ? Number(match[2]) : null };
|
|
66
|
-
} catch {
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
59
|
function resolveRunCandidate(candidates: RunCandidate[], headSha: string, checkName: string):
|
|
72
60
|
| { status: 'resolved'; runId: number; jobId: number | null }
|
|
73
61
|
| { status: 'missing' | 'ambiguous'; runId: null; jobId: null } {
|
|
@@ -85,41 +73,6 @@ function checksResult(status: PlatformResult['status'], overrides: Partial<Check
|
|
|
85
73
|
};
|
|
86
74
|
}
|
|
87
75
|
|
|
88
|
-
function normalizeBucket(value: { bucket?: string; status?: string; state?: string; conclusion?: string }): CheckBucket {
|
|
89
|
-
const raw = String(value.bucket || value.status || value.conclusion || value.state || '').toLowerCase();
|
|
90
|
-
if (['pass', 'success', 'successful', 'neutral'].includes(raw)) return 'pass';
|
|
91
|
-
if (['fail', 'failure', 'failed', 'error', 'timed_out', 'action_required'].includes(raw)) return 'fail';
|
|
92
|
-
if (['cancel', 'cancelled', 'canceled', 'skipped', 'stale'].includes(raw)) return 'cancel';
|
|
93
|
-
return 'pending';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function normalizeChecks(value: unknown): CheckSnapshot[] {
|
|
97
|
-
if (!Array.isArray(value)) return [];
|
|
98
|
-
return value.flatMap((raw) => {
|
|
99
|
-
const item = raw as Record<string, unknown>;
|
|
100
|
-
const name = String(item.name || item.context || '');
|
|
101
|
-
return name ? [{
|
|
102
|
-
name,
|
|
103
|
-
bucket: normalizeBucket(item as { bucket?: string; state?: string; conclusion?: string }),
|
|
104
|
-
workflow: item.workflow ? String(item.workflow) : null,
|
|
105
|
-
conclusion: item.conclusion ? String(item.conclusion) : item.state ? String(item.state) : null,
|
|
106
|
-
detailsUrl: item.link ? String(item.link) : item.detailsUrl ? String(item.detailsUrl) : null,
|
|
107
|
-
startedAt: item.startedAt ? String(item.startedAt) : null,
|
|
108
|
-
completedAt: item.completedAt ? String(item.completedAt) : null
|
|
109
|
-
}] : [];
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function inspectGitHubRequiredChecks(client: GitHubClient, repository: string, number: number, cwd: string) {
|
|
114
|
-
const inspected = client.json<unknown>([
|
|
115
|
-
'pr', 'checks', String(number), '--repo', repository,
|
|
116
|
-
'--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
|
|
117
|
-
], { cwd });
|
|
118
|
-
return inspected.ok
|
|
119
|
-
? { ok: true as const, value: normalizeChecks(inspected.value) }
|
|
120
|
-
: { ok: false as const, error: inspected.error };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
76
|
async function resolvedTask(taskRef: string, options: InspectionOptions) {
|
|
124
77
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
125
78
|
if (!resolved.ok) return { ok: false as const, output: checksResult('failed', { error: { code: resolved.code, message: resolved.message, retryable: false } }) };
|
|
@@ -141,14 +94,12 @@ async function resolvedTask(taskRef: string, options: InspectionOptions) {
|
|
|
141
94
|
async function inspectRequiredChecks(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
|
|
142
95
|
const base = await resolvedTask(taskRef, options);
|
|
143
96
|
if (!base.ok) return base.output;
|
|
144
|
-
return inspectChecksForResolvedTask(base
|
|
97
|
+
return inspectChecksForResolvedTask(base);
|
|
145
98
|
}
|
|
146
99
|
|
|
147
100
|
async function inspectChecksForResolvedTask(
|
|
148
|
-
base: Extract<Awaited<ReturnType<typeof resolvedTask>>, { ok: true }
|
|
149
|
-
options: InspectionOptions
|
|
101
|
+
base: Extract<Awaited<ReturnType<typeof resolvedTask>>, { ok: true }>
|
|
150
102
|
): Promise<ChecksResult> {
|
|
151
|
-
const repository = base.context.platform.repository!;
|
|
152
103
|
{
|
|
153
104
|
const inspected = base.provider.checks?.inspectRequired
|
|
154
105
|
? await base.provider.checks.inspectRequired({
|
|
@@ -164,7 +115,13 @@ async function inspectChecksForResolvedTask(
|
|
|
164
115
|
pullRequest: base.pullRequest,
|
|
165
116
|
error: providerError(inspected.error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
166
117
|
});
|
|
167
|
-
const required =
|
|
118
|
+
const required = inspected.value.map((check): CheckSnapshot => ({
|
|
119
|
+
name: check.name,
|
|
120
|
+
bucket: checkStatusBucket(check.status),
|
|
121
|
+
conclusion: check.conclusion || null,
|
|
122
|
+
detailsUrl: check.detailsUrl || null,
|
|
123
|
+
workflow: null, startedAt: null, completedAt: null
|
|
124
|
+
}));
|
|
168
125
|
const classified = classifyRequiredChecks(required);
|
|
169
126
|
const status = classified.state === 'passed' || classified.state === 'no-required'
|
|
170
127
|
? 'no-op'
|
|
@@ -184,7 +141,7 @@ async function inspectChecksForResolvedTask(
|
|
|
184
141
|
async function inspectPullRequestReadiness(taskRef: string, options: InspectionOptions = {}): Promise<ChecksResult> {
|
|
185
142
|
const base = await resolvedTask(taskRef, options);
|
|
186
143
|
if (!base.ok) return base.output;
|
|
187
|
-
const checked = await inspectChecksForResolvedTask(base
|
|
144
|
+
const checked = await inspectChecksForResolvedTask(base);
|
|
188
145
|
const classifiedCodes = new Set(['REQUIRED_CHECKS_PENDING', 'REQUIRED_CHECKS_FAILED', 'REQUIRED_CHECKS_CANCELLED']);
|
|
189
146
|
if (checked.error && !classifiedCodes.has(checked.error.code)) return checked;
|
|
190
147
|
const readiness = classifyPullRequestReadiness({
|
|
@@ -271,15 +228,6 @@ async function resolvePlatformCheckRun(taskRef: string, options: SharedOptions &
|
|
|
271
228
|
}
|
|
272
229
|
}
|
|
273
230
|
|
|
274
|
-
function fetchCheckLogText(client: GitHubClient, args: string[], cwd: string) {
|
|
275
|
-
if (!client.text) return { ok: false as const, error: { code: 'PLATFORM_CLIENT_TEXT_UNAVAILABLE', message: 'Platform client does not support text responses', retryable: false } };
|
|
276
|
-
const fetched = client.text(args, { cwd });
|
|
277
|
-
if (fetched.ok || args[0] !== 'api' || !/response contains terminal escape sequences/i.test(fetched.error.message)) {
|
|
278
|
-
return fetched;
|
|
279
|
-
}
|
|
280
|
-
return client.text([...args, '--allow-escape-sequences'], { cwd });
|
|
281
|
-
}
|
|
282
|
-
|
|
283
231
|
async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions & { run: number; job?: number }): Promise<ChecksResult> {
|
|
284
232
|
const base = await resolvedTask(taskRef, options);
|
|
285
233
|
if (!base.ok) return base.output;
|
|
@@ -308,17 +256,5 @@ async function fetchPlatformCheckLogs(taskRef: string, options: SharedOptions &
|
|
|
308
256
|
}
|
|
309
257
|
}
|
|
310
258
|
|
|
311
|
-
export {
|
|
312
|
-
classifyPullRequestReadiness,
|
|
313
|
-
classifyRequiredChecks,
|
|
314
|
-
fetchCheckLogText,
|
|
315
|
-
fetchPlatformCheckLogs,
|
|
316
|
-
inspectGitHubRequiredChecks,
|
|
317
|
-
inspectRequiredChecks,
|
|
318
|
-
inspectPullRequestReadiness,
|
|
319
|
-
parseRunJobIdentity,
|
|
320
|
-
resolvePlatformCheckRun,
|
|
321
|
-
resolveRunCandidate,
|
|
322
|
-
watchPullRequestReadiness
|
|
323
|
-
};
|
|
259
|
+
export { classifyPullRequestReadiness, classifyRequiredChecks, fetchPlatformCheckLogs, inspectRequiredChecks, inspectPullRequestReadiness, resolvePlatformCheckRun, resolveRunCandidate, watchPullRequestReadiness };
|
|
324
260
|
export type { CheckBucket, CheckSnapshot, ChecksResult, ChecksSnapshot, CheckState, ReadinessSnapshot, ReadinessState, RunCandidate };
|
|
@@ -16,7 +16,7 @@ import { platformResult } from './types.ts';
|
|
|
16
16
|
import type { PlatformResult } from './types.ts';
|
|
17
17
|
import type { OperationWarning } from '../task/operation-outcome.ts';
|
|
18
18
|
import { providerError, providerOperationContext, providerStatus, providerResourceToken, resourceIdentityNumber, unsupportedProviderOperation } from './provider-bridge.ts';
|
|
19
|
-
import type { PlatformChangeRequestSnapshot } from './
|
|
19
|
+
import type { PlatformChangeRequestSnapshot } from './snapshots.ts';
|
|
20
20
|
import type { ChangeRequestSnapshot } from './provider-contract.ts';
|
|
21
21
|
import {
|
|
22
22
|
buildPrChangeReport,
|
|
@@ -33,6 +33,8 @@ import type {
|
|
|
33
33
|
MechanicalChangeReport,
|
|
34
34
|
PrChangeReport
|
|
35
35
|
} from './pr-change-report.ts';
|
|
36
|
+
import { manualValidationFinalSummaryProjectionMatches } from '../task/manual-validation-receipt.ts';
|
|
37
|
+
import { readManualValidationCompletion } from '../task/manual-validation-completion.ts';
|
|
36
38
|
|
|
37
39
|
type SummaryComment = { id: number | string; body: string };
|
|
38
40
|
type ChangeReportState = 'ready' | 'missing' | 'stale' | 'invalid';
|
|
@@ -50,9 +52,21 @@ type PullRequestSummaryResult = PlatformResult & {
|
|
|
50
52
|
};
|
|
51
53
|
type PullRequestPrimaryResult = 'pr_created' | 'pr_reused' | 'no_op';
|
|
52
54
|
type SummaryOptions = { cwd?: string; client?: PlatformClient; runtimeVersion?: string };
|
|
55
|
+
type ManualValidationSummaryOptions = {
|
|
56
|
+
phase: 'pending' | 'final';
|
|
57
|
+
transactionId?: string;
|
|
58
|
+
receiptDigest?: string;
|
|
59
|
+
prHeadSha?: string;
|
|
60
|
+
authority?: 'coordinator';
|
|
61
|
+
};
|
|
53
62
|
type ReportWriteResult = PlatformResult & {
|
|
54
63
|
report: { path: string; status: 'written' | 'no-op' | 'planned'; precheckVerdict: 'clear' | 'needs-review'; nextAction: 'watch-pr' | 'review-code' } | null;
|
|
55
64
|
};
|
|
65
|
+
type SummaryCommentStateResult = Omit<PlatformResult, 'comment'> & {
|
|
66
|
+
task: { id: string | null; prNumber: number | null };
|
|
67
|
+
pullRequest: PlatformChangeRequestSnapshot | null;
|
|
68
|
+
comment: SummaryComment | null;
|
|
69
|
+
};
|
|
56
70
|
|
|
57
71
|
function summaryMarker(taskId: string): string {
|
|
58
72
|
return `<!-- sync-pr:${taskId}:summary -->`;
|
|
@@ -354,6 +368,23 @@ async function summaryContext(taskRef: string, options: SummaryOptions = {}): Pr
|
|
|
354
368
|
};
|
|
355
369
|
}
|
|
356
370
|
|
|
371
|
+
async function summaryCommentState(taskRef: string, options: SummaryOptions = {}): Promise<SummaryCommentStateResult> {
|
|
372
|
+
const context = await summaryContext(taskRef, options);
|
|
373
|
+
if (!context.task.prNumber || !context.pullRequest) return { ...context, comment: null };
|
|
374
|
+
const loaded = await resolvePlatformProviderContext({ cwd: options.cwd, client: options.client });
|
|
375
|
+
if (!loaded.ok || !loaded.value.provider.comments?.list) return { ...context, comment: null };
|
|
376
|
+
const listed = await loaded.value.provider.comments.list({
|
|
377
|
+
context: providerOperationContext(loaded.value),
|
|
378
|
+
parent: providerResourceToken(loaded.value.provider, 'pull-request', String(context.task.prNumber))
|
|
379
|
+
});
|
|
380
|
+
if (!listed.ok) return { ...context, comment: null };
|
|
381
|
+
const matches = listed.value.filter((comment) => comment.body.includes(summaryMarker(context.task.id!)));
|
|
382
|
+
return {
|
|
383
|
+
...context,
|
|
384
|
+
comment: matches.length === 1 ? { id: /^\d+$/u.test(matches[0]!.id) ? Number(matches[0]!.id) : matches[0]!.id, body: matches[0]!.body } : null
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
357
388
|
type ReportWriteOptions = {
|
|
358
389
|
agent: string;
|
|
359
390
|
mechanicalFile: string;
|
|
@@ -450,7 +481,7 @@ async function reportWrite(taskRef: string, options: ReportWriteOptions): Promis
|
|
|
450
481
|
|
|
451
482
|
async function syncPullRequestSummary(
|
|
452
483
|
taskRef: string,
|
|
453
|
-
options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string }
|
|
484
|
+
options: { agent: string; body: string; changeReportFile?: string; cwd?: string; client?: PlatformClient; dryRun?: boolean; strict?: boolean; primaryResult: PullRequestPrimaryResult; runtimeVersion?: string; manualValidation?: ManualValidationSummaryOptions; lockAlreadyHeld?: boolean }
|
|
454
485
|
): Promise<PullRequestSummaryResult> {
|
|
455
486
|
const warningResult = warningResultForPrimary(options.primaryResult);
|
|
456
487
|
let knownPrNumber: number | null = null;
|
|
@@ -503,7 +534,7 @@ async function syncPullRequestSummary(
|
|
|
503
534
|
return preserveFailure(context);
|
|
504
535
|
}
|
|
505
536
|
try {
|
|
506
|
-
|
|
537
|
+
const execute = async (): Promise<PullRequestSummaryResult> => {
|
|
507
538
|
const { taskContent, boundFact } = readBoundTaskSnapshot(resolved.taskMdPath);
|
|
508
539
|
if (!boundFact) return {
|
|
509
540
|
...platformResult('failed', { resource: { kind: 'pull-request', number: null }, error: { code: 'PR_NOT_LINKED', message: 'Task has no verified bound pull request', retryable: false } }),
|
|
@@ -519,6 +550,27 @@ async function syncPullRequestSummary(
|
|
|
519
550
|
const prNumber = boundPrNumber;
|
|
520
551
|
const initial = await inspectBoundPullRequest(context, resolved.repoRoot, boundPrNumber, loaded.value);
|
|
521
552
|
if (!initial.ok) return fail(initial.status, context, initial.error);
|
|
553
|
+
const manual = options.manualValidation;
|
|
554
|
+
const hasFinalManualValidation = /###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)/u.test(options.body);
|
|
555
|
+
if (hasFinalManualValidation && (!manual || manual.phase !== 'final')) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires the transaction coordinator', retryable: false }, prNumber);
|
|
556
|
+
if (manual && (manual.phase === 'pending' ? hasFinalManualValidation : !hasFinalManualValidation)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PHASE_INVALID', message: 'manual-validation summary phase does not match the requested writer phase', retryable: false }, prNumber);
|
|
557
|
+
if (manual) {
|
|
558
|
+
if (manual.phase === 'final' && (!manual.transactionId || !manual.receiptDigest || !manual.prHeadSha || manual.prHeadSha !== initial.value.head.sha)) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires transaction, receipt, and current head identity', retryable: false }, prNumber);
|
|
559
|
+
if (manual.phase === 'final') {
|
|
560
|
+
if (manual.authority !== 'coordinator') return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_REQUIRED', message: 'final manual-validation summary requires coordinator authority', retryable: false }, prNumber);
|
|
561
|
+
const completion = readManualValidationCompletion(resolved.taskDir, {
|
|
562
|
+
transactionId: manual.transactionId,
|
|
563
|
+
taskId: resolved.taskId,
|
|
564
|
+
prNumber,
|
|
565
|
+
prHeadSha: initial.value.head.sha,
|
|
566
|
+
receiptDigest: manual.receiptDigest
|
|
567
|
+
});
|
|
568
|
+
if (!completion.ok) return fail('failed', context, platformError(completion.error), prNumber);
|
|
569
|
+
const { receipt, transaction } = completion.value;
|
|
570
|
+
if (transaction.phase !== 'final-promotion-in-progress' || !transaction.eventAppended) return fail('failed', context, { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'final summary requires a receipt-backed promotion intent', retryable: false }, prNumber);
|
|
571
|
+
if (!manualValidationFinalSummaryProjectionMatches(options.body, receipt)) return fail('failed', context, { code: 'MANUAL_VALIDATION_SUMMARY_PROJECTION_INVALID', message: 'final summary body does not match the canonical receipt projection', retryable: false }, prNumber);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
522
574
|
const expectedReportPath = taskReportPath(resolved.taskDir);
|
|
523
575
|
const suppliedReportPath = path.resolve(resolved.repoRoot, options.changeReportFile!);
|
|
524
576
|
if (suppliedReportPath !== expectedReportPath) return fail('failed', context, { code: 'PR_CHANGE_REPORT_PATH_INVALID', message: 'summary-sync must consume the task-bound pr-change-report.json', retryable: false });
|
|
@@ -651,7 +703,10 @@ async function syncPullRequestSummary(
|
|
|
651
703
|
operations: [{ name: `summary:${reconciliation.action}`, status: 'applied', reasonCode: null }],
|
|
652
704
|
result: null, warnings: [], ...info
|
|
653
705
|
};
|
|
654
|
-
}
|
|
706
|
+
};
|
|
707
|
+
return await (options.lockAlreadyHeld
|
|
708
|
+
? execute()
|
|
709
|
+
: withTaskExecutionLock(resolved.repoRoot, resolved.taskId, options.agent, execute));
|
|
655
710
|
} catch (error) {
|
|
656
711
|
const lockError = error instanceof TaskExecutionLockError
|
|
657
712
|
? { code: error.code, message: error.message, retryable: error.code === 'ORCHESTRATION_LOCK_BUSY' }
|
|
@@ -664,9 +719,10 @@ export {
|
|
|
664
719
|
buildPullRequestSummary,
|
|
665
720
|
reconcileSummaryComment,
|
|
666
721
|
reportWrite,
|
|
722
|
+
summaryCommentState,
|
|
667
723
|
summaryContext,
|
|
668
724
|
summaryMarker,
|
|
669
725
|
syncPullRequestSummary,
|
|
670
726
|
warningResultForPrimary
|
|
671
727
|
};
|
|
672
|
-
export type { PullRequestSummaryResult, ReportWriteOptions, ReportWriteResult, SummaryContextResult };
|
|
728
|
+
export type { ManualValidationSummaryOptions, PullRequestSummaryResult, ReportWriteOptions, ReportWriteResult, SummaryCommentStateResult, SummaryContextResult };
|