@fitlab-ai/agent-infra 0.9.15 → 0.10.0

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 (184) hide show
  1. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  2. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  3. package/dist/lib/fs/durable-write.js +2 -0
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/manual-validation.js +17 -8
  6. package/dist/lib/internal/task-artifact.js +1 -1
  7. package/dist/lib/internal/task-lifecycle.js +2 -2
  8. package/dist/lib/internal/task-operation-registry.js +0 -1
  9. package/dist/lib/internal/task-review.js +4 -4
  10. package/dist/lib/platform/issue-comments.js +16 -10
  11. package/dist/lib/platform/issues.js +1 -1
  12. package/dist/lib/platform/pr-change-report.js +5 -4
  13. package/dist/lib/platform/pr-summary.js +64 -32
  14. package/dist/lib/platform/pull-requests.js +3 -3
  15. package/dist/lib/platform/task-identities.js +2 -9
  16. package/dist/lib/platform/verification-sync.js +6 -2
  17. package/dist/lib/pr-review/evidence-grading.js +4 -8
  18. package/dist/lib/sandbox/commands/rm.js +21 -2
  19. package/dist/lib/sandbox/control/server.js +29 -9
  20. package/dist/lib/sandbox/control/state.js +7 -1
  21. package/dist/lib/sandbox/control/workflow-executor.js +4 -14
  22. package/dist/lib/sandbox/index.js +1 -2
  23. package/dist/lib/sandbox/removal.js +116 -1011
  24. package/dist/lib/sandbox/workspace-identity.js +17 -1
  25. package/dist/lib/task/artifact-command.js +6 -28
  26. package/dist/lib/task/artifact-operations.js +16 -230
  27. package/dist/lib/task/artifact-recovery.js +430 -0
  28. package/dist/lib/task/artifact-repair-intent.js +73 -39
  29. package/dist/lib/task/commit-operation.js +31 -4
  30. package/dist/lib/task/control-authority.js +38 -7
  31. package/dist/lib/task/control-recovery.js +14 -2
  32. package/dist/lib/task/create-service.js +1 -8
  33. package/dist/lib/task/delegation-receipts.js +64 -3
  34. package/dist/lib/task/events.js +164 -22
  35. package/dist/lib/task/human-override.js +5 -8
  36. package/dist/lib/task/intermediate-cleanup.js +66 -8
  37. package/dist/lib/task/lifecycle-recovery.js +549 -0
  38. package/dist/lib/task/lifecycle.js +12 -3
  39. package/dist/lib/task/local-artifact-finalization.js +148 -123
  40. package/dist/lib/task/manual-validation-receipt.js +5 -1
  41. package/dist/lib/task/orchestration.js +52 -2
  42. package/dist/lib/task/pr-delivery-fact.js +12 -56
  43. package/dist/lib/task/recovery-warning.js +19 -0
  44. package/dist/lib/task/review-command.js +1 -0
  45. package/dist/lib/task/review-finalization.js +104 -77
  46. package/dist/lib/task/short-id.js +15 -7
  47. package/dist/lib/task/task-execution-lock.js +1 -1
  48. package/dist/lib/task/verification-engine.js +3 -14
  49. package/dist/lib/task/workflow-command.js +0 -1
  50. package/dist/lib/version.js +1 -11
  51. package/dist/lifecycle-build-manifest.json +3 -3
  52. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  53. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  54. package/lib/fs/durable-write.ts +1 -0
  55. package/lib/internal/cli-route-inventory.ts +1 -1
  56. package/lib/internal/manual-validation.ts +17 -8
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +4 -3
  59. package/lib/internal/task-operation-registry.ts +0 -1
  60. package/lib/internal/task-review.ts +4 -4
  61. package/lib/platform/issue-comments.ts +18 -10
  62. package/lib/platform/issues.ts +1 -1
  63. package/lib/platform/pr-change-report.ts +7 -5
  64. package/lib/platform/pr-summary.ts +64 -32
  65. package/lib/platform/pull-requests.ts +3 -3
  66. package/lib/platform/task-identities.ts +3 -14
  67. package/lib/platform/verification-sync.ts +6 -2
  68. package/lib/pr-review/evidence-grading.ts +4 -9
  69. package/lib/sandbox/commands/rm.ts +34 -2
  70. package/lib/sandbox/control/protocol.ts +5 -0
  71. package/lib/sandbox/control/server.ts +30 -10
  72. package/lib/sandbox/control/state.ts +9 -1
  73. package/lib/sandbox/control/workflow-executor.ts +4 -14
  74. package/lib/sandbox/index.ts +1 -2
  75. package/lib/sandbox/removal.ts +126 -1128
  76. package/lib/sandbox/workspace-identity.ts +19 -3
  77. package/lib/task/artifact-command.ts +8 -27
  78. package/lib/task/artifact-operations.ts +21 -251
  79. package/lib/task/artifact-recovery.ts +519 -0
  80. package/lib/task/artifact-repair-intent.ts +99 -51
  81. package/lib/task/commit-operation.ts +35 -4
  82. package/lib/task/control-authority.ts +47 -14
  83. package/lib/task/control-recovery.ts +13 -1
  84. package/lib/task/create-service.ts +1 -10
  85. package/lib/task/delegation-receipts.ts +73 -2
  86. package/lib/task/events.ts +187 -23
  87. package/lib/task/human-override.ts +5 -8
  88. package/lib/task/intermediate-cleanup.ts +50 -12
  89. package/lib/task/lifecycle-recovery.ts +691 -0
  90. package/lib/task/lifecycle.ts +13 -4
  91. package/lib/task/local-artifact-finalization.ts +176 -132
  92. package/lib/task/manual-validation-receipt.ts +5 -1
  93. package/lib/task/orchestration.ts +65 -0
  94. package/lib/task/pr-delivery-fact.ts +15 -62
  95. package/lib/task/recovery-warning.ts +21 -0
  96. package/lib/task/review-command.ts +1 -0
  97. package/lib/task/review-finalization.ts +106 -87
  98. package/lib/task/short-id.ts +27 -5
  99. package/lib/task/task-execution-lock.ts +1 -1
  100. package/lib/task/verification-engine.ts +3 -17
  101. package/lib/task/verification-types.ts +0 -2
  102. package/lib/task/workflow-command.ts +0 -1
  103. package/lib/version.ts +1 -15
  104. package/package.json +1 -1
  105. package/templates/.agents/rules/create-issue.en.md +1 -1
  106. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  107. package/templates/.agents/rules/issue-sync.en.md +4 -2
  108. package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
  109. package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
  110. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
  111. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  112. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  113. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  114. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  115. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  116. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  117. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  118. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  119. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  120. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  121. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  122. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
  123. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  124. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  125. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  126. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  127. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  130. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  131. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  132. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  133. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  134. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  135. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  136. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  137. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  138. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  139. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  140. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  141. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  142. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  143. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  144. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  145. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  146. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  147. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  148. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  149. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  150. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  151. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
  152. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  153. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  156. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  157. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  158. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  159. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  160. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  161. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  162. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  163. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  164. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  165. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  166. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  167. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  168. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  169. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  170. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  171. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  172. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  173. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  174. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  175. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  176. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  177. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  178. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  179. package/templates/.agents/templates/task.en.md +1 -1
  180. package/templates/.agents/templates/task.zh-CN.md +1 -1
  181. package/templates/.claude/commands/import-issue.en.md +1 -1
  182. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  183. package/templates/.claude/commands/restore-task.en.md +1 -1
  184. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
@@ -1,5 +1,4 @@
1
1
  import type { ResourceIdentity } from '../platform/resource-identity.ts';
2
- import { isLegacyCompatibilityEnabled, legacyCompatibilityError, VERSION } from '../version.ts';
3
2
  import {
4
3
  isResourceIdentity,
5
4
  parseResourceIdentity,
@@ -17,14 +16,6 @@ type PrDeliveryIdentity = {
17
16
  base: { repository: string; ref: string; sha: string };
18
17
  };
19
18
 
20
- type LegacyPrDeliveryIdentity = {
21
- repository: string;
22
- number: number;
23
- nodeId: string;
24
- url: string;
25
- head: { repository: string; ref: string; sha: string };
26
- base: { repository: string; ref: string; sha: string };
27
- };
28
19
  type CurrentPrDeliveryIdentityInput = {
29
20
  resource: ResourceIdentity;
30
21
  repository: string;
@@ -93,10 +84,6 @@ function timestamp(value: unknown, label: string): string {
93
84
  if (Number.isNaN(Date.parse(result))) throw factError(`${label} must be a valid timestamp`);
94
85
  return new Date(result).toISOString();
95
86
  }
96
- function positiveNumber(value: unknown, label: string): number {
97
- if (!Number.isSafeInteger(value) || (value as number) <= 0) throw factError(`${label} must be a positive integer`);
98
- return value as number;
99
- }
100
87
  function parseRef(value: unknown, label: string): { repository: string; ref: string; sha: string } {
101
88
  if (!isRecord(value)) throw factError(`${label} must be an object`);
102
89
  exactKeys(value, ['repository', 'ref', 'sha'], label);
@@ -115,35 +102,19 @@ function canonicalIdentity(value: unknown): PrDeliveryIdentity {
115
102
  };
116
103
  }
117
104
 
118
- function decodeLegacyIdentity(value: unknown): PrDeliveryIdentity {
119
- if (!isRecord(value)) throw factError('legacy identity must be an object');
120
- // TODO(compat): Remove the v1 identity decoder before the first stable v1.0.0 release.
121
- exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'legacy identity');
122
- const legacy = {
123
- number: positiveNumber(value.number, 'legacy identity.number'),
124
- repository: text(value.repository, 'identity.repository'),
125
- nodeId: text(value.nodeId, 'legacy identity.nodeId'),
126
- url: text(value.url, 'identity.url'),
127
- head: parseRef(value.head, 'identity.head'),
128
- base: parseRef(value.base, 'identity.base')
129
- } satisfies LegacyPrDeliveryIdentity;
130
- return {
131
- resource: { kind: 'number', value: legacy.number },
132
- repository: legacy.repository,
133
- url: legacy.url,
134
- head: legacy.head,
135
- base: legacy.base
136
- };
137
- }
138
-
139
105
  function provenanceForSource(source: PrDeliveryBindingSource): PrDeliveryProvenance { return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source; }
140
106
 
141
- function parseFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
107
+ function legacyFactError(): PrDeliveryFactError {
108
+ return Object.assign(
109
+ new Error('PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: v1 PR delivery fact is unsupported; use the previous tool version to view this task or migrate its fact to version 2 before using this version'),
110
+ { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' }
111
+ );
112
+ }
113
+
114
+ function parseFact(value: unknown): PrDeliveryFact {
142
115
  if (!isRecord(value)) throw factError('fact must be an object');
143
- if ((value.version !== 1 && value.version !== 2) || typeof value.state !== 'string') throw factError('version or state is invalid');
144
- if (value.version === 1 && !isLegacyCompatibilityEnabled(runtimeVersion)) {
145
- throw legacyCompatibilityError('v1 PR delivery fact', runtimeVersion);
146
- }
116
+ if (value.version === 1) throw legacyFactError();
117
+ if (value.version !== 2 || typeof value.state !== 'string') throw factError('version or state is invalid');
147
118
  if (value.state === 'unbound') {
148
119
  exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
149
120
  if (value.reason !== 'initial') throw factError('unbound reason is invalid');
@@ -155,24 +126,6 @@ function parseFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
155
126
  return { version: 2, state: 'skipped', reason: 'explicit', decidedAt: timestamp(value.decidedAt, 'skipped.decidedAt') };
156
127
  }
157
128
  if (value.state !== 'bound') throw factError('state is invalid');
158
- if (value.version === 1) {
159
- // TODO(compat): Remove this v1-to-v2 shape conversion before the first stable v1.0.0 release.
160
- exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'legacy bound fact');
161
- if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('legacy binding or provenance is invalid');
162
- exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'legacy binding');
163
- const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'legacy binding.issueNumber');
164
- return parseFact({
165
- version: 2,
166
- state: 'bound',
167
- identity: decodeLegacyIdentity(value.identity),
168
- binding: {
169
- status: 'verified', source: value.binding.source, verifiedAt: timestamp(value.binding.verifiedAt, 'binding.verifiedAt'),
170
- issueIdentity: issueNumber === null ? null : { kind: 'number', value: issueNumber }, remoteState: value.binding.remoteState,
171
- mergedAt: value.binding.mergedAt === null ? null : timestamp(value.binding.mergedAt, 'binding.mergedAt'), mergeCommitSha: nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha')
172
- },
173
- provenance: value.provenance
174
- }, runtimeVersion);
175
- }
176
129
  exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
177
130
  if (!isRecord(value.binding) || !isRecord(value.provenance)) throw factError('binding or provenance is invalid');
178
131
  exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueIdentity', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'binding');
@@ -195,19 +148,19 @@ function parseFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
195
148
  };
196
149
  }
197
150
 
198
- function decodePrDeliveryFact(value: unknown, runtimeVersion = VERSION): PrDeliveryFact {
151
+ function decodePrDeliveryFact(value: unknown, _runtimeVersion?: string): PrDeliveryFact {
199
152
  if (typeof value === 'string') {
200
153
  let decoded: unknown;
201
154
  try { decoded = JSON.parse(value); }
202
155
  catch { throw factError('value is not valid JSON'); }
203
- return parseFact(decoded, runtimeVersion);
156
+ return parseFact(decoded);
204
157
  }
205
- return parseFact(value, runtimeVersion);
158
+ return parseFact(value);
206
159
  }
207
160
  function encodePrDeliveryFact(fact: PrDeliveryFact): string { return JSON.stringify(parseFact(fact)); }
208
- function readPrDeliveryFact(metadata: Record<string, unknown>, runtimeVersion = VERSION): PrDeliveryFactReadResult {
161
+ function readPrDeliveryFact(metadata: Record<string, unknown>, _runtimeVersion?: string): PrDeliveryFactReadResult {
209
162
  if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '') return { status: 'missing', fact: null };
210
- try { return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY], runtimeVersion) }; }
163
+ try { return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY]) }; }
211
164
  catch (error) { return { status: 'invalid', fact: null, error: asFactError(error) }; }
212
165
  }
213
166
  function buildUnboundFact(): PrDeliveryFact { return { version: 2, state: 'unbound', reason: 'initial' }; }
@@ -0,0 +1,21 @@
1
+ export type RecoveryWarning = Readonly<{ code: string; message: string; action: string }>;
2
+
3
+ function warningText(value: unknown): value is string {
4
+ return typeof value === 'string' && value.length > 0 && value.trim() === value && !/[\r\n]/u.test(value);
5
+ }
6
+
7
+ export function isRecoveryWarning(value: unknown): value is RecoveryWarning {
8
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
9
+ const warning = value as Record<string, unknown>;
10
+ return Object.keys(warning).sort().join(',') === 'action,code,message'
11
+ && warningText(warning.code)
12
+ && warningText(warning.message)
13
+ && warningText(warning.action);
14
+ }
15
+
16
+ export function sameRecoveryWarning(left: unknown, right: unknown): boolean {
17
+ return isRecoveryWarning(left) && isRecoveryWarning(right)
18
+ && left.code === right.code
19
+ && left.message === right.message
20
+ && left.action === right.action;
21
+ }
@@ -7,6 +7,7 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
7
7
  const values: Record<string, string | boolean> = {};
8
8
  const flags = {
9
9
  '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
10
+ '--recovery-id': 'recoveryId',
10
11
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
11
12
  } as const;
12
13
  for (let index = 2; index < args.length; index += 1) {
@@ -1,6 +1,4 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
4
2
 
5
3
  import { parseArtifactName } from './artifact-name.ts';
6
4
  import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.ts';
@@ -16,9 +14,15 @@ import { allowsManualOverride } from './guard-override.ts';
16
14
  import type { ManualOverrideCapability } from './guard-override.ts';
17
15
  import { getArtifactSchema } from './artifact-schema.ts';
18
16
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
19
- import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
20
- import type { ArtifactRepairOperation } from './artifact-operations.ts';
21
- import type { ArtifactRepairIntent } from './artifact-repair-intent.ts';
17
+ import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
18
+ import {
19
+ beginArtifactRecovery,
20
+ commitArtifactRecovery,
21
+ prepareArtifactRecoveryCommit,
22
+ recoveryContextFromIntent,
23
+ stageArtifactCandidate
24
+ } from './artifact-recovery.ts';
25
+ import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
22
26
 
23
27
  type ReviewFinalizationErrorCode =
24
28
  | ResolveTaskRefErrorCode
@@ -35,8 +39,7 @@ type ReviewFinalizationErrorCode =
35
39
  | 'REVIEW_DECISION_DETAIL_INVALID'
36
40
  | 'REVIEW_ARTIFACT_CONFLICT'
37
41
  | 'REVIEW_PROVENANCE_INVALID'
38
- | 'REVIEW_TEMP_WRITE_FAILED'
39
- | 'REVIEW_RENAME_FAILED';
42
+ | 'REVIEW_RECOVERY_COMMIT_FAILED';
40
43
  type ReviewFinalizationError = { code: ReviewFinalizationErrorCode; message: string };
41
44
  type ReviewFinalizationRequest = {
42
45
  taskRef: string;
@@ -44,6 +47,7 @@ type ReviewFinalizationRequest = {
44
47
  artifact: string;
45
48
  orchestrated?: boolean;
46
49
  dryRun?: boolean;
50
+ recoveryId?: string;
47
51
  };
48
52
  type ReviewFinalizationResult = {
49
53
  status: 'planned' | 'applied' | 'no-op' | 'failed';
@@ -56,8 +60,12 @@ type ReviewFinalizationResult = {
56
60
  stageStatus: LedgerStageStatus | null;
57
61
  artifactSha256: string | null;
58
62
  semanticDigest: string | null;
59
- repairable: boolean;
60
- operation: ArtifactRepairOperation | null;
63
+ recovery?: Readonly<{
64
+ recoveryId: string;
65
+ candidatePath: string;
66
+ baselineSha256: string;
67
+ baselineSemanticDigest: string;
68
+ }>;
61
69
  operations: readonly {
62
70
  kind: 'artifact';
63
71
  artifact: string;
@@ -67,17 +75,13 @@ type ReviewFinalizationResult = {
67
75
  };
68
76
  type ReviewFileSystem = {
69
77
  readFileSync: (file: string) => string;
70
- statModeSync: (file: string) => number;
71
- writeFileSync: (file: string, content: string, mode: number) => void;
72
- renameSync: (from: string, to: string) => void;
73
- unlinkSync: (file: string) => void;
74
78
  };
75
79
  type ReviewFinalizationOptions = {
76
80
  repoRoot?: string;
77
- randomSuffix?: () => string;
78
81
  fileSystem?: Partial<ReviewFileSystem>;
79
82
  manualOverride?: ManualOverrideCapability;
80
83
  lockAlreadyHeld?: boolean;
84
+ startRecovery?: boolean;
81
85
  };
82
86
 
83
87
  const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
@@ -86,11 +90,7 @@ const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' |
86
90
  code: { family: 'review-code' }
87
91
  };
88
92
  const DEFAULT_FILE_SYSTEM: ReviewFileSystem = {
89
- readFileSync: (file) => fs.readFileSync(file, 'utf8'),
90
- statModeSync: (file) => fs.statSync(file).mode,
91
- writeFileSync: (file, content, mode) => fs.writeFileSync(file, content, { encoding: 'utf8', flag: 'wx', mode }),
92
- renameSync: (from, to) => fs.renameSync(from, to),
93
- unlinkSync: (file) => fs.unlinkSync(file)
93
+ readFileSync: (file) => fs.readFileSync(file, 'utf8')
94
94
  };
95
95
 
96
96
  function failed(
@@ -112,27 +112,17 @@ function failed(
112
112
  stageStatus,
113
113
  artifactSha256: null,
114
114
  semanticDigest: null,
115
- repairable: false,
116
- operation: null,
117
115
  operations: [],
118
116
  error: { code, message },
119
117
  ...extra
120
118
  };
121
119
  }
122
120
 
123
- function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
124
- try {
125
- fileSystem.unlinkSync(tempPath);
126
- } catch {
127
- // Best-effort cleanup preserves the primary failure.
128
- }
129
- }
130
-
131
121
  type ReviewSummaryCandidatePreparation = Readonly<{
132
122
  result: ReviewFinalizationResult;
133
123
  content: string;
134
- provenance?: Parameters<typeof writeArtifactRepairIntent>[1];
135
- expectedIntent?: ArtifactRepairIntent | null;
124
+ recovery?: ArtifactRecoveryContext;
125
+ lockAlreadyHeld?: boolean;
136
126
  }>;
137
127
 
138
128
  /** Prepare domain validation, summary bytes and provenance before any publication. */
@@ -163,39 +153,28 @@ function prepareReviewSummaryCandidate(
163
153
  let taskContent: string;
164
154
  try { taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath); }
165
155
  catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
156
+ let recovery: ArtifactRecoveryContext | undefined;
157
+ if (request.recoveryId) {
158
+ try {
159
+ const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact);
160
+ if (!intent || intent.recoveryOperationId !== request.recoveryId) return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
161
+ if (!['awaiting-recovery', 'finalize-ready', 'commit-started'].includes(intent.state)) return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
162
+ recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
163
+ artifactContent = fs.existsSync(recovery.stagingPath)
164
+ ? fs.readFileSync(recovery.stagingPath, 'utf8')
165
+ : fs.readFileSync(recovery.formalPath, 'utf8');
166
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error)); }
167
+ }
166
168
  const artifactSha256 = sha256Content(artifactContent);
167
169
  const semanticDigest = canonicalSemanticDigest(artifactContent);
168
170
  const digests = { artifactSha256, semanticDigest };
169
171
  let repairIntent;
170
- try { repairIntent = readArtifactRepairIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
172
+ try { repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
171
173
  catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
172
- const schema = getArtifactSchema(spec.family)!;
173
- const structure = inspectArtifactContract(artifactContent, schema);
174
- if (!structure.ok) {
175
- const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
176
- if (repairable && repairIntent && (repairIntent.state !== 'awaiting-repair'
177
- || repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== structure.semanticDigest)) {
178
- return reject('REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', digests);
174
+ if (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed') {
175
+ if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
176
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
179
177
  }
180
- return {
181
- ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
182
- artifactSha256, semanticDigest: structure.semanticDigest, repairable, operation: structure.repair
183
- }),
184
- ...(repairable && !repairIntent ? { provenance: {
185
- version: 2 as const, taskId: taskId!, family: spec.family, artifact: request.artifact,
186
- state: 'awaiting-repair' as const, baselineSemanticDigest: structure.semanticDigest,
187
- artifactSha256, semanticDigest: structure.semanticDigest,
188
- recoveryOperationId: null, phase: null, authorityDigest: null,
189
- requestId: `review-finalize:${taskId!}`, createdAt: Date.now(), updatedAt: Date.now()
190
- }, expectedIntent: null } : {})
191
- };
192
- }
193
- if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== semanticDigest) {
194
- return reject('REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', digests);
195
- }
196
- if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
197
- && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== semanticDigest)) {
198
- return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
199
178
  }
200
179
  if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
201
180
  return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
@@ -217,9 +196,54 @@ function prepareReviewSummaryCandidate(
217
196
  } catch (error) { return reject('REVIEW_LEDGER_INVALID', String(error)); }
218
197
  const detail = inspectDecisionDetailDuplicates(artifactContent);
219
198
  if (!detail.ok) return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
199
+ const schema = getArtifactSchema(spec.family)!;
200
+ const structure = inspectArtifactContract(artifactContent, schema);
201
+ if (!structure.ok) {
202
+ if (!recovery && !request.dryRun && options.startRecovery) {
203
+ try {
204
+ recovery = beginArtifactRecovery({
205
+ taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
206
+ requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
207
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
208
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
209
+ }
210
+ return {
211
+ ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
212
+ artifactSha256, semanticDigest: structure.semanticDigest,
213
+ ...(recovery ? { recovery: {
214
+ recoveryId: recovery.recoveryId,
215
+ candidatePath: recovery.stagingPath,
216
+ baselineSha256: recovery.baselineSha256,
217
+ baselineSemanticDigest: recovery.baselineSemanticDigest
218
+ } } : {})
219
+ }),
220
+ ...(recovery ? { recovery } : {})
221
+ };
222
+ }
220
223
  const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
221
224
  if (!transformed.ok) return reject(transformed.code, transformed.message);
222
225
  const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
226
+ if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
227
+ if (transformed.changed) return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
228
+ return {
229
+ content: transformed.content,
230
+ result: {
231
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
232
+ status: 'no-op',
233
+ changed: false,
234
+ error: null
235
+ },
236
+ lockAlreadyHeld: options.lockAlreadyHeld
237
+ };
238
+ }
239
+ if (!recovery && !request.dryRun && options.startRecovery) {
240
+ try {
241
+ recovery = beginArtifactRecovery({
242
+ taskId: taskId!, family: spec.family, artifact: request.artifact, round: parsed.round,
243
+ requestId: `review-finalize:${taskId!}:${stage}:${parsed.round}`
244
+ }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
245
+ } catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests); }
246
+ }
223
247
  return {
224
248
  content: transformed.content,
225
249
  result: {
@@ -229,10 +253,8 @@ function prepareReviewSummaryCandidate(
229
253
  operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
230
254
  error: null
231
255
  },
232
- ...(repairIntent?.state === 'awaiting-repair' ? {
233
- provenance: { ...repairIntent, state: 'passed' as const, ...finalDigests },
234
- expectedIntent: repairIntent
235
- } : {})
256
+ ...(recovery ? { recovery } : {}),
257
+ lockAlreadyHeld: options.lockAlreadyHeld
236
258
  };
237
259
  }
238
260
 
@@ -240,14 +262,28 @@ function commitReviewSummaryProvenance(
240
262
  prepared: ReviewSummaryCandidatePreparation,
241
263
  repoRoot: string
242
264
  ): ReviewFinalizationResult {
265
+ if (prepared.result.status === 'failed' || !prepared.recovery) return prepared.result;
243
266
  try {
244
- if (prepared.provenance) {
245
- writeArtifactRepairIntent(repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
267
+ const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
268
+ if (!intent) throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
269
+ let committed;
270
+ if (intent.state === 'finalize-ready' || intent.state === 'commit-started') {
271
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
272
+ } else {
273
+ const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
274
+ prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
275
+ committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
246
276
  }
247
- return prepared.result;
277
+ if (committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed.state}'`);
278
+ return {
279
+ ...prepared.result,
280
+ artifactSha256: committed.finalArtifactSha256,
281
+ semanticDigest: committed.finalSemanticDigest,
282
+ error: null
283
+ };
248
284
  } catch (error) {
249
285
  return { ...prepared.result, status: 'failed', error: {
250
- code: 'REVIEW_PROVENANCE_INVALID', message: `cannot record review provenance: ${String(error)}`
286
+ code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
251
287
  } };
252
288
  }
253
289
  }
@@ -270,24 +306,6 @@ function finalizeReviewSummaryUnlocked(
270
306
  catch (error) { return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
271
307
  const prepared = prepareReviewSummaryCandidate(request, content, options);
272
308
  if (request.dryRun) return prepared.result;
273
- if (prepared.result.status !== 'applied') return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
274
- const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
275
- try {
276
- fileSystem.writeFileSync(tempPath, prepared.content, fileSystem.statModeSync(validated.artifact.path));
277
- } catch (error) {
278
- cleanupTemp(fileSystem, tempPath);
279
- return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
280
- }
281
- try {
282
- if (fileSystem.readFileSync(validated.artifact.path) !== content) {
283
- cleanupTemp(fileSystem, tempPath);
284
- return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, prepared.result.stageStatus);
285
- }
286
- fileSystem.renameSync(tempPath, validated.artifact.path);
287
- } catch (error) {
288
- cleanupTemp(fileSystem, tempPath);
289
- return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
290
- }
291
309
  return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
292
310
  }
293
311
 
@@ -295,15 +313,16 @@ function finalizeReviewSummary(
295
313
  request: ReviewFinalizationRequest,
296
314
  options: ReviewFinalizationOptions = {}
297
315
  ): ReviewFinalizationResult {
298
- if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, options);
316
+ const effectiveOptions = { ...options, startRecovery: !request.dryRun };
317
+ if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
299
318
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
300
- if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, options);
319
+ if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
301
320
  try {
302
321
  return withTaskExecutionLock(
303
322
  resolved.repoRoot,
304
323
  resolved.taskId,
305
324
  'task-review.finalize-summary',
306
- () => finalizeReviewSummaryUnlocked(request, options)
325
+ () => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true })
307
326
  );
308
327
  } catch (error) {
309
328
  if (!(error instanceof TaskExecutionLockError)) throw error;
@@ -329,14 +329,18 @@ function mutateShortIdRegistryAt(
329
329
  activeDir: string,
330
330
  width: number,
331
331
  taskId: string,
332
- requested: ShortIdMutationEffect
332
+ requested: ShortIdMutationEffect,
333
+ onlyReleaseWhenInactive = false,
334
+ pruneOrphans = true
333
335
  ): ShortIdMutationResult {
334
336
  const registryPath = path.join(activeDir, REGISTRY_NAME);
335
337
  return withRegistryLock(activeDir, () => {
336
338
  const registry = readRegistryStrict(registryPath, width);
337
339
  const original = JSON.stringify(registry.ids);
338
- for (const [key, candidate] of Object.entries(registry.ids)) {
339
- if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))) delete registry.ids[key];
340
+ if (pruneOrphans) {
341
+ for (const [key, candidate] of Object.entries(registry.ids)) {
342
+ if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))) delete registry.ids[key];
343
+ }
340
344
  }
341
345
  const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
342
346
  let result: ShortIdMutationResult;
@@ -356,8 +360,13 @@ function mutateShortIdRegistryAt(
356
360
  result = { effect: 'allocated', shortId: key, changed: true };
357
361
  }
358
362
  } else if (requested === 'release') {
359
- if (!existing) result = { effect: 'unchanged', shortId: null, changed: false };
360
- else { delete registry.ids[existing[0]]; result = { effect: 'released', shortId: existing[0], changed: true }; }
363
+ const taskIsActive = fs.existsSync(path.join(activeDir, taskId, 'task.md'));
364
+ if (!existing || (onlyReleaseWhenInactive && taskIsActive)) {
365
+ result = { effect: 'unchanged', shortId: existing?.[0] ?? null, changed: false };
366
+ } else {
367
+ delete registry.ids[existing[0]];
368
+ result = { effect: 'released', shortId: existing[0], changed: true };
369
+ }
361
370
  } else result = { effect: 'unchanged', shortId: existing ? existing[0] : null, changed: false };
362
371
  const changed = original !== JSON.stringify(registry.ids);
363
372
  if (changed) writeRegistry(registryPath, registry);
@@ -365,6 +374,18 @@ function mutateShortIdRegistryAt(
365
374
  });
366
375
  }
367
376
 
377
+ function releaseStaleShortIdRegistry(repoRoot: string, taskId: string): ShortIdMutationResult {
378
+ const activeDir = path.join(repoRoot, '.agents', 'workspace', 'active');
379
+ return mutateShortIdRegistryAt(
380
+ activeDir,
381
+ configuredShortIdLength(repoRoot),
382
+ taskId,
383
+ 'release',
384
+ true,
385
+ false
386
+ );
387
+ }
388
+
368
389
  function executeShortIdCommand(request: ShortIdCommandRequest): ShortIdCommandResult {
369
390
  try {
370
391
  if (!Number.isInteger(request.shortIdLength) || request.shortIdLength < 1) {
@@ -550,6 +571,7 @@ export {
550
571
  inspectShortIdRegistry,
551
572
  configuredShortIdLength,
552
573
  mutateShortIdRegistry,
574
+ releaseStaleShortIdRegistry,
553
575
  executeShortIdCommand
554
576
  };
555
577
  export type {
@@ -293,7 +293,7 @@ function withTaskExecutionLock<T>(
293
293
  return value;
294
294
  } catch (error) {
295
295
  if (error instanceof TaskExecutionLockError) throw error;
296
- if (error instanceof Error && error.name === 'OrchestrationStateError') throw error;
296
+ if (error instanceof Error && ['OrchestrationStateError', 'ArtifactRecoveryError'].includes(error.name)) throw error;
297
297
  throw new TaskExecutionLockError(
298
298
  'ORCHESTRATION_LOCK_FAILED',
299
299
  'lifecycle task lock operation failed',
@@ -48,6 +48,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
48
48
  import { readManualValidationCompletion } from "./manual-validation-completion.ts";
49
49
  import { sha256File } from "./artifact-receipts.ts";
50
50
  import { summaryCommentState } from "../platform/pr-summary.ts";
51
+ import { taskIssueIdentity } from "../platform/task-identities.ts";
51
52
 
52
53
  const TASK_ENUMS = {
53
54
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
@@ -100,8 +101,6 @@ const sharedUtils = {
100
101
  failResult,
101
102
  blockedResult,
102
103
  safeStat,
103
- parseIssueNumber,
104
- parsePrNumber
105
104
  };
106
105
 
107
106
  async function runCheck(type: any, context: any, shared: any): Promise<any> {
@@ -361,8 +360,8 @@ function checkTaskMeta({ taskDir, config, repositoryRoot }: any): any {
361
360
  );
362
361
  }
363
362
 
364
- if (config.require_issue_number && !parseIssueNumber(metadata.issue_number)) {
365
- return failResult("task-meta", "Expected a valid issue_number in task metadata");
363
+ if (config.require_platform_issue_identity && !taskIssueIdentity(metadata)) {
364
+ return failResult("task-meta", "Expected a valid platform_issue_identity in task metadata");
366
365
  }
367
366
 
368
367
  if (config.require_completed_at && isBlank(metadata.completed_at)) {
@@ -1376,19 +1375,6 @@ function getCheckedRequirements(content: any): any {
1376
1375
  .map((match: any) => match[1].trim());
1377
1376
  }
1378
1377
 
1379
- function parseIssueNumber(value: any): any {
1380
- if (isBlank(value) || value === "N/A") {
1381
- return null;
1382
- }
1383
-
1384
- const match = String(value).match(/\d+/);
1385
- return match ? Number(match[0]) : null;
1386
- }
1387
-
1388
- function parsePrNumber(value: any): any {
1389
- return parseIssueNumber(value);
1390
- }
1391
-
1392
1378
  // === Utilities ===
1393
1379
 
1394
1380
  function normalizeContent(text: any): any {
@@ -11,8 +11,6 @@ export type VerificationShared = {
11
11
  failResult: (type: string, message: string, failType?: string) => VerificationCheckResult;
12
12
  blockedResult: (type: string, message: string, failType?: string) => VerificationCheckResult;
13
13
  safeStat: (filePath: string) => fs.Stats | null;
14
- parseIssueNumber: (value: unknown) => number | null;
15
- parsePrNumber: (value: unknown) => number | null;
16
14
  };
17
15
 
18
16
  type VerificationStatus = 'pass' | 'fail' | 'blocked';
@@ -1,7 +1,6 @@
1
1
  export const TASK_WORKFLOW_COMMANDS = Object.freeze({
2
2
  'artifact-inspect': ['task-artifact', 'inspect'],
3
3
  'artifact-init': ['task-artifact', 'init'],
4
- 'artifact-repair': ['task-artifact', 'repair'],
5
4
  'artifact-finalize-local': ['task-artifact', 'finalize-local'],
6
5
  'review-finalize-summary': ['task-review', 'finalize-summary'],
7
6
  event: ['task-event', null],
package/lib/version.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
- import semver from 'semver';
3
2
 
4
3
  const packageJsonUrl = [
5
4
  new URL('../package.json', import.meta.url),
@@ -13,22 +12,9 @@ if (!packageJsonUrl) {
13
12
  const { version } = JSON.parse(readFileSync(packageJsonUrl, 'utf8'));
14
13
  const VERSION = `v${version}`;
15
14
  const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
16
- const LEGACY_COMPATIBILITY_CUTOFF_VERSION = '1.0.0';
17
15
 
18
16
  function isValidAgentInfraVersion(value: unknown): value is string {
19
17
  return typeof value === 'string' && AGENT_INFRA_VERSION_PATTERN.test(value);
20
18
  }
21
19
 
22
- function isLegacyCompatibilityEnabled(runtimeVersion = VERSION): boolean {
23
- const normalized = runtimeVersion.startsWith('v') ? runtimeVersion.slice(1) : runtimeVersion;
24
- const parsed = semver.valid(normalized);
25
- return parsed !== null && semver.lt(parsed, LEGACY_COMPATIBILITY_CUTOFF_VERSION);
26
- }
27
-
28
- function legacyCompatibilityError(resource: string, runtimeVersion = VERSION): Error & { code: string } {
29
- return Object.assign(new Error(
30
- `PLATFORM_IDENTITY_LEGACY_UNSUPPORTED: ${resource} requires migration to the current schema before v${LEGACY_COMPATIBILITY_CUTOFF_VERSION} runtime ${runtimeVersion}`
31
- ), { code: 'PLATFORM_IDENTITY_LEGACY_UNSUPPORTED' });
32
- }
33
-
34
- export { LEGACY_COMPATIBILITY_CUTOFF_VERSION, VERSION, isLegacyCompatibilityEnabled, isValidAgentInfraVersion, legacyCompatibilityError };
20
+ export { VERSION, isValidAgentInfraVersion };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.9.15",
3
+ "version": "0.10.0",
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",