@fitlab-ai/agent-infra 0.9.15 → 0.11.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 (224) hide show
  1. package/README.md +2 -6
  2. package/README.zh-CN.md +2 -6
  3. package/bin/internal-cli.sh +0 -13
  4. package/dist/bin/internal-cli.js +11 -49
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  7. package/dist/lib/fs/durable-write.js +2 -0
  8. package/dist/lib/internal/cli-route-inventory.js +3 -4
  9. package/dist/lib/internal/manual-validation.js +17 -8
  10. package/dist/lib/internal/platform-comment.js +60 -2
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-finalization.js +2 -2
  13. package/dist/lib/internal/task-lifecycle.js +4 -4
  14. package/dist/lib/internal/task-operation-registry.js +22 -13
  15. package/dist/lib/internal/task-orchestration.js +2 -2
  16. package/dist/lib/internal/task-review.js +12 -7
  17. package/dist/lib/platform/activity-recovery.js +159 -0
  18. package/dist/lib/platform/issue-comments.js +89 -31
  19. package/dist/lib/platform/issues.js +1 -1
  20. package/dist/lib/platform/pr-change-report.js +5 -4
  21. package/dist/lib/platform/pr-summary.js +64 -32
  22. package/dist/lib/platform/pull-requests.js +3 -3
  23. package/dist/lib/platform/task-comment-projection.js +70 -0
  24. package/dist/lib/platform/task-identities.js +2 -9
  25. package/dist/lib/platform/task-recovery.js +55 -0
  26. package/dist/lib/platform/verification-sync.js +45 -7
  27. package/dist/lib/pr-review/evidence-grading.js +4 -8
  28. package/dist/lib/sandbox/commands/rm.js +21 -2
  29. package/dist/lib/sandbox/control/executor.js +4 -2
  30. package/dist/lib/sandbox/control/server.js +29 -9
  31. package/dist/lib/sandbox/control/state.js +7 -1
  32. package/dist/lib/sandbox/control/task-workflow.js +1 -1
  33. package/dist/lib/sandbox/control/workflow-executor.js +87 -35
  34. package/dist/lib/sandbox/index.js +1 -2
  35. package/dist/lib/sandbox/removal.js +116 -1011
  36. package/dist/lib/sandbox/workspace-identity.js +17 -1
  37. package/dist/lib/server/process-state.js +17 -5
  38. package/dist/lib/task/artifact-command.js +11 -30
  39. package/dist/lib/task/artifact-operations.js +17 -231
  40. package/dist/lib/task/artifact-recovery.js +476 -0
  41. package/dist/lib/task/artifact-repair-intent.js +90 -39
  42. package/dist/lib/task/commit-operation.js +31 -4
  43. package/dist/lib/task/control-authority.js +39 -8
  44. package/dist/lib/task/control-recovery.js +14 -2
  45. package/dist/lib/task/create-service.js +1 -8
  46. package/dist/lib/task/delegation-receipts.js +64 -3
  47. package/dist/lib/task/events.js +165 -23
  48. package/dist/lib/task/human-override.js +5 -8
  49. package/dist/lib/task/intermediate-cleanup.js +91 -8
  50. package/dist/lib/task/lifecycle-recovery.js +549 -0
  51. package/dist/lib/task/lifecycle.js +12 -3
  52. package/dist/lib/task/local-artifact-finalization.js +212 -123
  53. package/dist/lib/task/manual-validation-receipt.js +5 -1
  54. package/dist/lib/task/orchestration.js +52 -2
  55. package/dist/lib/task/pr-delivery-fact.js +12 -56
  56. package/dist/lib/task/recovery-warning.js +19 -0
  57. package/dist/lib/task/review-command.js +3 -2
  58. package/dist/lib/task/review-finalization.js +294 -74
  59. package/dist/lib/{host-control → task}/secure-fs.js +8 -19
  60. package/dist/lib/task/short-id.js +15 -7
  61. package/dist/lib/task/task-execution-lock.js +1 -1
  62. package/dist/lib/task/verification-engine.js +3 -14
  63. package/dist/lib/task/workflow-command.js +2 -1
  64. package/dist/lib/task/workflow-dispatch.js +161 -0
  65. package/dist/lib/version.js +1 -11
  66. package/dist/lifecycle-build-manifest.json +3 -3
  67. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  68. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  69. package/lib/fs/durable-write.ts +1 -0
  70. package/lib/internal/cli-route-inventory.ts +3 -4
  71. package/lib/internal/manual-validation.ts +17 -8
  72. package/lib/internal/platform-comment.ts +53 -2
  73. package/lib/internal/task-artifact.ts +1 -1
  74. package/lib/internal/task-finalization.ts +1 -1
  75. package/lib/internal/task-lifecycle.ts +5 -4
  76. package/lib/internal/task-operation-registry.ts +22 -13
  77. package/lib/internal/task-orchestration.ts +1 -1
  78. package/lib/internal/task-review.ts +11 -7
  79. package/lib/platform/activity-recovery.ts +179 -0
  80. package/lib/platform/issue-comments.ts +105 -30
  81. package/lib/platform/issues.ts +1 -1
  82. package/lib/platform/pr-change-report.ts +7 -5
  83. package/lib/platform/pr-summary.ts +64 -32
  84. package/lib/platform/pull-requests.ts +3 -3
  85. package/lib/platform/task-comment-projection.ts +79 -0
  86. package/lib/platform/task-identities.ts +3 -14
  87. package/lib/platform/task-recovery.ts +54 -0
  88. package/lib/platform/verification-sync.ts +48 -11
  89. package/lib/pr-review/evidence-grading.ts +4 -9
  90. package/lib/sandbox/commands/rm.ts +34 -2
  91. package/lib/sandbox/control/executor.ts +4 -2
  92. package/lib/sandbox/control/protocol.ts +6 -1
  93. package/lib/sandbox/control/server.ts +30 -10
  94. package/lib/sandbox/control/state.ts +9 -1
  95. package/lib/sandbox/control/task-workflow.ts +1 -1
  96. package/lib/sandbox/control/workflow-executor.ts +106 -35
  97. package/lib/sandbox/index.ts +1 -2
  98. package/lib/sandbox/removal.ts +126 -1128
  99. package/lib/sandbox/workspace-identity.ts +19 -3
  100. package/lib/server/process-state.ts +26 -5
  101. package/lib/task/artifact-command.ts +12 -29
  102. package/lib/task/artifact-operations.ts +22 -252
  103. package/lib/task/artifact-recovery.ts +572 -0
  104. package/lib/task/artifact-repair-intent.ts +126 -51
  105. package/lib/task/commit-operation.ts +35 -4
  106. package/lib/task/control-authority.ts +48 -15
  107. package/lib/task/control-recovery.ts +13 -1
  108. package/lib/task/create-service.ts +1 -10
  109. package/lib/task/delegation-receipts.ts +73 -2
  110. package/lib/task/events.ts +188 -24
  111. package/lib/task/human-override.ts +5 -8
  112. package/lib/task/intermediate-cleanup.ts +66 -12
  113. package/lib/task/lifecycle-recovery.ts +691 -0
  114. package/lib/task/lifecycle.ts +13 -4
  115. package/lib/task/local-artifact-finalization.ts +249 -131
  116. package/lib/task/manual-validation-receipt.ts +5 -1
  117. package/lib/task/orchestration.ts +65 -0
  118. package/lib/task/pr-delivery-fact.ts +15 -62
  119. package/lib/task/recovery-warning.ts +21 -0
  120. package/lib/task/review-command.ts +2 -1
  121. package/lib/task/review-finalization.ts +298 -84
  122. package/lib/{host-control → task}/secure-fs.ts +10 -32
  123. package/lib/task/short-id.ts +27 -5
  124. package/lib/task/task-execution-lock.ts +1 -1
  125. package/lib/task/verification-engine.ts +3 -17
  126. package/lib/task/verification-types.ts +0 -2
  127. package/lib/task/workflow-command.ts +2 -1
  128. package/lib/task/workflow-dispatch.ts +171 -0
  129. package/lib/version.ts +1 -15
  130. package/package.json +1 -1
  131. package/templates/.agents/rules/create-issue.en.md +1 -1
  132. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  133. package/templates/.agents/rules/issue-sync.en.md +12 -2
  134. package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
  135. package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
  136. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
  137. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  138. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  139. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  140. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  141. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  142. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  143. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  144. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  145. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  146. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  147. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  148. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
  149. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  150. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  151. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  152. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  153. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  154. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  155. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  157. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  158. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  159. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  160. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  161. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  162. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  163. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  164. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  165. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  166. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  167. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  168. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  169. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  170. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  171. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  172. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  173. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  174. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  175. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  176. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
  177. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
  178. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  179. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  180. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  181. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  182. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  183. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  184. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  185. package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
  186. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
  187. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  188. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  189. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  190. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  191. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  192. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  193. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  194. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  195. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  196. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  197. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  198. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  199. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  200. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  201. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  202. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  203. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  204. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  205. package/templates/.agents/templates/task.en.md +1 -1
  206. package/templates/.agents/templates/task.zh-CN.md +1 -1
  207. package/templates/.claude/commands/import-issue.en.md +1 -1
  208. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  209. package/templates/.claude/commands/restore-task.en.md +1 -1
  210. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  211. package/dist/lib/host-control/audit.js +0 -19
  212. package/dist/lib/host-control/client.js +0 -142
  213. package/dist/lib/host-control/command.js +0 -49
  214. package/dist/lib/host-control/path.js +0 -160
  215. package/dist/lib/host-control/server.js +0 -159
  216. package/dist/lib/host-control/service.js +0 -112
  217. package/dist/lib/internal/host-control.js +0 -28
  218. package/lib/host-control/audit.ts +0 -22
  219. package/lib/host-control/client.ts +0 -172
  220. package/lib/host-control/command.ts +0 -60
  221. package/lib/host-control/path.ts +0 -167
  222. package/lib/host-control/server.ts +0 -180
  223. package/lib/host-control/service.ts +0 -133
  224. package/lib/internal/host-control.ts +0 -28
@@ -23,7 +23,7 @@ export type SandboxCleanupTarget = Readonly<{
23
23
  requestedRef: string;
24
24
  branch: string;
25
25
  workspace: SandboxWorkspaceKey;
26
- taskState: TaskWorkspaceState | 'branch-only';
26
+ taskState: TaskWorkspaceState | 'branch-only' | 'unknown';
27
27
  }>;
28
28
 
29
29
  export type SandboxContainerWorkspaceIdentity =
@@ -147,14 +147,30 @@ export function resolveSandboxReentryContext(params: Readonly<{
147
147
  export function resolveSandboxCleanupTarget(
148
148
  requestedRef: string,
149
149
  repoRoot: string,
150
- options: Readonly<{ allowProtected?: boolean }> = {}
150
+ options: Readonly<{
151
+ allowProtected?: boolean;
152
+ resolveMissingTask?: (taskId: string) => string | null;
153
+ }> = {}
151
154
  ): SandboxCleanupTarget {
152
155
  if (isRemovedHashShortIdInput(requestedRef)) {
153
156
  throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
154
157
  }
155
158
 
156
159
  if (TASK_ID_RE.test(requestedRef)) {
157
- const task = resolveTaskWorkspace(requestedRef, repoRoot);
160
+ let task: ReturnType<typeof resolveTaskWorkspace>;
161
+ try {
162
+ task = resolveTaskWorkspace(requestedRef, repoRoot);
163
+ } catch (error) {
164
+ if (!(error instanceof Error && error.message === `Task not found: ${requestedRef}`)) throw error;
165
+ const branch = options.resolveMissingTask?.(requestedRef);
166
+ if (!branch) throw error;
167
+ return {
168
+ requestedRef,
169
+ branch,
170
+ workspace: { mode: 'task-bound', taskId: requestedRef },
171
+ taskState: 'unknown'
172
+ };
173
+ }
158
174
  if (!options.allowProtected && (task.state === 'blocked' || task.state === 'archive')) {
159
175
  throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${requestedRef} is ${task.state}`);
160
176
  }
@@ -12,6 +12,11 @@ export type ProcessIdentity = Readonly<{
12
12
  export type ProcessIdentityState = 'alive' | 'dead' | 'unknown';
13
13
  export type ProcessIdentityProbe = (identity: ProcessIdentity) => ProcessIdentityState;
14
14
 
15
+ type ProcessIdentityRuntime = Readonly<{
16
+ probePid?: (pid: number) => void;
17
+ execFileSync?: typeof execFileSync;
18
+ }>;
19
+
15
20
  export type PidRecord = ProcessIdentity & {
16
21
  version: 2;
17
22
  };
@@ -132,7 +137,8 @@ export function getProcessStartTime(
132
137
 
133
138
  export function getProcessIdentityState(
134
139
  identity: ProcessIdentity,
135
- platform: NodeJS.Platform = process.platform
140
+ platform: NodeJS.Platform = process.platform,
141
+ runtime: ProcessIdentityRuntime = {}
136
142
  ): ProcessIdentityState {
137
143
  if (!Number.isInteger(identity.pid) || identity.pid <= 0 || !Number.isSafeInteger(identity.startTime)) {
138
144
  return 'dead';
@@ -147,8 +153,9 @@ export function getProcessIdentityState(
147
153
  return (error as NodeJS.ErrnoException).code === 'ENOENT' ? 'dead' : 'unknown';
148
154
  }
149
155
  }
156
+ const probePid = runtime.probePid ?? ((pid: number) => process.kill(pid, 0));
150
157
  try {
151
- process.kill(identity.pid, 0);
158
+ probePid(identity.pid);
152
159
  } catch (error) {
153
160
  const code = (error as NodeJS.ErrnoException).code;
154
161
  if (code === 'ESRCH') return 'dead';
@@ -157,20 +164,34 @@ export function getProcessIdentityState(
157
164
  const query = buildProcessStartTimeQuery(identity.pid, platform);
158
165
  if (query === null) return 'unknown';
159
166
  try {
160
- const output = execFileSync(query.command, query.args, {
167
+ const output = (runtime.execFileSync ?? execFileSync)(query.command, query.args, {
161
168
  encoding: 'utf8',
162
169
  env: platform === 'darwin' ? { ...process.env, LC_ALL: 'C', LANG: 'C', TZ: 'UTC' } : process.env
163
170
  }).trim();
164
- if (output.length === 0) return 'unknown';
171
+ if (output.length === 0) return recheckDarwinProcessExit(identity.pid, platform, probePid);
165
172
  const startTime = platform === 'win32'
166
173
  ? new Date(output).getTime()
167
174
  : platform === 'darwin'
168
175
  ? parseDarwinStartTime(output)
169
176
  : null;
170
- if (startTime === null || Number.isNaN(startTime)) return 'unknown';
177
+ if (startTime === null || Number.isNaN(startTime)) return recheckDarwinProcessExit(identity.pid, platform, probePid);
171
178
  return startTime === identity.startTime ? 'alive' : 'dead';
172
179
  } catch {
180
+ return recheckDarwinProcessExit(identity.pid, platform, probePid);
181
+ }
182
+ }
183
+
184
+ function recheckDarwinProcessExit(
185
+ pid: number,
186
+ platform: NodeJS.Platform,
187
+ probePid: (pid: number) => void
188
+ ): ProcessIdentityState {
189
+ if (platform !== 'darwin') return 'unknown';
190
+ try {
191
+ probePid(pid);
173
192
  return 'unknown';
193
+ } catch (error) {
194
+ return (error as NodeJS.ErrnoException).code === 'ESRCH' ? 'dead' : 'unknown';
174
195
  }
175
196
  }
176
197
 
@@ -1,36 +1,33 @@
1
1
  import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { readStableFileSync } from '../host-control/secure-fs.ts';
4
2
 
5
3
  import { resolveArtifactContext, hasOpenArtifactRound } from './artifact-lifecycle.ts';
6
4
  import { parseArtifactName } from './artifact-name.ts';
7
5
  import { resolveTaskRef } from './resolve-ref.ts';
8
6
  import { getArtifactSchema } from './artifact-schema.ts';
9
- import { finalizeLocalArtifact } from './local-artifact-finalization.ts';
10
- import { applyArtifactRepair, initializeArtifactSkeleton, inspectArtifactStructure } from './artifact-operations.ts';
7
+ import { finalizeLocalArtifact, preflightLocalArtifact } from './local-artifact-finalization.ts';
8
+ import { initializeArtifactSkeleton } from './artifact-operations.ts';
11
9
 
12
10
  export type ArtifactCommand = Readonly<{
13
11
  taskRef: string;
14
- operation: 'inspect' | 'init' | 'repair' | 'finalize-local';
12
+ operation: 'inspect' | 'init' | 'preflight' | 'finalize-local';
15
13
  family: string;
16
14
  artifact: string;
17
15
  locale?: 'zh-CN' | 'en';
18
- expectedSha256: string;
19
- expectedSemanticDigest: string;
16
+ recoveryId?: string;
20
17
  }>;
21
18
 
22
19
  /** One option contract for the CLI and trusted projection executor. */
23
20
  export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
24
21
  const [taskRef, operation] = args;
25
- if (!taskRef || !['inspect', 'init', 'repair', 'finalize-local'].includes(operation ?? '')) {
22
+ if (!taskRef || !['inspect', 'init', 'preflight', 'finalize-local'].includes(operation ?? '')) {
26
23
  throw new Error('task ref and a supported artifact operation are required');
27
24
  }
28
25
  const fields: Record<string, string> = {};
29
26
  const allowed = {
30
27
  inspect: ['--family'],
31
28
  init: ['--family', '--artifact', '--locale'],
32
- repair: ['--family', '--artifact', '--expected-sha256', '--expected-semantic-digest'],
33
- 'finalize-local': ['--family', '--artifact']
29
+ 'preflight': ['--family', '--artifact', '--recovery-id'],
30
+ 'finalize-local': ['--family', '--artifact', '--recovery-id']
34
31
  }[operation as ArtifactCommand['operation']];
35
32
  for (let index = 2; index < args.length; index += 1) {
36
33
  const flag = args[index]!;
@@ -44,13 +41,9 @@ export function parseArtifactCommand(args: readonly string[]): ArtifactCommand {
44
41
  if (operation !== 'inspect' && !fields['--artifact']) throw new Error("option '--artifact' is required");
45
42
  const locale = fields['--locale'];
46
43
  if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en') throw new Error("option '--locale' must be 'zh-CN' or 'en'");
47
- if (operation === 'repair' && !['--expected-sha256', '--expected-semantic-digest'].every((flag) => /^[a-f0-9]{64}$/u.test(fields[flag] ?? ''))) {
48
- throw new Error('repair requires lowercase 64-character digests');
49
- }
50
44
  return {
51
45
  taskRef, operation: operation as ArtifactCommand['operation'], family: fields['--family'],
52
- artifact: fields['--artifact'] ?? '', locale,
53
- expectedSha256: fields['--expected-sha256'] ?? '', expectedSemanticDigest: fields['--expected-semantic-digest'] ?? ''
46
+ artifact: fields['--artifact'] ?? '', locale, recoveryId: fields['--recovery-id']
54
47
  };
55
48
  }
56
49
  /** Resolve authoritative task metadata separately from a trusted candidate directory. */
@@ -58,7 +51,7 @@ export function executeArtifactCommand(
58
51
  command: ArtifactCommand,
59
52
  options: Readonly<{ repoRoot?: string; artifactDir?: string }> = {}
60
53
  ): Record<string, unknown> {
61
- const { taskRef, operation, family, artifact, locale, expectedSha256, expectedSemanticDigest } = command;
54
+ const { taskRef, operation, family, artifact, locale, recoveryId } = command;
62
55
  const fail = (code: string, message: string) => ({ status: 'failed', changed: false, error: { code, message } });
63
56
  if (operation === 'inspect') {
64
57
  const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
@@ -86,19 +79,9 @@ export function executeArtifactCommand(
86
79
  }
87
80
  return { ...initializeArtifactSkeleton({ ...input, ...(locale ? { locale } : {}) }), ...identity };
88
81
  }
89
- if (operation === 'repair') {
90
- let content: string;
91
- try { content = readStableFileSync(path.join(taskDir, artifact), { maxBytes: 1024 * 1024 }).bytes.toString('utf8'); }
92
- catch (error) { return fail('ARTIFACT_REPAIR_TARGET_INVALID', String(error)); }
93
- const inspection = inspectArtifactStructure(content, schema);
94
- if (!inspection.repair) {
95
- return { ...fail('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available'),
96
- artifact, family, diagnostics: inspection.diagnostics };
97
- }
98
- return { ...applyArtifactRepair({ ...input, expectedSha256, expectedSemanticDigest, operation: inspection.repair }), ...identity };
99
- }
100
82
  if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
101
- return fail('ARTIFACT_PAYLOAD_INVALID', "finalize-local only supports 'analysis', 'plan', and 'code'");
83
+ return fail('ARTIFACT_PAYLOAD_INVALID', "preflight and finalize-local only support 'analysis', 'plan', and 'code'");
102
84
  }
103
- return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot }) };
85
+ if (operation === 'preflight') return { ...preflightLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
86
+ return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
104
87
  }
@@ -1,33 +1,20 @@
1
1
  import fs from 'node:fs';
2
- import { parseArtifactName } from './artifact-name.ts';
3
2
  import path from 'node:path';
4
3
  import { createHash } from 'node:crypto';
5
- import { readStableFileSync } from '../host-control/secure-fs.ts';
4
+ import { readStableFileSync } from './secure-fs.ts';
5
+ import { parseArtifactName } from './artifact-name.ts';
6
6
 
7
7
  import { scanVisibleMarkdown, type VisibleMarkdown, type VisibleHeading } from './markdown.ts';
8
- import { hasOpenArtifactRound } from './artifact-lifecycle.ts';
9
- import { readArtifactRepairIntent } from './artifact-repair-intent.ts';
10
- import { resolveTaskRef } from './resolve-ref.ts';
11
8
  import {
12
9
  getArtifactSchema,
13
10
  renderArtifactSkeleton
14
11
  } from './artifact-schema.ts';
12
+ import { withTaskExecutionLock } from './task-execution-lock.ts';
15
13
  import type {
16
14
  ArtifactSchema,
17
15
  ArtifactSchemaFamily,
18
16
  ArtifactSection
19
17
  } from './artifact-schema.ts';
20
- import { withTaskExecutionLock } from './task-execution-lock.ts';
21
-
22
- type ArtifactRepairOperation = Readonly<{
23
- kind: 'replace-line' | 'insert-section';
24
- sectionId: string;
25
- line: number;
26
- from: string;
27
- to: string;
28
- start: number;
29
- end: number;
30
- }>;
31
18
 
32
19
  type ArtifactStructuralDiagnosticCode =
33
20
  | 'ARTIFACT_EMPTY'
@@ -47,8 +34,6 @@ type ArtifactStructuralDiagnostic = Readonly<{
47
34
  message: string;
48
35
  sectionId: string | null;
49
36
  line: number | null;
50
- repairable: boolean;
51
- operation?: ArtifactRepairOperation;
52
37
  }>;
53
38
 
54
39
  type ArtifactStructureResult = Readonly<{
@@ -56,7 +41,6 @@ type ArtifactStructureResult = Readonly<{
56
41
  family: ArtifactSchemaFamily;
57
42
  semanticDigest: string;
58
43
  diagnostics: readonly ArtifactStructuralDiagnostic[];
59
- repair: ArtifactRepairOperation | null;
60
44
  }>;
61
45
 
62
46
  type ArtifactFileResult = Readonly<{
@@ -64,21 +48,9 @@ type ArtifactFileResult = Readonly<{
64
48
  changed: boolean;
65
49
  artifactSha256: string | null;
66
50
  semanticDigest: string | null;
67
- operation: ArtifactRepairOperation | null;
68
51
  error: { code: string; message: string } | null;
69
52
  }>;
70
53
 
71
- type ArtifactRepairRequest = Readonly<{
72
- repoRoot: string;
73
- taskId: string;
74
- taskDir: string;
75
- family: ArtifactSchemaFamily;
76
- artifact: string;
77
- expectedSha256: string;
78
- expectedSemanticDigest: string;
79
- operation: ArtifactRepairOperation;
80
- }>;
81
-
82
54
  type ArtifactInitRequest = Readonly<{
83
55
  repoRoot: string;
84
56
  taskId: string;
@@ -147,71 +119,17 @@ function diagnostic(
147
119
  code: ArtifactStructuralDiagnosticCode,
148
120
  message: string,
149
121
  sectionId: string | null,
150
- line: number | null,
151
- repairable = false,
152
- operation?: ArtifactRepairOperation
122
+ line: number | null
153
123
  ): ArtifactStructuralDiagnostic {
154
- return {
155
- code, message, sectionId, line, repairable,
156
- ...(operation ? { operation } : {})
157
- };
124
+ return { code, message, sectionId, line };
158
125
  }
159
126
 
160
127
  function sectionHeadings(schema: ArtifactSchema, section: ArtifactSection): readonly string[] {
161
128
  return [section.headings.zh, section.headings.en];
162
129
  }
163
130
 
164
- function createHeadingRepair(
165
- content: string,
166
- section: ArtifactSection,
167
- heading: { start: number; end: number; text: string }
168
- ): ArtifactRepairOperation {
169
- const to = heading.text.slice(0, -1);
170
- return {
171
- kind: 'replace-line',
172
- sectionId: section.id,
173
- line: lineNumber(content, heading.start),
174
- from: heading.text,
175
- to,
176
- start: heading.start,
177
- end: heading.end
178
- };
179
- }
180
-
181
- function createSectionInsertionRepair(
182
- content: string,
183
- section: ArtifactSection,
184
- marker: { start: number; line: number },
185
- locale: 'zh-CN' | 'en'
186
- ): ArtifactRepairOperation {
187
- return {
188
- kind: 'insert-section',
189
- sectionId: section.id,
190
- line: marker.line,
191
- from: '',
192
- to: `## ${section.headings[locale === 'en' ? 'en' : 'zh']}\n`,
193
- start: marker.start,
194
- end: marker.start
195
- };
196
- }
197
-
198
- function applyRepairOperation(content: string, operation: ArtifactRepairOperation): string | null {
199
- if (operation.start < 0 || operation.end < operation.start || operation.end > content.length) return null;
200
- if (operation.kind === 'insert-section') {
201
- if (operation.from !== '' || operation.start !== operation.end || !operation.to) return null;
202
- return `${content.slice(0, operation.start)}${operation.to}${content.slice(operation.end)}`;
203
- }
204
- const raw = content.slice(operation.start, operation.end);
205
- const offset = raw.indexOf(operation.from);
206
- if (!operation.from || offset < 0 || raw.indexOf(operation.from, offset + operation.from.length) >= 0) return null;
207
- return `${content.slice(0, operation.start)}${raw.slice(0, offset)}${operation.to}${raw.slice(offset + operation.from.length)}${content.slice(operation.end)}`;
208
- }
209
-
210
- function canonicalContent(content: string, operation: ArtifactRepairOperation | null = null): string {
131
+ function canonicalContent(content: string): string {
211
132
  let normalized = content;
212
- if (operation) {
213
- normalized = applyRepairOperation(normalized, operation) ?? normalized;
214
- }
215
133
  normalized = normalized.replace(/\r\n/g, '\n');
216
134
  normalized = normalized.replace(/^<!--\s*artifact-context:[^\n]+-->\s*\n?/gm, '');
217
135
  normalized = normalized.replace(/^\s*<!--\s*artifact-section:[^\n]+-->\s*\n?/gm, '');
@@ -219,8 +137,8 @@ function canonicalContent(content: string, operation: ArtifactRepairOperation |
219
137
  return normalized;
220
138
  }
221
139
 
222
- function canonicalSemanticDigest(content: string, operation: ArtifactRepairOperation | null = null): string {
223
- return sha256Content(canonicalContent(content, operation));
140
+ function canonicalSemanticDigest(content: string): string {
141
+ return sha256Content(canonicalContent(content));
224
142
  }
225
143
 
226
144
  function inspectArtifactStructure(
@@ -229,7 +147,6 @@ function inspectArtifactStructure(
229
147
  ): ArtifactStructureResult {
230
148
  const diagnostics: ArtifactStructuralDiagnostic[] = [];
231
149
  const scanned = scanVisibleMarkdown(content);
232
- const repairCandidates: ArtifactRepairOperation[] = [];
233
150
  if (!content.trim()) diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
234
151
  if (scanned.hasUnclosedFence) diagnostics.push(diagnostic('ARTIFACT_UNCLOSED_FENCE', 'artifact contains an unclosed Markdown fence', null, null));
235
152
 
@@ -238,7 +155,7 @@ function inspectArtifactStructure(
238
155
  const aliases = sectionHeadings(schema, section);
239
156
  const exact = scanned.headings.filter((heading) => heading.level === 2 && aliases.includes(heading.text));
240
157
  const punctuated = scanned.headings.filter((heading) => heading.level === 2 && aliases.some((alias) => heading.text === `${alias}:` || heading.text === `${alias}:`));
241
- headingsBySection.set(section.id, exact.length === 0 && punctuated.length === 1 ? punctuated : exact);
158
+ headingsBySection.set(section.id, exact.length > 0 ? exact : punctuated);
242
159
  if (exact.length > 1 || punctuated.length > 0 && exact.length > 0 || punctuated.length > 1) {
243
160
  const first = exact[1] ?? punctuated[0] ?? exact[0];
244
161
  diagnostics.push(diagnostic(
@@ -248,16 +165,18 @@ function inspectArtifactStructure(
248
165
  first ? lineNumber(content, first.start) : null
249
166
  ));
250
167
  } else if (exact.length === 0 && punctuated.length === 1) {
251
- repairCandidates.push(createHeadingRepair(content, section, punctuated[0]!));
168
+ diagnostics.push(diagnostic(
169
+ 'ARTIFACT_HEADING_TRAILING_PUNCTUATION',
170
+ `visible required H2 '${punctuated[0]!.text}' has trailing punctuation`,
171
+ section.id,
172
+ lineNumber(content, punctuated[0]!.start)
173
+ ));
252
174
  } else if (exact.length === 0) {
253
175
  diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
254
176
  }
255
177
  }
256
178
 
257
179
  const markers = markerEntries(content, scanned);
258
- const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
259
- const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
260
- const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
261
180
  for (const section of schema.sections) {
262
181
  const matches = markers.filter((entry) => entry.marker === section.marker);
263
182
  if (matches.length === 0) {
@@ -281,9 +200,7 @@ function inspectArtifactStructure(
281
200
  if (marker && !heading && marker.standalone) {
282
201
  const nextHeading = scanned.headings.find((candidate) => candidate.start > marker.start && candidate.level <= 2);
283
202
  const body = stripHtmlComments(content.slice(marker.end, nextHeading?.start ?? content.length)).trim();
284
- if (body && repairCandidates.length === 0) {
285
- repairCandidates.push(createSectionInsertionRepair(content, section, marker, locale));
286
- } else if (!body) {
203
+ if (!body) {
287
204
  diagnostics.push(diagnostic('ARTIFACT_EMPTY_SECTION', `section '${section.headings.zh}' has no semantic body`, section.id, marker.line));
288
205
  }
289
206
  }
@@ -294,32 +211,6 @@ function inspectArtifactStructure(
294
211
  }
295
212
  }
296
213
 
297
- for (const candidate of repairCandidates) {
298
- if (candidate.kind === 'replace-line') {
299
- diagnostics.push(diagnostic(
300
- 'ARTIFACT_HEADING_TRAILING_PUNCTUATION',
301
- `visible required H2 '${candidate.from}' may be normalized to '${candidate.to}'`,
302
- candidate.sectionId,
303
- candidate.line,
304
- true,
305
- candidate
306
- ));
307
- continue;
308
- }
309
- const missing = diagnostics.findIndex((item) => item.code === 'ARTIFACT_MISSING_SECTION' && item.sectionId === candidate.sectionId);
310
- if (missing >= 0) {
311
- const section = schema.sections.find((item) => item.id === candidate.sectionId)!;
312
- diagnostics.splice(missing, 1, diagnostic(
313
- 'ARTIFACT_MISSING_SECTION',
314
- `required section '${section.headings.zh}' is missing and can be inserted at its unique marker`,
315
- candidate.sectionId,
316
- candidate.line,
317
- true,
318
- candidate
319
- ));
320
- }
321
- }
322
-
323
214
  const orderedHeadings = schema.sections.flatMap((section) => {
324
215
  const heading = headingsBySection.get(section.id)?.[0];
325
216
  return heading ? [{ section, start: heading.start }] : [];
@@ -335,16 +226,11 @@ function inspectArtifactStructure(
335
226
  diagnostics.push(diagnostic('ARTIFACT_SECTION_ORDER_INVALID', 'section markers are not in schema order', null, null));
336
227
  }
337
228
 
338
- const repair = repairCandidates.length === 1 && (
339
- diagnostics.length === 0 ||
340
- (diagnostics.length === 1 && diagnostics[0]?.operation?.kind === repairCandidates[0]?.kind)
341
- ) ? repairCandidates[0]! : null;
342
229
  return {
343
230
  ok: diagnostics.length === 0,
344
231
  family: schema.family,
345
- semanticDigest: canonicalSemanticDigest(content, repair),
346
- diagnostics,
347
- repair
232
+ semanticDigest: canonicalSemanticDigest(content),
233
+ diagnostics
348
234
  };
349
235
  }
350
236
 
@@ -364,8 +250,7 @@ function inspectArtifactPatterns(
364
250
  ok: diagnostics.length === 0,
365
251
  family: schema.family,
366
252
  semanticDigest: canonicalSemanticDigest(content),
367
- diagnostics,
368
- repair: null
253
+ diagnostics
369
254
  };
370
255
  }
371
256
 
@@ -376,18 +261,16 @@ function inspectArtifactContract(
376
261
  const structure = inspectArtifactStructure(content, schema);
377
262
  const patterns = inspectArtifactPatterns(content, schema);
378
263
  const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
379
- const repair = patterns.diagnostics.length === 0 ? structure.repair : null;
380
264
  return {
381
265
  ok: diagnostics.length === 0,
382
266
  family: schema.family,
383
- semanticDigest: canonicalSemanticDigest(content, repair),
384
- diagnostics,
385
- repair
267
+ semanticDigest: canonicalSemanticDigest(content),
268
+ diagnostics
386
269
  };
387
270
  }
388
271
 
389
272
  function resultFailure(code: string, message: string): ArtifactFileResult {
390
- return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null, operation: null, error: { code, message } };
273
+ return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null, error: { code, message } };
391
274
  }
392
275
 
393
276
  function resultNoOp(content: string): ArtifactFileResult {
@@ -396,124 +279,15 @@ function resultNoOp(content: string): ArtifactFileResult {
396
279
  changed: false,
397
280
  artifactSha256: sha256Content(content),
398
281
  semanticDigest: canonicalSemanticDigest(content),
399
- operation: null,
400
282
  error: null
401
283
  };
402
284
  }
403
285
 
404
- function validateRepairContext(request: ArtifactRepairRequest, content: string): { ok: true } | { ok: false; code: string; message: string } {
405
- const round = artifactRound(request.family, request.artifact);
406
- if (round === null) return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact round is not canonical' };
407
- const contextPattern = /^<!--\s*artifact-context:([^:\s]+):([^:\s]+):(\d+)\s*-->\s*$/;
408
- const contexts = scanVisibleMarkdown(content).lines
409
- .map((line) => line.text.trim().match(contextPattern))
410
- .filter((match): match is RegExpMatchArray => match !== null);
411
- if (contexts.length !== 1
412
- || contexts[0]![1] !== request.taskId
413
- || contexts[0]![2] !== request.family
414
- || Number(contexts[0]![3]) !== round) {
415
- return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
416
- }
417
- let taskContent: string;
418
- const resolved = resolveTaskRef(request.taskId, { repoRoot: request.repoRoot });
419
- if (!resolved.ok) return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: resolved.message };
420
- try { taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
421
- catch (error) { return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` }; }
422
- if (!hasOpenArtifactRound(taskContent, request.family, round)) {
423
- return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
424
- }
425
- let intent;
426
- try { intent = readArtifactRepairIntent(request.repoRoot, request.taskId, request.family, request.artifact); }
427
- catch (error) { return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: String(error) }; }
428
- if (!intent || intent.state !== 'awaiting-repair') {
429
- return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'artifact has no awaiting-repair finalization provenance' };
430
- }
431
- if (intent.artifactSha256 !== request.expectedSha256 || intent.semanticDigest !== request.expectedSemanticDigest) {
432
- return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'repair request does not match the current finalization provenance' };
433
- }
434
- return { ok: true };
435
- }
436
-
437
286
  function artifactRound(family: ArtifactSchemaFamily, artifact: string): number | null {
438
287
  const identity = parseArtifactName(artifact);
439
288
  return identity?.family === family ? identity.round : null;
440
289
  }
441
290
 
442
- function validateTarget(taskDir: string, family: ArtifactSchemaFamily, artifact: string): { path: string } | ArtifactFileResult {
443
- if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md')) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be a canonical top-level Markdown file');
444
- if (!/^TASK-\d{8}-\d{6}$/.test(path.basename(path.resolve(taskDir)))) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'target directory is not a task directory');
445
- const target = path.join(taskDir, artifact);
446
- if (path.dirname(path.resolve(target)) !== path.resolve(taskDir)) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be inside the current task directory');
447
- let stat: fs.Stats;
448
- try { stat = fs.lstatSync(target); }
449
- catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact cannot be read: ${String(error)}`); }
450
- if (stat.isSymbolicLink() || !stat.isFile()) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact is not a regular file');
451
- try { fs.accessSync(target, fs.constants.R_OK | fs.constants.W_OK); }
452
- catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact is not readable and writable: ${String(error)}`); }
453
- return { path: target };
454
- }
455
-
456
- function applyRepairUnlocked(request: ArtifactRepairRequest): ArtifactFileResult {
457
- const schema = getArtifactSchema(request.family);
458
- if (!schema) return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
459
- const target = validateTarget(request.taskDir, request.family, request.artifact);
460
- if ('status' in target) return target;
461
- let content: string;
462
- try { content = readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'); }
463
- catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error)); }
464
- const actualSha256 = sha256Content(content);
465
- if (actualSha256 !== request.expectedSha256) return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact SHA-256 does not match the expected repair baseline');
466
- const context = validateRepairContext(request, content);
467
- if (!context.ok) return resultFailure(context.code, context.message);
468
- const inspection = inspectArtifactStructure(content, schema);
469
- if (!inspection.repair || inspection.diagnostics.length !== 1) {
470
- return resultFailure('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available');
471
- }
472
- if (inspection.repair.kind !== request.operation.kind
473
- || inspection.repair.sectionId !== request.operation.sectionId
474
- || inspection.repair.from !== request.operation.from
475
- || inspection.repair.to !== request.operation.to
476
- || inspection.repair.start !== request.operation.start
477
- || inspection.repair.end !== request.operation.end) {
478
- return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact structure');
479
- }
480
- if (inspection.semanticDigest !== request.expectedSemanticDigest) return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact semantic digest does not match the expected repair baseline');
481
- const transformed = applyRepairOperation(content, inspection.repair);
482
- if (transformed === null) return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact bytes');
483
- if (transformed === content) return resultFailure('ARTIFACT_REPAIR_NO_PROGRESS', 'repair operation produced no byte change');
484
- const tempPath = path.join(request.taskDir, `.${request.artifact}.repair-${process.pid}-${Date.now()}.tmp`);
485
- let mode = 0o600;
486
- try {
487
- mode = fs.statSync(target.path).mode & 0o777;
488
- fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
489
- const currentStat = fs.lstatSync(target.path);
490
- if (currentStat.isSymbolicLink() || !currentStat.isFile() || readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).sha256 !== actualSha256) {
491
- fs.unlinkSync(tempPath);
492
- return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
493
- }
494
- fs.renameSync(tempPath, target.path);
495
- } catch (error) {
496
- try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
497
- return resultFailure('ARTIFACT_REPAIR_WRITE_FAILED', String(error));
498
- }
499
- return {
500
- status: 'applied',
501
- changed: true,
502
- artifactSha256: sha256Content(transformed),
503
- semanticDigest: canonicalSemanticDigest(transformed),
504
- operation: inspection.repair,
505
- error: null
506
- };
507
- }
508
-
509
- function applyArtifactRepair(request: ArtifactRepairRequest): ArtifactFileResult {
510
- try {
511
- return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.repair', () => applyRepairUnlocked(request));
512
- } catch (error) {
513
- return resultFailure('ARTIFACT_REPAIR_LOCK_FAILED', String(error));
514
- }
515
- }
516
-
517
291
  function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileResult {
518
292
  const schema = getArtifactSchema(request.family);
519
293
  if (!schema) return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
@@ -551,7 +325,6 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
551
325
  changed: true,
552
326
  artifactSha256: sha256Content(content),
553
327
  semanticDigest: canonicalSemanticDigest(content),
554
- operation: null,
555
328
  error: null
556
329
  };
557
330
  });
@@ -561,7 +334,6 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
561
334
  }
562
335
 
563
336
  export {
564
- applyArtifactRepair,
565
337
  canonicalSemanticDigest,
566
338
  initializeArtifactSkeleton,
567
339
  inspectArtifactContract,
@@ -572,8 +344,6 @@ export {
572
344
  export type {
573
345
  ArtifactFileResult,
574
346
  ArtifactInitRequest,
575
- ArtifactRepairOperation,
576
- ArtifactRepairRequest,
577
347
  ArtifactStructuralDiagnostic,
578
348
  ArtifactStructuralDiagnosticCode,
579
349
  ArtifactStructureResult