@fitlab-ai/agent-infra 0.9.13 → 0.9.14

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 (280) hide show
  1. package/bin/cli.ts +26 -9
  2. package/dist/bin/cli.js +173 -157
  3. package/dist/bin/internal-cli.js +42 -21
  4. package/dist/lib/agent-client.js +5 -0
  5. package/dist/lib/internal/agent-client.js +5 -2
  6. package/dist/lib/internal/cli-route-inventory.js +152 -0
  7. package/dist/lib/internal/codex-lifecycle.js +9 -6
  8. package/dist/lib/internal/codex-sandbox-controller.js +8 -2
  9. package/dist/lib/internal/git-workflow.js +10 -6
  10. package/dist/lib/internal/platform-checks.js +16 -4
  11. package/dist/lib/internal/platform-comment.js +16 -4
  12. package/dist/lib/internal/platform-context.js +4 -1
  13. package/dist/lib/internal/platform-issue.js +12 -4
  14. package/dist/lib/internal/platform-metadata.js +88 -0
  15. package/dist/lib/internal/platform-pr-review.js +16 -3
  16. package/dist/lib/internal/platform-pr.js +24 -10
  17. package/dist/lib/internal/platform-release-notes.js +6 -3
  18. package/dist/lib/internal/platform-security.js +93 -0
  19. package/dist/lib/internal/pr-review-grade.js +14 -3
  20. package/dist/lib/internal/release-workflow.js +19 -5
  21. package/dist/lib/internal/sandbox-control.js +22 -5
  22. package/dist/lib/internal/task-activity.js +13 -4
  23. package/dist/lib/internal/task-artifact.js +186 -27
  24. package/dist/lib/internal/task-context.js +4 -1
  25. package/dist/lib/internal/task-create.js +48 -15
  26. package/dist/lib/internal/task-delivery.js +4 -1
  27. package/dist/lib/internal/task-event.js +4 -1
  28. package/dist/lib/internal/task-finalization.js +4 -1
  29. package/dist/lib/internal/task-invalidation.js +4 -1
  30. package/dist/lib/internal/task-ledger.js +12 -1
  31. package/dist/lib/internal/task-lifecycle.js +4 -1
  32. package/dist/lib/internal/task-operation-registry.js +421 -0
  33. package/dist/lib/internal/task-orchestration.js +12 -0
  34. package/dist/lib/internal/task-override.js +10 -3
  35. package/dist/lib/internal/task-qualification.js +90 -0
  36. package/dist/lib/internal/task-review.js +4 -1
  37. package/dist/lib/internal/task-short-id.js +11 -1
  38. package/dist/lib/internal/task-snapshot.js +4 -1
  39. package/dist/lib/internal/task-validate.js +7 -2
  40. package/dist/lib/internal/task-verify.js +5 -1
  41. package/dist/lib/internal/task-warning.js +8 -1
  42. package/dist/lib/platform/comment-safety.js +226 -0
  43. package/dist/lib/platform/github-provider.js +233 -6
  44. package/dist/lib/platform/issue-comments.js +146 -34
  45. package/dist/lib/platform/pr-change-report.js +6 -12
  46. package/dist/lib/platform/pr-summary.js +28 -7
  47. package/dist/lib/platform/provider-contract.d.ts +67 -1
  48. package/dist/lib/platform/provider-contract.js +6 -2
  49. package/dist/lib/platform/provider-validation.js +76 -3
  50. package/dist/lib/platform/pull-requests.js +115 -170
  51. package/dist/lib/platform/repository-metadata.js +377 -0
  52. package/dist/lib/platform/security-alerts.js +155 -0
  53. package/dist/lib/platform/verification-sync.js +33 -26
  54. package/dist/lib/process-data/index.js +7 -0
  55. package/dist/lib/run/index.js +5 -0
  56. package/dist/lib/sandbox/commands/control-status.js +22 -0
  57. package/dist/lib/sandbox/commands/create.js +32 -6
  58. package/dist/lib/sandbox/commands/ls.js +5 -0
  59. package/dist/lib/sandbox/commands/rm.js +827 -120
  60. package/dist/lib/sandbox/commands/show.js +16 -0
  61. package/dist/lib/sandbox/control/client.js +83 -9
  62. package/dist/lib/sandbox/control/completed-reentry.js +85 -0
  63. package/dist/lib/sandbox/control/container-identity.js +28 -31
  64. package/dist/lib/sandbox/control/executor.js +149 -14
  65. package/dist/lib/sandbox/control/lifecycle.js +621 -127
  66. package/dist/lib/sandbox/control/native-file-lock.js +177 -0
  67. package/dist/lib/sandbox/control/server.js +305 -35
  68. package/dist/lib/sandbox/control/state.js +5 -3
  69. package/dist/lib/sandbox/control/task-view.js +183 -0
  70. package/dist/lib/sandbox/engines/authority.js +289 -0
  71. package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
  72. package/dist/lib/sandbox/index.js +5 -0
  73. package/dist/lib/sandbox/recovery.js +21 -1
  74. package/dist/lib/sandbox/workspace-view.js +2 -1
  75. package/dist/lib/sandbox/worktree-safety.js +4 -1
  76. package/dist/lib/server/index.js +8 -0
  77. package/dist/lib/task/artifact-lifecycle.js +42 -13
  78. package/dist/lib/task/artifact-operations.js +520 -0
  79. package/dist/lib/task/artifact-repair-intent.js +59 -0
  80. package/dist/lib/task/artifact-schema.js +135 -0
  81. package/dist/lib/task/capabilities.js +72 -1
  82. package/dist/lib/task/control-authority.js +4 -0
  83. package/dist/lib/task/create-service.js +103 -4
  84. package/dist/lib/task/create.js +25 -0
  85. package/dist/lib/task/events.js +186 -67
  86. package/dist/lib/task/index.js +11 -3
  87. package/dist/lib/task/local-artifact-finalization.js +75 -150
  88. package/dist/lib/task/orchestration.js +192 -52
  89. package/dist/lib/task/qualification-audit.js +490 -0
  90. package/dist/lib/task/qualification-confirmation.js +109 -0
  91. package/dist/lib/task/qualification-intents.js +148 -0
  92. package/dist/lib/task/review-artifacts.js +22 -23
  93. package/dist/lib/task/review-finalization.js +121 -4
  94. package/dist/lib/task/sections.js +6 -4
  95. package/dist/lib/task/verification-config.js +11 -0
  96. package/dist/lib/task/verification-engine.js +54 -4
  97. package/dist/lifecycle-build-manifest.json +3 -3
  98. package/lib/agent-client.ts +9 -0
  99. package/lib/internal/agent-client.ts +4 -2
  100. package/lib/internal/cli-route-inventory.ts +140 -0
  101. package/lib/internal/codex-lifecycle.ts +8 -6
  102. package/lib/internal/codex-sandbox-controller.ts +7 -2
  103. package/lib/internal/git-workflow.ts +9 -6
  104. package/lib/internal/platform-checks.ts +12 -4
  105. package/lib/internal/platform-comment.ts +12 -4
  106. package/lib/internal/platform-context.ts +3 -1
  107. package/lib/internal/platform-issue.ts +11 -4
  108. package/lib/internal/platform-metadata.ts +70 -0
  109. package/lib/internal/platform-pr-review.ts +14 -3
  110. package/lib/internal/platform-pr.ts +23 -10
  111. package/lib/internal/platform-release-notes.ts +5 -3
  112. package/lib/internal/platform-security.ts +83 -0
  113. package/lib/internal/pr-review-grade.ts +10 -3
  114. package/lib/internal/release-workflow.ts +17 -5
  115. package/lib/internal/sandbox-control.ts +19 -5
  116. package/lib/internal/task-activity.ts +12 -4
  117. package/lib/internal/task-artifact.ts +164 -25
  118. package/lib/internal/task-context.ts +3 -1
  119. package/lib/internal/task-create.ts +76 -15
  120. package/lib/internal/task-delivery.ts +3 -1
  121. package/lib/internal/task-event.ts +3 -1
  122. package/lib/internal/task-finalization.ts +3 -1
  123. package/lib/internal/task-invalidation.ts +3 -1
  124. package/lib/internal/task-ledger.ts +11 -1
  125. package/lib/internal/task-lifecycle.ts +3 -1
  126. package/lib/internal/task-operation-registry.ts +482 -0
  127. package/lib/internal/task-orchestration.ts +12 -0
  128. package/lib/internal/task-override.ts +9 -3
  129. package/lib/internal/task-qualification.ts +61 -0
  130. package/lib/internal/task-review.ts +3 -1
  131. package/lib/internal/task-short-id.ts +10 -1
  132. package/lib/internal/task-snapshot.ts +3 -1
  133. package/lib/internal/task-validate.ts +6 -2
  134. package/lib/internal/task-verify.ts +4 -1
  135. package/lib/internal/task-warning.ts +7 -1
  136. package/lib/platform/comment-safety.ts +250 -0
  137. package/lib/platform/github-provider.ts +229 -7
  138. package/lib/platform/issue-comments.ts +160 -35
  139. package/lib/platform/pr-change-report.ts +6 -12
  140. package/lib/platform/pr-summary.ts +27 -6
  141. package/lib/platform/provider-contract.ts +78 -3
  142. package/lib/platform/provider-validation.ts +85 -3
  143. package/lib/platform/pull-requests.ts +126 -178
  144. package/lib/platform/repository-metadata.ts +453 -0
  145. package/lib/platform/security-alerts.ts +191 -0
  146. package/lib/platform/verification-sync.ts +32 -28
  147. package/lib/process-data/index.ts +7 -0
  148. package/lib/run/index.ts +5 -0
  149. package/lib/sandbox/commands/control-status.ts +26 -0
  150. package/lib/sandbox/commands/create.ts +46 -6
  151. package/lib/sandbox/commands/ls.ts +7 -0
  152. package/lib/sandbox/commands/rm.ts +1000 -87
  153. package/lib/sandbox/commands/show.ts +16 -0
  154. package/lib/sandbox/control/client.ts +103 -9
  155. package/lib/sandbox/control/completed-reentry.ts +98 -0
  156. package/lib/sandbox/control/container-identity.ts +27 -43
  157. package/lib/sandbox/control/executor.ts +177 -12
  158. package/lib/sandbox/control/lifecycle.ts +629 -17
  159. package/lib/sandbox/control/native-file-lock.ts +209 -0
  160. package/lib/sandbox/control/protocol.ts +5 -2
  161. package/lib/sandbox/control/server.ts +319 -37
  162. package/lib/sandbox/control/state.ts +6 -3
  163. package/lib/sandbox/control/task-view.ts +240 -0
  164. package/lib/sandbox/engines/authority.ts +365 -0
  165. package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
  166. package/lib/sandbox/index.ts +7 -0
  167. package/lib/sandbox/recovery.ts +21 -1
  168. package/lib/sandbox/workspace-view.ts +9 -3
  169. package/lib/sandbox/worktree-safety.ts +4 -1
  170. package/lib/server/index.ts +10 -0
  171. package/lib/task/artifact-lifecycle.ts +38 -16
  172. package/lib/task/artifact-operations.ts +626 -0
  173. package/lib/task/artifact-repair-intent.ts +71 -0
  174. package/lib/task/artifact-schema.ts +192 -0
  175. package/lib/task/capabilities.ts +70 -1
  176. package/lib/task/control-authority.ts +11 -1
  177. package/lib/task/create-service.ts +131 -6
  178. package/lib/task/create.ts +27 -0
  179. package/lib/task/events.ts +161 -65
  180. package/lib/task/index.ts +16 -3
  181. package/lib/task/local-artifact-finalization.ts +87 -218
  182. package/lib/task/orchestration.ts +172 -49
  183. package/lib/task/qualification-audit.ts +599 -0
  184. package/lib/task/qualification-confirmation.ts +117 -0
  185. package/lib/task/qualification-intents.ts +150 -0
  186. package/lib/task/review-artifacts.ts +21 -22
  187. package/lib/task/review-finalization.ts +133 -4
  188. package/lib/task/sections.ts +6 -4
  189. package/lib/task/verification-config.ts +12 -0
  190. package/lib/task/verification-engine.ts +55 -4
  191. package/package.json +6 -1
  192. package/templates/.agents/rules/README.en.md +2 -0
  193. package/templates/.agents/rules/README.zh-CN.md +2 -0
  194. package/templates/.agents/rules/decision-qualification.en.md +22 -0
  195. package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
  196. package/templates/.agents/rules/evidence-reporting.en.md +28 -0
  197. package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
  198. package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
  199. package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
  200. package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
  201. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
  202. package/templates/.agents/rules/security-alerts.en.md +8 -8
  203. package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
  204. package/templates/.agents/skills/analyze-task/SKILL.en.md +15 -2
  205. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
  206. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
  207. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
  208. package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
  209. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
  210. package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
  211. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
  212. package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
  213. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
  214. package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
  215. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
  216. package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
  217. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
  218. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  219. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  220. package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
  221. package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
  222. package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
  223. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
  224. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -1
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -1
  226. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  227. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  228. package/templates/.agents/skills/complete-task/SKILL.en.md +9 -22
  229. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +9 -22
  230. package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
  231. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
  232. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  233. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  234. package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
  235. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  236. package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
  237. package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
  238. package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
  239. package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
  240. package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
  241. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
  242. package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
  243. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
  244. package/templates/.agents/skills/review-analysis/SKILL.en.md +14 -1
  245. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
  246. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
  247. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
  248. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
  249. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
  250. package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
  251. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
  252. package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
  253. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
  254. package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
  255. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
  256. package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
  257. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
  258. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
  259. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
  260. package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
  261. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
  262. package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
  263. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
  264. package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
  265. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
  266. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -1
  267. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -1
  268. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
  269. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
  270. package/templates/.agents/skills/test/SKILL.en.md +13 -2
  271. package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
  272. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
  273. package/templates/.agents/templates/task.en.md +6 -0
  274. package/templates/.agents/templates/task.zh-CN.md +6 -0
  275. package/templates/.agents/scripts/security-alerts.github.sh +0 -151
  276. package/templates/.agents/scripts/security-alerts.sh +0 -31
  277. package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
  278. package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
  279. package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
  280. package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
@@ -1,5 +1,7 @@
1
1
  import { runDaemon } from './daemon.js';
2
2
  import { start, stop, status, logs } from './process-control.js';
3
+ import { PUBLIC_CLI_ROUTE_SELECTORS } from '../internal/cli-route-inventory.js';
4
+ const SERVER_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS.server;
3
5
  const USAGE = `Usage: ai server <command> [options]
4
6
 
5
7
  Commands:
@@ -21,6 +23,12 @@ export async function runServer(args) {
21
23
  process.stdout.write(`${USAGE}\n`);
22
24
  return;
23
25
  }
26
+ if (!SERVER_OPERATIONS.includes(subcommand)) {
27
+ process.stderr.write(`Unknown server command: ${subcommand}\n`);
28
+ process.stdout.write(`${USAGE}\n`);
29
+ process.exitCode = 1;
30
+ return;
31
+ }
24
32
  switch (subcommand) {
25
33
  case 'start':
26
34
  await start({ foreground: rest.includes('--foreground') });
@@ -7,17 +7,9 @@ import { parseVerdict } from './review-artifacts.js';
7
7
  import { extractSection, findSectionHeading } from './sections.js';
8
8
  import { receiptForOutput, sha256File } from './artifact-receipts.js';
9
9
  import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
10
- const artifactFamilyCatalog = [
11
- { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
12
- { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
13
- { family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
14
- { family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
15
- { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
16
- { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
17
- { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
18
- { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
19
- { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
20
- ];
10
+ import { validateQualificationAudit } from './qualification-audit.js';
11
+ import { ARTIFACT_FAMILY_CATALOG } from './artifact-schema.js';
12
+ const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
21
13
  const FAMILY_SET = new Set(artifactFamilyCatalog.map((item) => item.family));
22
14
  const BLOCKING_DIAGNOSTICS = new Set([
23
15
  'NONCANONICAL_NAME', 'ROUND_OUT_OF_RANGE', 'MISSING_BASE', 'ROUND_GAP',
@@ -286,6 +278,7 @@ const OPTIONAL_CONTEXT = {
286
278
  'manual-validation': { family: 'review-code' },
287
279
  'validation-run': { family: 'review-code' }
288
280
  };
281
+ const QUALIFICATION_RECOVERY_OPTIONAL_CONTEXT_CONSUMERS = new Set(['analysis', 'plan']);
289
282
  function resolveArtifactContext(taskRef, family, options = {}) {
290
283
  const inventory = inspectTaskArtifacts(taskRef, family, options);
291
284
  if (inventory.status === 'failed')
@@ -302,6 +295,9 @@ function resolveArtifactContext(taskRef, family, options = {}) {
302
295
  if (input.status === 'failed' || !input.latest) {
303
296
  return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
304
297
  }
298
+ const qualificationError = qualificationErrorForArtifact(input.latest);
299
+ if (qualificationError)
300
+ return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
305
301
  inputs.push(input.latest);
306
302
  }
307
303
  const optional = OPTIONAL_CONTEXT[inventory.family];
@@ -311,11 +307,26 @@ function resolveArtifactContext(taskRef, family, options = {}) {
311
307
  if (optional.requireReference && !context.reviewedInput) {
312
308
  return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: `${context.latest.name} has no valid reviewed input` } };
313
309
  }
310
+ const qualificationError = qualificationErrorForArtifact(context.latest);
311
+ if (qualificationError && !QUALIFICATION_RECOVERY_OPTIONAL_CONTEXT_CONSUMERS.has(inventory.family)) {
312
+ return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
313
+ }
314
314
  inputs.push(context.latest);
315
315
  }
316
316
  }
317
317
  return { ...inventory, inputs, codeMode: null };
318
318
  }
319
+ function qualificationErrorForArtifact(artifact) {
320
+ try {
321
+ const taskContent = fs.readFileSync(path.join(path.dirname(artifact.path), 'task.md'), 'utf8');
322
+ const artifactContent = fs.readFileSync(artifact.path, 'utf8');
323
+ const result = validateQualificationAudit(taskContent, artifactContent);
324
+ return result.ok ? null : `${result.code}: ${result.message}`;
325
+ }
326
+ catch (error) {
327
+ return error instanceof Error ? error.message : String(error);
328
+ }
329
+ }
319
330
  function resolveCodeContext(inventory, options) {
320
331
  const taskRef = inventory.requestRef;
321
332
  const plan = inspectTaskArtifacts(taskRef, 'plan', options);
@@ -327,6 +338,19 @@ function resolveCodeContext(inventory, options) {
327
338
  const codeMax = latestCode?.round ?? 0;
328
339
  const reviewMax = reviewCode.latest?.round ?? 0;
329
340
  const inputs = [plan.latest];
341
+ const planQualificationError = qualificationErrorForArtifact(plan.latest);
342
+ if (planQualificationError)
343
+ return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', planQualificationError);
344
+ if (latestCode) {
345
+ const qualificationError = qualificationErrorForArtifact(latestCode);
346
+ if (qualificationError)
347
+ return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, latestCode.name);
348
+ }
349
+ if (reviewCode.latest) {
350
+ const qualificationError = qualificationErrorForArtifact(reviewCode.latest);
351
+ if (qualificationError)
352
+ return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, reviewCode.latest.name);
353
+ }
330
354
  if (!latestCode) {
331
355
  if (!reviewPlan.latest || reviewPlan.reviewedInput?.name !== plan.latest.name) {
332
356
  return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest plan '${plan.latest.name}' requires a matching approved review-plan`);
@@ -346,6 +370,11 @@ function resolveCodeContext(inventory, options) {
346
370
  if (reviewPlan.latest && !reviewPlan.reviewedInput) {
347
371
  return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `${reviewPlan.latest.name} has no valid reviewed input`, reviewPlan.latest.name);
348
372
  }
373
+ if (reviewPlan.latest) {
374
+ const qualificationError = qualificationErrorForArtifact(reviewPlan.latest);
375
+ if (qualificationError)
376
+ return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, reviewPlan.latest.name);
377
+ }
349
378
  if (reviewPlan.latest && reviewPlan.reviewedInput?.name === plan.latest.name) {
350
379
  const verdict = parseVerdict(reviewPlan.latest.path);
351
380
  if (!verdict.ok)
@@ -355,8 +384,8 @@ function resolveCodeContext(inventory, options) {
355
384
  return withCodeMode(inventory, inputs, 'ready', 'init', codeMax, reviewMax, verdict.verdict, reviewPlan.latest.name, `Latest ${reviewPlan.latest.name} approves plan content not captured by the latest code input receipt. Entering replan-driven init.`);
356
385
  }
357
386
  }
358
- if (reviewMax < codeMax) {
359
- const expected = artifactName('review-code', codeMax);
387
+ if (!reviewCode.latest || reviewCode.reviewedInput?.name !== latestCode.name) {
388
+ const expected = reviewCode.next?.name ?? artifactName('review-code', reviewMax + 1);
360
389
  return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `${expected} is required before another code round`, expected);
361
390
  }
362
391
  const review = reviewCode.latest;
@@ -0,0 +1,520 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { scanVisibleMarkdown } from './decision-details.js';
5
+ import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
6
+ import { readArtifactRepairIntent } from './artifact-repair-intent.js';
7
+ import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
8
+ import { withTaskExecutionLock } from './task-execution-lock.js';
9
+ function sha256Content(content) {
10
+ return createHash('sha256').update(content, 'utf8').digest('hex');
11
+ }
12
+ function sourceLines(content) {
13
+ const lines = [];
14
+ let start = 0;
15
+ while (start < content.length) {
16
+ const newline = content.indexOf('\n', start);
17
+ const end = newline === -1 ? content.length : newline;
18
+ lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
19
+ start = newline === -1 ? content.length : newline + 1;
20
+ }
21
+ if (content.length === 0)
22
+ lines.push({ start: 0, end: 0, text: '' });
23
+ return lines;
24
+ }
25
+ function hasUnclosedFence(content) {
26
+ let fence = null;
27
+ for (const line of sourceLines(content)) {
28
+ if (fence) {
29
+ const close = line.text.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
30
+ if (close && close[1][0] === fence.character && close[1].length >= fence.length)
31
+ fence = null;
32
+ continue;
33
+ }
34
+ const open = line.text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
35
+ if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
36
+ fence = { character: open[1][0], length: open[1].length };
37
+ }
38
+ }
39
+ return fence !== null;
40
+ }
41
+ function lineNumber(content, offset) {
42
+ return content.slice(0, offset).split('\n').length;
43
+ }
44
+ function markerPattern(marker) {
45
+ return new RegExp(`<!--\\s*${escapeRegExp(marker)}\\s*-->`);
46
+ }
47
+ function escapeRegExp(value) {
48
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
+ }
50
+ function markerEntries(content) {
51
+ const entries = [];
52
+ for (const line of scanVisibleMarkdown(content).lines) {
53
+ const match = line.text.match(/<!--\s*(artifact-section:[^\s>]+)\s*-->/g);
54
+ if (!match)
55
+ continue;
56
+ for (const raw of match) {
57
+ const marker = raw.match(/artifact-section:[^\s>]+/)[0];
58
+ const offset = line.text.indexOf(raw);
59
+ entries.push({
60
+ marker,
61
+ start: line.start + offset,
62
+ end: line.start + offset + raw.length,
63
+ line: lineNumber(content, line.start),
64
+ standalone: line.text.trim() === raw
65
+ });
66
+ }
67
+ }
68
+ return entries;
69
+ }
70
+ function sectionBodyBounds(content, heading) {
71
+ const next = scanVisibleMarkdown(content).headings.find((candidate) => (candidate.start > heading.start && candidate.level <= 2));
72
+ const headingEnd = scanVisibleMarkdown(content).headings.find((candidate) => candidate.start === heading.start)?.end ?? heading.start;
73
+ return { start: headingEnd, end: next?.start ?? content.length };
74
+ }
75
+ function stripHtmlComments(content) {
76
+ let result = '';
77
+ let cursor = 0;
78
+ while (cursor < content.length) {
79
+ const start = content.indexOf('<!--', cursor);
80
+ if (start < 0)
81
+ return result + content.slice(cursor);
82
+ result += content.slice(cursor, start);
83
+ const end = content.indexOf('-->', start + 4);
84
+ if (end < 0)
85
+ return result;
86
+ cursor = end + 3;
87
+ }
88
+ return result;
89
+ }
90
+ function diagnostic(code, message, sectionId, line, repairable = false, operation) {
91
+ return {
92
+ code, message, sectionId, line, repairable,
93
+ ...(operation ? { operation } : {})
94
+ };
95
+ }
96
+ function sectionHeadings(schema, section) {
97
+ return [section.headings.zh, section.headings.en];
98
+ }
99
+ function createHeadingRepair(content, section, heading) {
100
+ const to = heading.text.slice(0, -1);
101
+ return {
102
+ kind: 'replace-line',
103
+ sectionId: section.id,
104
+ line: lineNumber(content, heading.start),
105
+ from: heading.text,
106
+ to,
107
+ start: heading.start,
108
+ end: heading.end
109
+ };
110
+ }
111
+ function createSectionInsertionRepair(content, section, marker, locale) {
112
+ return {
113
+ kind: 'insert-section',
114
+ sectionId: section.id,
115
+ line: marker.line,
116
+ from: '',
117
+ to: `## ${section.headings[locale === 'en' ? 'en' : 'zh']}\n`,
118
+ start: marker.start,
119
+ end: marker.start
120
+ };
121
+ }
122
+ function applyRepairOperation(content, operation) {
123
+ if (operation.start < 0 || operation.end < operation.start || operation.end > content.length)
124
+ return null;
125
+ if (operation.kind === 'insert-section') {
126
+ if (operation.from !== '' || operation.start !== operation.end || !operation.to)
127
+ return null;
128
+ return `${content.slice(0, operation.start)}${operation.to}${content.slice(operation.end)}`;
129
+ }
130
+ const raw = content.slice(operation.start, operation.end);
131
+ const offset = raw.indexOf(operation.from);
132
+ if (!operation.from || offset < 0 || raw.indexOf(operation.from, offset + operation.from.length) >= 0)
133
+ return null;
134
+ return `${content.slice(0, operation.start)}${raw.slice(0, offset)}${operation.to}${raw.slice(offset + operation.from.length)}${content.slice(operation.end)}`;
135
+ }
136
+ function canonicalContent(content, operation = null) {
137
+ let normalized = content;
138
+ if (operation) {
139
+ normalized = applyRepairOperation(normalized, operation) ?? normalized;
140
+ }
141
+ normalized = normalized.replace(/\r\n/g, '\n');
142
+ normalized = normalized.replace(/^<!--\s*artifact-context:[^\n]+-->\s*\n?/gm, '');
143
+ normalized = normalized.replace(/^\s*<!--\s*artifact-section:[^\n]+-->\s*\n?/gm, '');
144
+ normalized = normalized.replace(/^\s*<!--\s*artifact-slot:empty\s*-->\s*\n?/gm, '');
145
+ return normalized;
146
+ }
147
+ function canonicalSemanticDigest(content, operation = null) {
148
+ return sha256Content(canonicalContent(content, operation));
149
+ }
150
+ function inspectArtifactStructure(content, schema) {
151
+ const diagnostics = [];
152
+ const scanned = scanVisibleMarkdown(content);
153
+ const repairCandidates = [];
154
+ if (!content.trim())
155
+ diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
156
+ if (hasUnclosedFence(content))
157
+ diagnostics.push(diagnostic('ARTIFACT_UNCLOSED_FENCE', 'artifact contains an unclosed Markdown fence', null, null));
158
+ const headingsBySection = new Map();
159
+ for (const section of schema.sections) {
160
+ const aliases = sectionHeadings(schema, section);
161
+ const exact = scanned.headings.filter((heading) => heading.level === 2 && aliases.includes(heading.text));
162
+ const punctuated = scanned.headings.filter((heading) => heading.level === 2 && aliases.some((alias) => heading.text === `${alias}:` || heading.text === `${alias}:`));
163
+ headingsBySection.set(section.id, exact.length === 0 && punctuated.length === 1 ? punctuated : exact);
164
+ if (exact.length > 1 || punctuated.length > 0 && exact.length > 0 || punctuated.length > 1) {
165
+ const first = exact[1] ?? punctuated[0] ?? exact[0];
166
+ diagnostics.push(diagnostic('ARTIFACT_DUPLICATE_SECTION', `required section '${aliases[0]}' is duplicated or has an ambiguous punctuation variant`, section.id, first ? lineNumber(content, first.start) : null));
167
+ }
168
+ else if (exact.length === 0 && punctuated.length === 1) {
169
+ repairCandidates.push(createHeadingRepair(content, section, punctuated[0]));
170
+ }
171
+ else if (exact.length === 0) {
172
+ diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
173
+ }
174
+ }
175
+ const markers = markerEntries(content);
176
+ const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
177
+ const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
178
+ const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
179
+ for (const section of schema.sections) {
180
+ const matches = markers.filter((entry) => entry.marker === section.marker);
181
+ if (matches.length === 0) {
182
+ diagnostics.push(diagnostic('ARTIFACT_MARKER_MISSING', `section marker '${section.marker}' is missing`, section.id, null));
183
+ }
184
+ else if (matches.length > 1) {
185
+ diagnostics.push(diagnostic('ARTIFACT_MARKER_DUPLICATE', `section marker '${section.marker}' is duplicated`, section.id, matches[1].line));
186
+ }
187
+ const marker = matches[0];
188
+ const heading = headingsBySection.get(section.id)?.[0];
189
+ if (marker && !marker.standalone) {
190
+ diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' must occupy its own visible line`, section.id, marker.line));
191
+ }
192
+ if (marker && heading) {
193
+ const bounds = sectionBodyBounds(content, heading);
194
+ if (marker.start < bounds.start || marker.start >= bounds.end) {
195
+ diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' is outside its section`, section.id, marker.line));
196
+ }
197
+ const body = stripHtmlComments(content.slice(bounds.start, bounds.end)).trim();
198
+ if (!body)
199
+ diagnostics.push(diagnostic('ARTIFACT_EMPTY_SECTION', `section '${section.headings.zh}' has no semantic body`, section.id, lineNumber(content, heading.start)));
200
+ }
201
+ if (marker && !heading && marker.standalone) {
202
+ const nextHeading = scanned.headings.find((candidate) => candidate.start > marker.start && candidate.level <= 2);
203
+ const body = stripHtmlComments(content.slice(marker.end, nextHeading?.start ?? content.length)).trim();
204
+ if (body && repairCandidates.length === 0) {
205
+ repairCandidates.push(createSectionInsertionRepair(content, section, marker, locale));
206
+ }
207
+ else if (!body) {
208
+ diagnostics.push(diagnostic('ARTIFACT_EMPTY_SECTION', `section '${section.headings.zh}' has no semantic body`, section.id, marker.line));
209
+ }
210
+ }
211
+ }
212
+ for (const marker of markers) {
213
+ if (!schema.sections.some((section) => markerPattern(section.marker).test(`<!-- ${marker.marker} -->`))) {
214
+ diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `unknown section marker '${marker.marker}'`, null, marker.line));
215
+ }
216
+ }
217
+ for (const candidate of repairCandidates) {
218
+ if (candidate.kind === 'replace-line') {
219
+ diagnostics.push(diagnostic('ARTIFACT_HEADING_TRAILING_PUNCTUATION', `visible required H2 '${candidate.from}' may be normalized to '${candidate.to}'`, candidate.sectionId, candidate.line, true, candidate));
220
+ continue;
221
+ }
222
+ const missing = diagnostics.findIndex((item) => item.code === 'ARTIFACT_MISSING_SECTION' && item.sectionId === candidate.sectionId);
223
+ if (missing >= 0) {
224
+ const section = schema.sections.find((item) => item.id === candidate.sectionId);
225
+ diagnostics.splice(missing, 1, diagnostic('ARTIFACT_MISSING_SECTION', `required section '${section.headings.zh}' is missing and can be inserted at its unique marker`, candidate.sectionId, candidate.line, true, candidate));
226
+ }
227
+ }
228
+ const orderedHeadings = schema.sections.flatMap((section) => {
229
+ const heading = headingsBySection.get(section.id)?.[0];
230
+ return heading ? [{ section, start: heading.start }] : [];
231
+ });
232
+ if (orderedHeadings.some((item, index) => index > 0 && item.start <= orderedHeadings[index - 1].start)) {
233
+ diagnostics.push(diagnostic('ARTIFACT_SECTION_ORDER_INVALID', 'required sections are not in schema order', null, null));
234
+ }
235
+ const orderedMarkers = schema.sections.flatMap((section) => {
236
+ const marker = markers.find((entry) => entry.marker === section.marker);
237
+ return marker ? [{ section, start: marker.start }] : [];
238
+ });
239
+ if (orderedMarkers.some((item, index) => index > 0 && item.start <= orderedMarkers[index - 1].start)) {
240
+ diagnostics.push(diagnostic('ARTIFACT_SECTION_ORDER_INVALID', 'section markers are not in schema order', null, null));
241
+ }
242
+ const repair = repairCandidates.length === 1 && (diagnostics.length === 0 ||
243
+ (diagnostics.length === 1 && diagnostics[0]?.operation?.kind === repairCandidates[0]?.kind)) ? repairCandidates[0] : null;
244
+ return {
245
+ ok: diagnostics.length === 0,
246
+ family: schema.family,
247
+ semanticDigest: canonicalSemanticDigest(content, repair),
248
+ diagnostics,
249
+ repair
250
+ };
251
+ }
252
+ function inspectArtifactPatterns(content, schema) {
253
+ const diagnostics = schema.requiredPatterns
254
+ .filter((pattern) => !new RegExp(pattern, 'm').test(content))
255
+ .map((pattern) => diagnostic('ARTIFACT_REQUIRED_PATTERN_MISSING', `artifact is missing required pattern '${pattern}'`, null, null));
256
+ return {
257
+ ok: diagnostics.length === 0,
258
+ family: schema.family,
259
+ semanticDigest: canonicalSemanticDigest(content),
260
+ diagnostics,
261
+ repair: null
262
+ };
263
+ }
264
+ function inspectArtifactContract(content, schema) {
265
+ const structure = inspectArtifactStructure(content, schema);
266
+ const patterns = inspectArtifactPatterns(content, schema);
267
+ const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
268
+ const repair = patterns.diagnostics.length === 0 ? structure.repair : null;
269
+ return {
270
+ ok: diagnostics.length === 0,
271
+ family: schema.family,
272
+ semanticDigest: canonicalSemanticDigest(content, repair),
273
+ diagnostics,
274
+ repair
275
+ };
276
+ }
277
+ function resultFailure(code, message) {
278
+ return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null, operation: null, error: { code, message } };
279
+ }
280
+ function resultNoOp(content) {
281
+ return {
282
+ status: 'no-op',
283
+ changed: false,
284
+ artifactSha256: sha256Content(content),
285
+ semanticDigest: canonicalSemanticDigest(content),
286
+ operation: null,
287
+ error: null
288
+ };
289
+ }
290
+ function validateRepairContext(request, content) {
291
+ const round = artifactRound(request.family, request.artifact);
292
+ if (round === null)
293
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact round is not canonical' };
294
+ const contextPattern = /^<!--\s*artifact-context:([^:\s]+):([^:\s]+):(\d+)\s*-->\s*$/;
295
+ const contexts = scanVisibleMarkdown(content).lines
296
+ .map((line) => line.text.trim().match(contextPattern))
297
+ .filter((match) => match !== null);
298
+ if (contexts.length !== 1
299
+ || contexts[0][1] !== request.taskId
300
+ || contexts[0][2] !== request.family
301
+ || Number(contexts[0][3]) !== round) {
302
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
303
+ }
304
+ let taskContent;
305
+ try {
306
+ taskContent = fs.readFileSync(path.join(request.taskDir, 'task.md'), 'utf8');
307
+ }
308
+ catch (error) {
309
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` };
310
+ }
311
+ const activity = locateActivityLog(taskContent);
312
+ if (!activity)
313
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'task has no unique Activity Log section' };
314
+ const label = {
315
+ analysis: 'Analyze Task',
316
+ 'review-analysis': 'Review Analysis',
317
+ plan: 'Plan Task',
318
+ 'review-plan': 'Review Plan',
319
+ code: 'Code Task',
320
+ 'review-code': 'Review Code'
321
+ };
322
+ const escaped = escapeRegExp(label[request.family]);
323
+ const qualifier = request.family === 'code'
324
+ ? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d+))?'
325
+ : '';
326
+ const expected = new RegExp(`^${escaped} \\(Round ${round}${qualifier}\\)$`);
327
+ const open = startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done);
328
+ if (open.length !== 1) {
329
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
330
+ }
331
+ let intent;
332
+ try {
333
+ intent = readArtifactRepairIntent(request.repoRoot, request.taskId, request.family, request.artifact);
334
+ }
335
+ catch (error) {
336
+ return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: String(error) };
337
+ }
338
+ if (!intent || intent.state !== 'awaiting-repair') {
339
+ return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'artifact has no awaiting-repair finalization provenance' };
340
+ }
341
+ if (intent.artifactSha256 !== request.expectedSha256 || intent.semanticDigest !== request.expectedSemanticDigest) {
342
+ return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'repair request does not match the current finalization provenance' };
343
+ }
344
+ return { ok: true };
345
+ }
346
+ function artifactRound(family, artifact) {
347
+ if (artifact === `${family}.md`)
348
+ return 1;
349
+ const match = artifact.match(new RegExp(`^${escapeRegExp(family)}-r([2-9]|[1-9]\\d+)\\.md$`));
350
+ if (!match)
351
+ return null;
352
+ const round = Number(match[1]);
353
+ return Number.isSafeInteger(round) ? round : null;
354
+ }
355
+ function validateTarget(taskDir, family, artifact) {
356
+ if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md'))
357
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be a canonical top-level Markdown file');
358
+ if (!/^TASK-\d{8}-\d{6}$/.test(path.basename(path.resolve(taskDir))))
359
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'target directory is not a task directory');
360
+ const target = path.join(taskDir, artifact);
361
+ if (path.dirname(path.resolve(target)) !== path.resolve(taskDir))
362
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be inside the current task directory');
363
+ let stat;
364
+ try {
365
+ stat = fs.lstatSync(target);
366
+ }
367
+ catch (error) {
368
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact cannot be read: ${String(error)}`);
369
+ }
370
+ if (stat.isSymbolicLink() || !stat.isFile())
371
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact is not a regular file');
372
+ try {
373
+ fs.accessSync(target, fs.constants.R_OK | fs.constants.W_OK);
374
+ }
375
+ catch (error) {
376
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact is not readable and writable: ${String(error)}`);
377
+ }
378
+ return { path: target };
379
+ }
380
+ function applyRepairUnlocked(request) {
381
+ const schema = getArtifactSchema(request.family);
382
+ if (!schema)
383
+ return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
384
+ const target = validateTarget(request.taskDir, request.family, request.artifact);
385
+ if ('status' in target)
386
+ return target;
387
+ let content;
388
+ try {
389
+ content = fs.readFileSync(target.path, 'utf8');
390
+ }
391
+ catch (error) {
392
+ return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error));
393
+ }
394
+ const actualSha256 = sha256Content(content);
395
+ if (actualSha256 !== request.expectedSha256)
396
+ return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact SHA-256 does not match the expected repair baseline');
397
+ const context = validateRepairContext(request, content);
398
+ if (!context.ok)
399
+ return resultFailure(context.code, context.message);
400
+ const inspection = inspectArtifactStructure(content, schema);
401
+ if (!inspection.repair || inspection.diagnostics.length !== 1) {
402
+ return resultFailure('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available');
403
+ }
404
+ if (inspection.repair.kind !== request.operation.kind
405
+ || inspection.repair.sectionId !== request.operation.sectionId
406
+ || inspection.repair.from !== request.operation.from
407
+ || inspection.repair.to !== request.operation.to
408
+ || inspection.repair.start !== request.operation.start
409
+ || inspection.repair.end !== request.operation.end) {
410
+ return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact structure');
411
+ }
412
+ if (inspection.semanticDigest !== request.expectedSemanticDigest)
413
+ return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact semantic digest does not match the expected repair baseline');
414
+ const transformed = applyRepairOperation(content, inspection.repair);
415
+ if (transformed === null)
416
+ return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact bytes');
417
+ if (transformed === content)
418
+ return resultFailure('ARTIFACT_REPAIR_NO_PROGRESS', 'repair operation produced no byte change');
419
+ const tempPath = path.join(request.taskDir, `.${request.artifact}.repair-${process.pid}-${Date.now()}.tmp`);
420
+ let mode = 0o600;
421
+ try {
422
+ mode = fs.statSync(target.path).mode & 0o777;
423
+ fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
424
+ const currentStat = fs.lstatSync(target.path);
425
+ if (currentStat.isSymbolicLink() || !currentStat.isFile() || sha256Content(fs.readFileSync(target.path, 'utf8')) !== actualSha256) {
426
+ fs.unlinkSync(tempPath);
427
+ return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
428
+ }
429
+ fs.renameSync(tempPath, target.path);
430
+ }
431
+ catch (error) {
432
+ try {
433
+ fs.unlinkSync(tempPath);
434
+ }
435
+ catch { /* preserve primary error */ }
436
+ return resultFailure('ARTIFACT_REPAIR_WRITE_FAILED', String(error));
437
+ }
438
+ return {
439
+ status: 'applied',
440
+ changed: true,
441
+ artifactSha256: sha256Content(transformed),
442
+ semanticDigest: canonicalSemanticDigest(transformed),
443
+ operation: inspection.repair,
444
+ error: null
445
+ };
446
+ }
447
+ function applyArtifactRepair(request) {
448
+ try {
449
+ return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.repair', () => applyRepairUnlocked(request));
450
+ }
451
+ catch (error) {
452
+ return resultFailure('ARTIFACT_REPAIR_LOCK_FAILED', String(error));
453
+ }
454
+ }
455
+ function initializeArtifactSkeleton(request) {
456
+ const schema = getArtifactSchema(request.family);
457
+ if (!schema)
458
+ return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
459
+ if (artifactRound(request.family, request.artifact) === null)
460
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be a canonical top-level Markdown file');
461
+ if (!/^TASK-\d{8}-\d{6}$/.test(path.basename(path.resolve(request.taskDir))) || path.basename(path.resolve(request.taskDir)) !== request.taskId)
462
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'target directory is not the requested task directory');
463
+ const target = path.join(request.taskDir, request.artifact);
464
+ if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir))
465
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
466
+ try {
467
+ return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.init', () => {
468
+ try {
469
+ const existing = fs.lstatSync(target);
470
+ if (existing.isSymbolicLink() || !existing.isFile())
471
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
472
+ return resultNoOp(fs.readFileSync(target, 'utf8'));
473
+ }
474
+ catch (error) {
475
+ if (error.code !== 'ENOENT')
476
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
477
+ }
478
+ const content = renderArtifactSkeleton({
479
+ taskId: request.taskId,
480
+ family: request.family,
481
+ artifact: request.artifact,
482
+ ...(request.locale ? { locale: request.locale } : {})
483
+ });
484
+ const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
485
+ try {
486
+ fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
487
+ try {
488
+ fs.lstatSync(target);
489
+ fs.unlinkSync(tempPath);
490
+ return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
491
+ }
492
+ catch (error) {
493
+ if (error.code !== 'ENOENT')
494
+ throw error;
495
+ }
496
+ fs.renameSync(tempPath, target);
497
+ }
498
+ catch (error) {
499
+ try {
500
+ fs.unlinkSync(tempPath);
501
+ }
502
+ catch { /* preserve primary error */ }
503
+ return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
504
+ }
505
+ return {
506
+ status: 'applied',
507
+ changed: true,
508
+ artifactSha256: sha256Content(content),
509
+ semanticDigest: canonicalSemanticDigest(content),
510
+ operation: null,
511
+ error: null
512
+ };
513
+ });
514
+ }
515
+ catch (error) {
516
+ return resultFailure('ARTIFACT_INIT_LOCK_FAILED', String(error));
517
+ }
518
+ }
519
+ export { applyArtifactRepair, canonicalSemanticDigest, initializeArtifactSkeleton, inspectArtifactContract, inspectArtifactPatterns, inspectArtifactStructure, sha256Content };
520
+ //# sourceMappingURL=artifact-operations.js.map