@fitlab-ai/agent-infra 0.11.6 → 0.11.8

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.
Files changed (192) hide show
  1. package/dist/bin/internal-cli.js +8 -2
  2. package/dist/lib/agent-clients/adapters/codex.js +1 -1
  3. package/dist/lib/agent-clients/config.js +11 -3
  4. package/dist/lib/defaults.json +6 -3
  5. package/dist/lib/git/primary-worktree.js +58 -0
  6. package/dist/lib/internal/cli-route-inventory.js +3 -0
  7. package/dist/lib/internal/platform-pr-review.js +20 -3
  8. package/dist/lib/internal/task-operation-registry.js +3 -1
  9. package/dist/lib/internal/task-platform-recovery.js +56 -0
  10. package/dist/lib/merge.js +25 -5
  11. package/dist/lib/platform/completion-backfill.js +2 -1
  12. package/dist/lib/platform/in-label-sync.js +12 -1
  13. package/dist/lib/platform/issue-comments.js +58 -4
  14. package/dist/lib/platform/issues.js +105 -5
  15. package/dist/lib/platform/operation-coordinator.js +74 -0
  16. package/dist/lib/platform/pr-review.js +107 -1
  17. package/dist/lib/platform/pr-summary.js +79 -57
  18. package/dist/lib/platform/pull-requests.js +125 -7
  19. package/dist/lib/sandbox/commands/create.js +6 -3
  20. package/dist/lib/sandbox/commands/ls.js +4 -3
  21. package/dist/lib/sandbox/commands/rm.js +1 -1
  22. package/dist/lib/sandbox/config.js +22 -4
  23. package/dist/lib/sandbox/control/server.js +6 -2
  24. package/dist/lib/sandbox/git-metadata.js +50 -0
  25. package/dist/lib/sandbox/index.js +1 -1
  26. package/dist/lib/sandbox/mounts.js +12 -0
  27. package/dist/lib/sandbox/recovery.js +11 -0
  28. package/dist/lib/sandbox/removal.js +58 -20
  29. package/dist/lib/sandbox/tools.js +29 -7
  30. package/dist/lib/server/config.js +7 -27
  31. package/dist/lib/server/daemon.js +4 -3
  32. package/dist/lib/server/process-control.js +4 -2
  33. package/dist/lib/server/runner.js +4 -3
  34. package/dist/lib/task/activity-intent.js +4 -3
  35. package/dist/lib/task/artifact-lifecycle.js +42 -10
  36. package/dist/lib/task/artifact-receipts.js +62 -22
  37. package/dist/lib/task/capabilities.js +22 -4
  38. package/dist/lib/task/completion-facts.js +56 -0
  39. package/dist/lib/task/create-service.js +11 -5
  40. package/dist/lib/task/create.js +60 -79
  41. package/dist/lib/task/events.js +268 -195
  42. package/dist/lib/task/finalization-artifacts.js +4 -1
  43. package/dist/lib/task/finalization-handoff.js +2 -1
  44. package/dist/lib/task/finalization.js +157 -24
  45. package/dist/lib/task/invalidation-command.js +4 -2
  46. package/dist/lib/task/invalidation.js +38 -9
  47. package/dist/lib/task/lifecycle.js +38 -2
  48. package/dist/lib/task/local-artifact-finalization.js +17 -3
  49. package/dist/lib/task/platform-operation-journal.js +152 -0
  50. package/dist/lib/task/platform-operation-recovery.js +459 -0
  51. package/dist/lib/task/qualification-audit.js +23 -105
  52. package/dist/lib/task/review-finalization.js +7 -0
  53. package/dist/lib/task/review-identity.js +69 -0
  54. package/dist/lib/task/sections.js +6 -5
  55. package/dist/lib/task/task-execution-lock.js +6 -3
  56. package/dist/lib/task/verification-engine.js +14 -63
  57. package/dist/lib/task/verification.js +35 -38
  58. package/dist/lib/task/workflow-warning-intents.js +7 -1
  59. package/dist/lifecycle-build-manifest.json +3 -3
  60. package/lib/agent-clients/adapters/codex.ts +1 -1
  61. package/lib/agent-clients/config.ts +9 -3
  62. package/lib/defaults.json +6 -3
  63. package/lib/git/primary-worktree.ts +62 -0
  64. package/lib/internal/cli-route-inventory.ts +2 -0
  65. package/lib/internal/platform-pr-review.ts +14 -3
  66. package/lib/internal/task-operation-registry.ts +3 -1
  67. package/lib/internal/task-platform-recovery.ts +53 -0
  68. package/lib/merge.ts +26 -6
  69. package/lib/platform/completion-backfill.ts +2 -1
  70. package/lib/platform/in-label-sync.ts +12 -0
  71. package/lib/platform/issue-comments.ts +59 -3
  72. package/lib/platform/issues.ts +103 -5
  73. package/lib/platform/operation-coordinator.ts +96 -0
  74. package/lib/platform/pr-review.ts +113 -1
  75. package/lib/platform/pr-summary.ts +22 -1
  76. package/lib/platform/pull-requests.ts +132 -8
  77. package/lib/sandbox/commands/create.ts +7 -8
  78. package/lib/sandbox/commands/ls.ts +4 -3
  79. package/lib/sandbox/commands/rm.ts +1 -1
  80. package/lib/sandbox/config.ts +23 -5
  81. package/lib/sandbox/control/server.ts +5 -2
  82. package/lib/sandbox/git-metadata.ts +66 -0
  83. package/lib/sandbox/index.ts +1 -1
  84. package/lib/sandbox/mounts.ts +14 -0
  85. package/lib/sandbox/recovery.ts +13 -0
  86. package/lib/sandbox/removal.ts +57 -19
  87. package/lib/sandbox/tool-types.ts +1 -1
  88. package/lib/sandbox/tools.ts +36 -7
  89. package/lib/server/config.ts +7 -29
  90. package/lib/server/daemon.ts +5 -3
  91. package/lib/server/process-control.ts +4 -2
  92. package/lib/server/runner.ts +7 -4
  93. package/lib/server/server.schema.json +0 -4
  94. package/lib/task/activity-intent.ts +4 -3
  95. package/lib/task/artifact-lifecycle.ts +35 -11
  96. package/lib/task/artifact-receipts.ts +55 -15
  97. package/lib/task/capabilities.ts +17 -3
  98. package/lib/task/completion-facts.ts +66 -0
  99. package/lib/task/create-service.ts +12 -6
  100. package/lib/task/create.ts +54 -69
  101. package/lib/task/events.ts +248 -182
  102. package/lib/task/finalization-artifacts.ts +4 -1
  103. package/lib/task/finalization-handoff.ts +2 -1
  104. package/lib/task/finalization.ts +154 -21
  105. package/lib/task/invalidation-command.ts +4 -2
  106. package/lib/task/invalidation.ts +35 -10
  107. package/lib/task/lifecycle.ts +35 -3
  108. package/lib/task/local-artifact-finalization.ts +13 -3
  109. package/lib/task/platform-operation-journal.ts +230 -0
  110. package/lib/task/platform-operation-recovery.ts +435 -0
  111. package/lib/task/qualification-audit.ts +21 -114
  112. package/lib/task/review-finalization.ts +7 -0
  113. package/lib/task/review-identity.ts +85 -0
  114. package/lib/task/sections.ts +6 -6
  115. package/lib/task/task-execution-lock.ts +7 -3
  116. package/lib/task/verification-engine.ts +13 -86
  117. package/lib/task/verification.ts +34 -38
  118. package/lib/task/workflow-warning-intents.ts +9 -1
  119. package/package.json +1 -1
  120. package/templates/.agents/README.en.md +19 -17
  121. package/templates/.agents/README.zh-CN.md +18 -16
  122. package/templates/.agents/rules/README.en.md +2 -1
  123. package/templates/.agents/rules/README.zh-CN.md +2 -1
  124. package/templates/.agents/rules/decision-qualification.en.md +4 -3
  125. package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
  126. package/templates/.agents/rules/task-management.en.md +12 -0
  127. package/templates/.agents/rules/task-management.zh-CN.md +12 -0
  128. package/templates/.agents/rules/validation-output.en.md +19 -0
  129. package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
  130. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
  131. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  132. package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
  133. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  134. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -2
  135. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -3
  136. package/templates/.agents/skills/code-task/SKILL.en.md +5 -3
  137. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  138. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
  139. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
  140. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  142. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  143. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  144. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  145. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  146. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  147. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  148. package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
  149. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  150. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
  151. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  152. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
  153. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  154. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
  155. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  156. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
  157. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  158. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  159. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  160. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
  161. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
  162. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
  163. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
  164. package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
  165. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
  166. package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
  167. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
  168. package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
  169. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
  170. package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
  171. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
  172. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
  173. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
  174. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
  175. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
  176. package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
  177. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
  178. package/templates/.agents/skills/review-pr/SKILL.en.md +13 -11
  179. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +13 -11
  180. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
  181. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
  182. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
  183. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
  184. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  185. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  186. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  187. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  188. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +19 -6
  189. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
  190. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  191. package/templates/.agents/templates/task.en.md +2 -2
  192. package/templates/.agents/templates/task.zh-CN.md +2 -2
@@ -0,0 +1,230 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+
5
+ import { isResourceIdentity } from '../platform/resource-identity.ts';
6
+ import type { ResourceIdentity } from '../platform/resource-identity.ts';
7
+ import { resolveTaskRef } from './resolve-ref.ts';
8
+
9
+ const JOURNAL_FILE = '.platform-operations.json';
10
+ const MAX_ATTEMPTS = 3;
11
+
12
+ type PlatformOperationKind = 'task-comment' | 'artifact-comment' | 'summary-comment' | 'cancel-comment' | 'issue-create' | 'issue-metadata' | 'pull-request' | 'pull-request-summary' | 'pull-request-review';
13
+ type PlatformOperationState = 'queued' | 'pending' | 'unknown' | 'succeeded' | 'failed';
14
+ type PlatformIssueMetadataIntent = Readonly<{
15
+ requirements: boolean;
16
+ issueType: boolean;
17
+ fields: boolean;
18
+ status?: string;
19
+ assignees?: 'current' | 'none';
20
+ milestone?: 'initial' | 'specific' | 'none';
21
+ inLabels?: 'from-diff' | 'none';
22
+ base?: string;
23
+ fromDiffFiles?: readonly string[];
24
+ inLabelMappingDigest?: string;
25
+ state?: 'open' | 'closed';
26
+ closeReason?: 'completed' | 'not_planned';
27
+ }>;
28
+ type PlatformPullRequestIntent = Readonly<{
29
+ action: 'create' | 'bind' | 'sync';
30
+ baseRef?: string;
31
+ headRef?: string;
32
+ prToken?: string;
33
+ metadata?: boolean;
34
+ closingIssue?: boolean;
35
+ }>;
36
+ type PlatformIssueCreateIntent = Readonly<{ title: string; bodyDigest: string }>;
37
+ type PlatformPullRequestSummaryIntent = Readonly<{ body: string; changeReportFile: string }>;
38
+ type PlatformPullRequestReviewIntent = Readonly<{
39
+ prNumber: string;
40
+ resource: ResourceIdentity;
41
+ providerScopeId: string;
42
+ scope: string;
43
+ round: number;
44
+ commitSha: string;
45
+ event: 'COMMENT' | 'APPROVE' | 'REQUEST_CHANGES';
46
+ artifactFile: string;
47
+ bodyDigest: string;
48
+ }>;
49
+ type PlatformOperation = Readonly<{
50
+ id: string;
51
+ kind: PlatformOperationKind;
52
+ target: string;
53
+ expectedDigest: string;
54
+ dependency: 'deferred' | 'required';
55
+ state: PlatformOperationState;
56
+ attempts: number;
57
+ maxAttempts: typeof MAX_ATTEMPTS;
58
+ lastCode: string | null;
59
+ issueMetadata?: PlatformIssueMetadataIntent;
60
+ issueCreate?: PlatformIssueCreateIntent;
61
+ pullRequest?: PlatformPullRequestIntent;
62
+ pullRequestSummary?: PlatformPullRequestSummaryIntent;
63
+ pullRequestReview?: PlatformPullRequestReviewIntent;
64
+ updatedAt: string;
65
+ }>;
66
+ type PlatformOperationJournal = Readonly<{
67
+ version: 1;
68
+ taskId: string;
69
+ operations: readonly PlatformOperation[];
70
+ }>;
71
+
72
+ type RecordOperationInput = Readonly<{
73
+ taskRef: string;
74
+ kind: PlatformOperationKind;
75
+ target: string;
76
+ expectedDigest: string;
77
+ dependency: 'deferred' | 'required';
78
+ state: PlatformOperationState;
79
+ lastCode?: string | null;
80
+ issueMetadata?: PlatformIssueMetadataIntent;
81
+ issueCreate?: PlatformIssueCreateIntent;
82
+ pullRequest?: PlatformPullRequestIntent;
83
+ pullRequestSummary?: PlatformPullRequestSummaryIntent;
84
+ pullRequestReview?: PlatformPullRequestReviewIntent;
85
+ cwd?: string;
86
+ }>;
87
+
88
+ function operationId(input: Pick<RecordOperationInput, 'kind' | 'target' | 'expectedDigest'>): string {
89
+ return createHash('sha256')
90
+ .update(`${input.kind}\0${input.target}\0${input.expectedDigest}`)
91
+ .digest('hex');
92
+ }
93
+
94
+ function journalPath(taskDir: string): string {
95
+ return path.join(taskDir, JOURNAL_FILE);
96
+ }
97
+
98
+ function parseJournal(file: string, taskId: string): PlatformOperationJournal {
99
+ let value: unknown;
100
+ try { value = JSON.parse(fs.readFileSync(file, 'utf8')); }
101
+ catch (error) {
102
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { version: 1, taskId, operations: [] };
103
+ throw Object.assign(new Error(`Platform operation journal is unreadable: ${String(error)}`), { code: 'PLATFORM_OPERATION_JOURNAL_INVALID' });
104
+ }
105
+ if (!value || typeof value !== 'object') throw Object.assign(new Error('Platform operation journal is invalid'), { code: 'PLATFORM_OPERATION_JOURNAL_INVALID' });
106
+ const journal = value as Partial<PlatformOperationJournal>;
107
+ if (journal.version !== 1 || journal.taskId !== taskId || !Array.isArray(journal.operations)) {
108
+ throw Object.assign(new Error('Platform operation journal identity or version is invalid'), { code: 'PLATFORM_OPERATION_JOURNAL_INVALID' });
109
+ }
110
+ for (const item of journal.operations) {
111
+ if (!item || typeof item !== 'object' || !/^[a-f0-9]{64}$/u.test(item.id)
112
+ || !['task-comment', 'artifact-comment', 'summary-comment', 'cancel-comment', 'issue-create', 'issue-metadata', 'pull-request', 'pull-request-summary', 'pull-request-review'].includes(item.kind)
113
+ || typeof item.target !== 'string' || !item.target
114
+ || !/^[a-f0-9]{64}$/u.test(item.expectedDigest)
115
+ || !['deferred', 'required'].includes(item.dependency)
116
+ || !['queued', 'pending', 'unknown', 'succeeded', 'failed'].includes(item.state)
117
+ || !Number.isSafeInteger(item.attempts) || item.attempts < 0 || item.attempts > MAX_ATTEMPTS
118
+ || item.maxAttempts !== MAX_ATTEMPTS
119
+ || !(item.lastCode === null || typeof item.lastCode === 'string')
120
+ || (item.kind === 'issue-metadata'
121
+ ? !item.issueMetadata || typeof item.issueMetadata.requirements !== 'boolean'
122
+ || typeof item.issueMetadata.issueType !== 'boolean' || typeof item.issueMetadata.fields !== 'boolean'
123
+ || (item.issueMetadata.status !== undefined && typeof item.issueMetadata.status !== 'string')
124
+ || (item.issueMetadata.assignees !== undefined && !['current', 'none'].includes(item.issueMetadata.assignees))
125
+ || (item.issueMetadata.milestone !== undefined && !['initial', 'specific', 'none'].includes(item.issueMetadata.milestone))
126
+ || (item.issueMetadata.inLabels !== undefined && !['from-diff', 'none'].includes(item.issueMetadata.inLabels))
127
+ || (item.issueMetadata.base !== undefined && (typeof item.issueMetadata.base !== 'string' || !item.issueMetadata.base.trim()))
128
+ || (item.issueMetadata.fromDiffFiles !== undefined && (!Array.isArray(item.issueMetadata.fromDiffFiles)
129
+ || item.issueMetadata.fromDiffFiles.some((file: unknown) => typeof file !== 'string')))
130
+ || (item.issueMetadata.inLabelMappingDigest !== undefined && !/^[a-f0-9]{64}$/u.test(item.issueMetadata.inLabelMappingDigest))
131
+ || (item.issueMetadata.state !== undefined && !['open', 'closed'].includes(item.issueMetadata.state))
132
+ || (item.issueMetadata.closeReason !== undefined && !['completed', 'not_planned'].includes(item.issueMetadata.closeReason))
133
+ || (!item.issueMetadata.requirements && !item.issueMetadata.issueType && !item.issueMetadata.fields
134
+ && item.issueMetadata.status === undefined && item.issueMetadata.assignees === undefined
135
+ && item.issueMetadata.milestone === undefined && item.issueMetadata.inLabels === undefined && item.issueMetadata.state === undefined)
136
+ : item.issueMetadata !== undefined)
137
+ || (item.kind === 'pull-request'
138
+ ? !item.pullRequest || !['create', 'bind', 'sync'].includes(item.pullRequest.action)
139
+ || Object.entries(item.pullRequest).some(([key, field]) => key !== 'action'
140
+ && key !== 'metadata' && key !== 'closingIssue' && (typeof field !== 'string' || !field.trim()))
141
+ || (item.pullRequest.metadata !== undefined && typeof item.pullRequest.metadata !== 'boolean')
142
+ || (item.pullRequest.closingIssue !== undefined && typeof item.pullRequest.closingIssue !== 'boolean')
143
+ : item.pullRequest !== undefined)
144
+ || (item.kind === 'issue-create'
145
+ ? !item.issueCreate || typeof item.issueCreate.title !== 'string' || !item.issueCreate.title.trim()
146
+ || !/^[a-f0-9]{64}$/u.test(item.issueCreate.bodyDigest)
147
+ : item.issueCreate !== undefined)
148
+ || (item.kind === 'pull-request-summary'
149
+ ? !item.pullRequestSummary || typeof item.pullRequestSummary.body !== 'string'
150
+ || !item.pullRequestSummary.body.trim() || typeof item.pullRequestSummary.changeReportFile !== 'string'
151
+ || !item.pullRequestSummary.changeReportFile.trim()
152
+ : item.pullRequestSummary !== undefined)
153
+ || (item.kind === 'pull-request-review'
154
+ ? !item.pullRequestReview || typeof item.pullRequestReview.prNumber !== 'string'
155
+ || !isResourceIdentity(item.pullRequestReview.resource)
156
+ || typeof item.pullRequestReview.providerScopeId !== 'string' || !item.pullRequestReview.providerScopeId.trim()
157
+ || !item.pullRequestReview.prNumber.trim() || !/^TASK-\d{8}-\d{6}$|^pr\d+$/u.test(item.pullRequestReview.scope)
158
+ || !Number.isSafeInteger(item.pullRequestReview.round) || item.pullRequestReview.round <= 0
159
+ || !/^[0-9a-f]{7,40}$/iu.test(item.pullRequestReview.commitSha)
160
+ || !['COMMENT', 'APPROVE', 'REQUEST_CHANGES'].includes(item.pullRequestReview.event)
161
+ || typeof item.pullRequestReview.artifactFile !== 'string'
162
+ || !/^pr-review(?:-r\d+)?\.md$/u.test(item.pullRequestReview.artifactFile)
163
+ || (item.pullRequestReview.artifactFile === 'pr-review.md'
164
+ ? item.pullRequestReview.round !== 1
165
+ : item.pullRequestReview.artifactFile !== `pr-review-r${item.pullRequestReview.round}.md`)
166
+ || !/^[a-f0-9]{64}$/u.test(item.pullRequestReview.bodyDigest)
167
+ : item.pullRequestReview !== undefined)
168
+ || typeof item.updatedAt !== 'string') {
169
+ throw Object.assign(new Error('Platform operation journal contains an invalid operation'), { code: 'PLATFORM_OPERATION_JOURNAL_INVALID' });
170
+ }
171
+ }
172
+ return journal as PlatformOperationJournal;
173
+ }
174
+
175
+ function writeJournal(file: string, journal: PlatformOperationJournal): void {
176
+ fs.mkdirSync(path.dirname(file), { recursive: true });
177
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
178
+ try {
179
+ fs.writeFileSync(temporary, `${JSON.stringify(journal, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
180
+ fs.renameSync(temporary, file);
181
+ } finally {
182
+ try { fs.unlinkSync(temporary); } catch { /* renamed or already removed */ }
183
+ }
184
+ }
185
+
186
+ function resolveJournal(taskRef: string, cwd?: string): { taskId: string; file: string } {
187
+ const resolved = resolveTaskRef(taskRef, cwd ? { repoRoot: cwd } : {});
188
+ if (!resolved.ok) throw Object.assign(new Error(resolved.message), { code: resolved.code });
189
+ return { taskId: resolved.taskId, file: journalPath(resolved.taskDir) };
190
+ }
191
+
192
+ function recordPlatformOperation(input: RecordOperationInput): PlatformOperation {
193
+ if (!/^[a-f0-9]{64}$/u.test(input.expectedDigest) || !input.target.trim()) {
194
+ throw Object.assign(new Error('Platform operation requires a stable target and SHA-256 digest'), { code: 'PLATFORM_OPERATION_PAYLOAD_INVALID' });
195
+ }
196
+ const { taskId, file } = resolveJournal(input.taskRef, input.cwd);
197
+ const journal = parseJournal(file, taskId);
198
+ const id = operationId(input);
199
+ const previous = journal.operations.find((item) => item.id === id);
200
+ const next: PlatformOperation = {
201
+ id,
202
+ kind: input.kind,
203
+ target: input.target,
204
+ expectedDigest: input.expectedDigest,
205
+ dependency: input.dependency,
206
+ state: input.state,
207
+ attempts: Math.min(MAX_ATTEMPTS, (previous?.attempts ?? 0) + (input.state === 'pending' ? 1 : 0)),
208
+ maxAttempts: MAX_ATTEMPTS,
209
+ lastCode: input.lastCode ?? null,
210
+ ...(input.issueMetadata ? { issueMetadata: input.issueMetadata } : {}),
211
+ ...(input.issueCreate ? { issueCreate: input.issueCreate } : {}),
212
+ ...(input.pullRequest ? { pullRequest: input.pullRequest } : {}),
213
+ ...(input.pullRequestSummary ? { pullRequestSummary: input.pullRequestSummary } : {}),
214
+ ...(input.pullRequestReview ? { pullRequestReview: input.pullRequestReview } : {}),
215
+ updatedAt: new Date().toISOString()
216
+ };
217
+ const operations = previous
218
+ ? journal.operations.map((item) => item.id === id ? next : item)
219
+ : [...journal.operations, next];
220
+ writeJournal(file, { version: 1, taskId, operations });
221
+ return next;
222
+ }
223
+
224
+ function readPlatformOperationJournal(taskRef: string, cwd?: string): PlatformOperationJournal {
225
+ const { taskId, file } = resolveJournal(taskRef, cwd);
226
+ return parseJournal(file, taskId);
227
+ }
228
+
229
+ export { JOURNAL_FILE as PLATFORM_OPERATION_JOURNAL_FILE, MAX_ATTEMPTS as PLATFORM_OPERATION_MAX_ATTEMPTS, operationId, recordPlatformOperation, readPlatformOperationJournal };
230
+ export type { PlatformIssueCreateIntent, PlatformIssueMetadataIntent, PlatformOperation, PlatformOperationJournal, PlatformOperationKind, PlatformOperationState, PlatformPullRequestIntent, PlatformPullRequestReviewIntent, PlatformPullRequestSummaryIntent, RecordOperationInput };