@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
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ import type { ArtifactSchemaFamily } from './artifact-schema.ts';
5
+
6
+ type ArtifactRepairIntent = Readonly<{
7
+ version: 1;
8
+ taskId: string;
9
+ family: ArtifactSchemaFamily;
10
+ artifact: string;
11
+ state: 'awaiting-repair' | 'passed' | 'consumed';
12
+ baselineSemanticDigest: string | null;
13
+ artifactSha256: string;
14
+ semanticDigest: string;
15
+ }>;
16
+
17
+ function intentRoot(repoRoot: string): string {
18
+ return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
19
+ }
20
+
21
+ function intentPath(repoRoot: string, taskId: string, family: ArtifactSchemaFamily, artifact: string): string {
22
+ return path.join(intentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
23
+ }
24
+
25
+ function isArtifactRepairIntent(value: unknown): value is ArtifactRepairIntent {
26
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
27
+ const intent = value as Record<string, unknown>;
28
+ return intent.version === 1
29
+ && typeof intent.taskId === 'string' && /^TASK-\d{8}-\d{6}$/.test(intent.taskId)
30
+ && typeof intent.family === 'string' && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
31
+ && typeof intent.artifact === 'string' && intent.artifact.length > 0
32
+ && ['awaiting-repair', 'passed', 'consumed'].includes(String(intent.state))
33
+ && (intent.baselineSemanticDigest === null || (typeof intent.baselineSemanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.baselineSemanticDigest)))
34
+ && typeof intent.artifactSha256 === 'string' && /^[a-f0-9]{64}$/.test(intent.artifactSha256)
35
+ && typeof intent.semanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.semanticDigest)
36
+ && (intent.state === 'awaiting-repair' ? intent.baselineSemanticDigest === intent.semanticDigest : true);
37
+ }
38
+
39
+ function readArtifactRepairIntent(
40
+ repoRoot: string,
41
+ taskId: string,
42
+ family: ArtifactSchemaFamily,
43
+ artifact: string
44
+ ): ArtifactRepairIntent | null {
45
+ const target = intentPath(repoRoot, taskId, family, artifact);
46
+ if (!fs.existsSync(target)) return null;
47
+ let value: unknown;
48
+ try { value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown; }
49
+ catch (error) { throw new Error(`ARTIFACT_REPAIR_INTENT_INVALID: ${String(error)}`); }
50
+ if (!isArtifactRepairIntent(value) || value.taskId !== taskId || value.family !== family || value.artifact !== artifact) {
51
+ throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
52
+ }
53
+ return value;
54
+ }
55
+
56
+ function writeArtifactRepairIntent(repoRoot: string, value: ArtifactRepairIntent): void {
57
+ if (!isArtifactRepairIntent(value)) throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
58
+ const directory = intentRoot(repoRoot);
59
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
60
+ const target = intentPath(repoRoot, value.taskId, value.family, value.artifact);
61
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
62
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
63
+ try { fs.renameSync(temporary, target); }
64
+ catch (error) {
65
+ try { fs.unlinkSync(temporary); } catch { /* preserve primary error */ }
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ export { readArtifactRepairIntent, writeArtifactRepairIntent };
71
+ export type { ArtifactRepairIntent };
@@ -0,0 +1,192 @@
1
+ type ArtifactFamily =
2
+ | 'analysis'
3
+ | 'review-analysis'
4
+ | 'plan'
5
+ | 'review-plan'
6
+ | 'code'
7
+ | 'review-code'
8
+ | 'manual-validation'
9
+ | 'validation-run'
10
+ | 'pr-review';
11
+
12
+ type ArtifactSchemaFamily = Exclude<ArtifactFamily, 'manual-validation' | 'validation-run' | 'pr-review'>;
13
+ type ArtifactLocale = 'zh-CN' | 'en';
14
+ type ArtifactSection = Readonly<{
15
+ id: string;
16
+ order: number;
17
+ headings: Readonly<{ zh: string; en: string }>;
18
+ marker: string;
19
+ }>;
20
+ type ArtifactSchema = Readonly<{
21
+ family: ArtifactSchemaFamily;
22
+ title: Readonly<{ zh: string; en: string }>;
23
+ sections: readonly ArtifactSection[];
24
+ requiredPatterns: readonly string[];
25
+ }>;
26
+ type ArtifactSkeletonInput = Readonly<{
27
+ taskId: string;
28
+ family: ArtifactSchemaFamily;
29
+ artifact: string;
30
+ locale?: ArtifactLocale;
31
+ }>;
32
+
33
+ type ArtifactFamilySpec = Readonly<{
34
+ family: ArtifactFamily;
35
+ sectionAliases: readonly [string, string];
36
+ heading: string;
37
+ labels: readonly [string, string];
38
+ }>;
39
+
40
+ const ARTIFACT_FAMILY_CATALOG = [
41
+ { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
42
+ { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
43
+ { family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
44
+ { family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
45
+ { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
46
+ { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
47
+ { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
48
+ { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
49
+ { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
50
+ ] as const satisfies readonly ArtifactFamilySpec[];
51
+
52
+ const LOCAL_SECTIONS = {
53
+ analysis: [
54
+ ['source', '需求来源', 'Requirements Source'],
55
+ ['understanding', '需求理解', 'Requirements Understanding'],
56
+ ['files', '相关文件', 'Related Files'],
57
+ ['impact', '影响评估', 'Impact Assessment'],
58
+ ['risks', '技术风险', 'Technical Risks'],
59
+ ['effort', '工作量和复杂度评估', 'Effort and Complexity Assessment'],
60
+ ['state-check', '状态核对', 'State Check']
61
+ ],
62
+ plan: [
63
+ ['understanding', '问题理解', 'Problem Understanding'],
64
+ ['constraints', '约束条件', 'Constraints'],
65
+ ['options', '方案对比', 'Options Comparison'],
66
+ ['approach', '技术方法', 'Technical Approach'],
67
+ ['steps', '实施步骤', 'Implementation Steps'],
68
+ ['files', '文件清单', 'File List'],
69
+ ['verification', '验证策略', 'Verification Strategy'],
70
+ ['state-check', '状态核对', 'State Check']
71
+ ],
72
+ code: [
73
+ ['input', '实现输入', 'Implementation Input'],
74
+ ['files', '变更文件', 'Changed Files'],
75
+ ['key-code', '关键代码说明', 'Key Code Notes'],
76
+ ['tests', '测试结果', 'Test Results'],
77
+ ['deviations', '与方案的差异', 'Differences from Plan'],
78
+ ['review-focus', '供审查关注的内容', 'Review Focus'],
79
+ ['state-check', '状态核对', 'State Check'],
80
+ ['evidence', '证据原文', 'Raw Evidence']
81
+ ],
82
+ 'review-analysis': [
83
+ ['summary', '审查摘要', 'Review Summary'],
84
+ ['coverage', '检视覆盖声明', 'Inspection Coverage'],
85
+ ['specialized-coverage', '需求分析专项覆盖', 'Requirements Analysis Coverage'],
86
+ ['traceability', '追踪矩阵', 'Traceability Matrix'],
87
+ ['issues', '问题清单', 'Findings'],
88
+ ['non-blocking', '非阻塞建议', 'Non-blocking Suggestions'],
89
+ ['manual-validation', '人工校验项', 'Manual Validation Items'],
90
+ ['conclusion', '结论与建议', 'Conclusion and Recommendation'],
91
+ ['state-check', '状态核对', 'State Check'],
92
+ ['evidence', '证据原文', 'Raw Evidence'],
93
+ ['self-critique', '自我质疑', 'Self-critique'],
94
+ ['ledger-writeback', '审查分歧账本回写', 'Review Disagreement Ledger Write-back']
95
+ ],
96
+ 'review-plan': [
97
+ ['summary', '审查摘要', 'Review Summary'],
98
+ ['coverage', '检视覆盖声明', 'Inspection Coverage'],
99
+ ['specialized-coverage', '技术方案架构覆盖', 'Technical Plan Architecture Coverage'],
100
+ ['traceability', '追踪矩阵', 'Traceability Matrix'],
101
+ ['issues', '问题清单', 'Findings'],
102
+ ['non-blocking', '非阻塞建议', 'Non-blocking Suggestions'],
103
+ ['manual-validation', '人工校验项', 'Manual Validation Items'],
104
+ ['conclusion', '结论与建议', 'Conclusion and Recommendation'],
105
+ ['state-check', '状态核对', 'State Check'],
106
+ ['evidence', '证据原文', 'Raw Evidence'],
107
+ ['self-critique', '自我质疑', 'Self-critique'],
108
+ ['ledger-writeback', '审查分歧账本回写', 'Review Disagreement Ledger Write-back']
109
+ ],
110
+ 'review-code': [
111
+ ['summary', '审查摘要', 'Review Summary'],
112
+ ['coverage', '检视覆盖声明', 'Inspection Coverage'],
113
+ ['specialized-coverage', '代码实现专项覆盖', 'Code Implementation Coverage'],
114
+ ['traceability', '追踪矩阵', 'Traceability Matrix'],
115
+ ['issues', '问题清单', 'Findings'],
116
+ ['non-blocking', '非阻塞建议', 'Non-blocking Suggestions'],
117
+ ['manual-validation', '人工校验项', 'Manual Validation Items'],
118
+ ['conclusion', '结论与建议', 'Conclusion and Recommendation'],
119
+ ['state-check', '状态核对', 'State Check'],
120
+ ['evidence', '证据原文', 'Raw Evidence'],
121
+ ['self-critique', '自我质疑', 'Self-critique'],
122
+ ['ledger-writeback', '审查分歧账本回写', 'Review Disagreement Ledger Write-back']
123
+ ]
124
+ } as const;
125
+
126
+ function buildSections(family: ArtifactSchemaFamily): readonly ArtifactSection[] {
127
+ return LOCAL_SECTIONS[family].map(([id, zh, en], index) => ({
128
+ id,
129
+ order: index + 1,
130
+ headings: { zh, en },
131
+ marker: `artifact-section:${family}:${id}`
132
+ }));
133
+ }
134
+
135
+ const ARTIFACT_SCHEMAS: readonly ArtifactSchema[] = [
136
+ { family: 'analysis', title: { zh: '需求分析报告', en: 'Requirements Analysis' }, sections: buildSections('analysis'), requiredPatterns: ['^\\$ '] },
137
+ { family: 'review-analysis', title: { zh: '需求分析审查报告', en: 'Analysis Review Report' }, sections: buildSections('review-analysis'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$', '^\\$ '] },
138
+ { family: 'plan', title: { zh: '技术方案', en: 'Technical Plan' }, sections: buildSections('plan'), requiredPatterns: ['^\\$ '] },
139
+ { family: 'review-plan', title: { zh: '技术方案审查报告', en: 'Plan Review Report' }, sections: buildSections('review-plan'), requiredPatterns: ['^### (?:审查决定|Approval Decision)$', '^\\$ '] },
140
+ { family: 'code', title: { zh: '实现报告', en: 'Implementation Report' }, sections: buildSections('code'), requiredPatterns: ['^\\$ '] },
141
+ { family: 'review-code', title: { zh: '代码审查报告', en: 'Code Review Report' }, sections: buildSections('review-code'), requiredPatterns: [
142
+ '^### (?:审查决定|Approval Decision)$',
143
+ '^\\$ ',
144
+ '^- \\*\\*(?:总体结论|Overall Verdict)\\*\\*[::]\\s*(?:通过|需要修改|拒绝|Approved|Changes Requested|Rejected)\\s*$',
145
+ '^- \\*\\*(?:审查基线提交|Review Baseline Commit)\\*\\*[::]\\s*\\S',
146
+ '^- \\*\\*(?:审查差异基线|Reviewed Diff Base)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$',
147
+ '^- \\*\\*(?:审查差异指纹|Reviewed Diff Fingerprint)\\*\\*[::]\\s*sha256:[0-9a-f]{64}\\s*$',
148
+ '^- \\*\\*(?:审查快照树|Reviewed Snapshot Tree)\\*\\*[::]\\s*[0-9a-f]{40,64}\\s*$'
149
+ ] }
150
+ ];
151
+
152
+ function getArtifactSchema(family: string): ArtifactSchema | null {
153
+ return ARTIFACT_SCHEMAS.find((schema) => schema.family === family) ?? null;
154
+ }
155
+
156
+ function renderArtifactSkeleton(input: ArtifactSkeletonInput): string {
157
+ const schema = getArtifactSchema(input.family);
158
+ if (!schema) throw new Error(`unknown artifact schema family '${input.family}'`);
159
+ const locale = input.locale ?? 'zh-CN';
160
+ const roundMatch = input.artifact.match(/-r(\d+)\.md$/);
161
+ const round = roundMatch ? Number(roundMatch[1]) : 1;
162
+ const title = schema.title[locale === 'en' ? 'en' : 'zh'];
163
+ const headings = locale === 'en' ? 'en' : 'zh';
164
+ const lines = [
165
+ `<!-- artifact-context:${input.taskId}:${input.family}:${round} -->`,
166
+ `# ${title}`,
167
+ ''
168
+ ];
169
+ for (const section of schema.sections) {
170
+ lines.push(`## ${section.headings[headings]}`);
171
+ lines.push(`<!-- ${section.marker} -->`);
172
+ lines.push('<!-- artifact-slot:empty -->');
173
+ lines.push('');
174
+ }
175
+ return `${lines.join('\n').trimEnd()}\n`;
176
+ }
177
+
178
+ export {
179
+ ARTIFACT_FAMILY_CATALOG,
180
+ ARTIFACT_SCHEMAS,
181
+ getArtifactSchema,
182
+ renderArtifactSkeleton
183
+ };
184
+ export type {
185
+ ArtifactFamily,
186
+ ArtifactFamilySpec,
187
+ ArtifactLocale,
188
+ ArtifactSchema,
189
+ ArtifactSchemaFamily,
190
+ ArtifactSection,
191
+ ArtifactSkeletonInput
192
+ };
@@ -11,6 +11,10 @@ import { parseReworkIntentDocument } from './rework-intent.ts';
11
11
  import type { ReworkIntent } from './rework-intent.ts';
12
12
  import { sha256File } from './artifact-receipts.ts';
13
13
  import { hasOpenLifecycleExecution } from './activity-log.ts';
14
+ import { parseQualificationAudit, parseTaskQualification } from './qualification-audit.ts';
15
+ import type { QualificationAudit, TaskQualification } from './qualification-audit.ts';
16
+
17
+ const ARTIFACT_AUDIT_FAMILIES = new Set(['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code']);
14
18
 
15
19
  type LifecycleAction =
16
20
  | 'analysis' | 'review-analysis' | 'plan' | 'review-plan'
@@ -42,6 +46,8 @@ type LifecycleFacts = {
42
46
  unresolvedLedger: Record<'analysis' | 'plan' | 'code', number>;
43
47
  executionBusy: boolean;
44
48
  recommendedAction?: LifecycleAction | null;
49
+ qualificationStale?: boolean;
50
+ qualificationStaleArtifacts?: readonly string[];
45
51
  };
46
52
  type CapabilityResult = {
47
53
  allowed: boolean;
@@ -86,6 +92,9 @@ function canStart(action: LifecycleAction, facts: LifecycleFacts, trigger: Expli
86
92
  }
87
93
  if (facts.taskState !== 'active') return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
88
94
  if (invalidationBlocks(facts.invalidation)) return deny('INVALIDATION_INCOMPLETE');
95
+ if (facts.qualificationStale && facts.recommendedAction !== action) {
96
+ return deny('QUALIFICATION_STALE', ...(facts.qualificationStaleArtifacts ?? ['qualification audit is stale']));
97
+ }
89
98
  if (facts.executionBusy) return deny('EXECUTION_BUSY');
90
99
 
91
100
  const implicit = trigger.explicitRequest === false;
@@ -154,8 +163,42 @@ function reviewMatchesLatest(facts: LifecycleFacts, source: 'analysis' | 'plan'
154
163
  return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
155
164
  }
156
165
 
166
+ function qualificationCandidateSnapshotMatches(
167
+ audit: QualificationAudit,
168
+ qualification: TaskQualification
169
+ ): boolean {
170
+ const current = new Map(qualification.candidates.map((candidate) => [candidate.candidateId, candidate]));
171
+ return audit.candidateQualifications.length === current.size && audit.candidateQualifications.every((row) => {
172
+ const candidate = current.get(row.candidateId);
173
+ return Boolean(candidate && candidate.status === row.status && candidate.impact === row.impact
174
+ && candidate.evidence === row.evidence && candidate.constraintIds.join(',') === row.constraintIds.join(','));
175
+ });
176
+ }
177
+
178
+ const QUALIFICATION_RECOVERY_ORDER: ReadonlyArray<{ action: LifecycleAction; pattern: RegExp }> = [
179
+ { action: 'analysis', pattern: /^analysis(?:-r\d+)?\.md$/ },
180
+ { action: 'review-analysis', pattern: /^review-analysis(?:-r\d+)?\.md$/ },
181
+ { action: 'plan', pattern: /^plan(?:-r\d+)?\.md$/ },
182
+ { action: 'review-plan', pattern: /^review-plan(?:-r\d+)?\.md$/ },
183
+ { action: 'code', pattern: /^code(?:-r\d+)?\.md$/ },
184
+ { action: 'review-code', pattern: /^review-code(?:-r\d+)?\.md$/ }
185
+ ];
186
+
187
+ function qualificationRecoveryAction(facts: LifecycleFacts): LifecycleAction | null {
188
+ const stale = facts.qualificationStaleArtifacts ?? [];
189
+ return QUALIFICATION_RECOVERY_ORDER.find(({ pattern }) => stale.some((name) => pattern.test(name)))?.action ?? null;
190
+ }
191
+
157
192
  function recommendNext(facts: LifecycleFacts): LifecycleRecommendation {
158
193
  if (invalidationBlocks(facts.invalidation)) return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
194
+ if (facts.qualificationStale) {
195
+ const action = qualificationRecoveryAction(facts);
196
+ return {
197
+ action,
198
+ reasonCode: 'QUALIFICATION_RECOVERY_REQUIRED',
199
+ evidence: facts.qualificationStaleArtifacts ?? ['qualification audit is stale']
200
+ };
201
+ }
159
202
  const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
160
203
  if (pendingIntent) return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
161
204
  if (!hasArtifact(facts, 'analysis')) return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
@@ -226,6 +269,28 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
226
269
  family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
227
270
  ])
228
271
  ) as Partial<Record<LifecycleAction, readonly string[]>>;
272
+ const qualification = parseTaskQualification(content);
273
+ if (!qualification.ok) return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: qualification.message };
274
+ const qualificationStaleArtifacts: string[] = [];
275
+ if (qualification.qualification.present) {
276
+ const constraints = new Map(qualification.qualification.constraints.map((row) => [row.constraintId, row.digest]));
277
+ for (const family of Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>) {
278
+ if (!ARTIFACT_AUDIT_FAMILIES.has(family)) continue;
279
+ const name = latestArtifact(artifacts[family] ?? []);
280
+ if (!name) continue;
281
+ let auditContent: string;
282
+ try { auditContent = fs.readFileSync(path.join(taskDir, name), 'utf8'); }
283
+ catch { qualificationStaleArtifacts.push(name); continue; }
284
+ const audit = parseQualificationAudit(auditContent);
285
+ if (!audit.ok || !audit.audit.present || !audit.audit.snapshot) { qualificationStaleArtifacts.push(name); continue; }
286
+ const constraintsChanged = audit.audit.constraintDependencies.some((dependency) => constraints.get(dependency.constraintId) !== dependency.constraintDigest);
287
+ const taskInputChanged = audit.audit.snapshot.taskInputDigest !== qualification.qualification.taskInputDigest;
288
+ if (audit.audit.snapshot.nonConstraintInputDigest !== qualification.qualification.nonConstraintInputDigest
289
+ || constraintsChanged
290
+ || (taskInputChanged && !constraintsChanged)
291
+ || (taskInputChanged && !qualificationCandidateSnapshotMatches(audit.audit, qualification.qualification))) qualificationStaleArtifacts.push(name);
292
+ }
293
+ }
229
294
  const reviews: LifecycleFacts['reviews'] = {};
230
295
  const reviewedInputs: NonNullable<LifecycleFacts['reviewedInputs']> = {};
231
296
  for (const family of ['review-analysis', 'review-plan', 'review-code'] as const) {
@@ -249,7 +314,11 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
249
314
  taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
250
315
  staleArtifacts, reviewedInputs, artifactHashes,
251
316
  invalidation: invalidation.document, reworkIntents: rework.intents,
252
- unresolvedLedger, executionBusy: executionBusy || hasOpenLifecycleExecution(content), recommendedAction: null
317
+ unresolvedLedger,
318
+ executionBusy: executionBusy || hasOpenLifecycleExecution(content),
319
+ recommendedAction: null,
320
+ qualificationStale: qualificationStaleArtifacts.length > 0,
321
+ qualificationStaleArtifacts
253
322
  };
254
323
  facts.recommendedAction = recommendNext(facts).action;
255
324
  return { ok: true, facts };
@@ -20,7 +20,11 @@ import {
20
20
  sealOrchestrationDelegation,
21
21
  statusOrchestration
22
22
  } from './orchestration.ts';
23
- import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
23
+ import type {
24
+ OrchestrationDiagnosticLogger,
25
+ OrchestrationOptions,
26
+ OrchestrationResult
27
+ } from './orchestration.ts';
24
28
  import { prepareCodexOrchestrationDelegation } from './codex-orchestration.ts';
25
29
  import {
26
30
  applyTaskFinalization,
@@ -60,6 +64,7 @@ export type TaskControlExecutionContext =
60
64
  generation: string;
61
65
  manifestPath: string;
62
66
  requestId: string;
67
+ diagnosticLog?: OrchestrationDiagnosticLogger;
63
68
  controllerBinding?: TaskControlControllerBinding | null;
64
69
  }>;
65
70
 
@@ -134,6 +139,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
134
139
  generation: string;
135
140
  manifestPath: string;
136
141
  requestId: string;
142
+ diagnosticLog?: OrchestrationDiagnosticLogger;
137
143
  controllerBinding?: TaskControlControllerBinding | null;
138
144
  }>): TaskControlExecutionContext {
139
145
  if (!/^TASK-\d{8}-\d{6}$/u.test(params.taskId) || !params.generation || !params.requestId) {
@@ -153,6 +159,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
153
159
  generation: params.generation,
154
160
  manifestPath: absolute('manifestPath', params.manifestPath),
155
161
  requestId: params.requestId,
162
+ ...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
156
163
  ...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
157
164
  };
158
165
  }
@@ -192,6 +199,9 @@ function domainOptions(
192
199
  return {
193
200
  ...options,
194
201
  repoRoot: context.repoRoot,
202
+ ...(context.source === 'sandbox-executor' && context.diagnosticLog !== undefined
203
+ ? { diagnosticLog: context.diagnosticLog }
204
+ : {}),
195
205
  ...(context.source === 'sandbox-executor' ? { gitWorktreeRoot: context.worktreeRoot } : {})
196
206
  };
197
207
  }
@@ -12,6 +12,13 @@ import { verifyTaskEvent } from './verification.ts';
12
12
  type TaskCreateStatus = 'applied' | 'no-op' | 'degraded' | 'failed' | 'blocked';
13
13
  type TaskCreateOperation = { name: string; status: string; reasonCode: string | null };
14
14
  type TaskCreateWarning = { code: string; severity: string; action: string };
15
+ type TaskCreateRecovery = 'none' | 'same-request-id' | 'new-request-id' | 'inspect-domain-state';
16
+ type TaskCreateControl = Readonly<{
17
+ requestId: string | null;
18
+ accepted: boolean;
19
+ recovery: TaskCreateRecovery;
20
+ }>;
21
+ type TaskCreateError = { code: string; message: string; retryable: boolean };
15
22
  type TaskCreateResult = Readonly<{
16
23
  status: TaskCreateStatus;
17
24
  changed: boolean;
@@ -19,7 +26,8 @@ type TaskCreateResult = Readonly<{
19
26
  issue: { number: number; url: string } | null;
20
27
  operations: readonly TaskCreateOperation[];
21
28
  warnings: readonly TaskCreateWarning[];
22
- error: { code: string; message: string; retryable: boolean } | null;
29
+ error: TaskCreateError | null;
30
+ control?: TaskCreateControl;
23
31
  }>;
24
32
 
25
33
  type CreateTaskOptions = Readonly<{
@@ -42,6 +50,116 @@ const DEFAULT_DEPENDENCIES: TaskCreateDependencies = {
42
50
  addWarning: applyWorkflowWarningIntent
43
51
  };
44
52
 
53
+ function invalidTaskCreateResult(): never {
54
+ throw new Error('TASK_CREATE_RESULT_INVALID');
55
+ }
56
+
57
+ function exactKeys(value: Record<string, unknown>, expected: readonly string[]): boolean {
58
+ return Object.keys(value).sort().join(',') === [...expected].sort().join(',');
59
+ }
60
+
61
+ function validTaskReference(value: unknown): boolean {
62
+ return value === null || (typeof value === 'string' && value.length > 0);
63
+ }
64
+
65
+ function validTaskCreateError(value: unknown): value is TaskCreateError {
66
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
67
+ const error = value as Record<string, unknown>;
68
+ return exactKeys(error, ['code', 'message', 'retryable'])
69
+ && typeof error.code === 'string' && /^[A-Z][A-Z0-9_]+$/u.test(error.code)
70
+ && typeof error.message === 'string'
71
+ && typeof error.retryable === 'boolean';
72
+ }
73
+
74
+ function validTaskCreateControl(value: unknown): value is TaskCreateControl {
75
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
76
+ const control = value as Record<string, unknown>;
77
+ return exactKeys(control, ['accepted', 'recovery', 'requestId'])
78
+ && validTaskReference(control.requestId)
79
+ && typeof control.accepted === 'boolean'
80
+ && ['none', 'same-request-id', 'new-request-id', 'inspect-domain-state'].includes(control.recovery as string);
81
+ }
82
+
83
+ export function parseTaskCreateResult(value: unknown): TaskCreateResult {
84
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return invalidTaskCreateResult();
85
+ const result = value as Record<string, unknown>;
86
+ if (!exactKeys(result, ['changed', 'control', 'error', 'issue', 'operations', 'status', 'task', 'warnings'])
87
+ && !exactKeys(result, ['changed', 'error', 'issue', 'operations', 'status', 'task', 'warnings'])) {
88
+ return invalidTaskCreateResult();
89
+ }
90
+ if (!['applied', 'no-op', 'degraded', 'failed', 'blocked'].includes(result.status as string)
91
+ || typeof result.changed !== 'boolean'
92
+ || !result.task || typeof result.task !== 'object' || Array.isArray(result.task)
93
+ || !exactKeys(result.task as Record<string, unknown>, ['id', 'shortId'])
94
+ || !validTaskReference((result.task as { id?: unknown }).id)
95
+ || !validTaskReference((result.task as { shortId?: unknown }).shortId)
96
+ || !Array.isArray(result.operations)
97
+ || !result.operations.every((operation) => {
98
+ if (!operation || typeof operation !== 'object' || Array.isArray(operation)) return false;
99
+ const value = operation as Record<string, unknown>;
100
+ return exactKeys(value, ['name', 'reasonCode', 'status'])
101
+ && typeof value.name === 'string' && typeof value.status === 'string'
102
+ && (value.reasonCode === null || typeof value.reasonCode === 'string');
103
+ })
104
+ || !Array.isArray(result.warnings)
105
+ || !result.warnings.every((warning) => {
106
+ if (!warning || typeof warning !== 'object' || Array.isArray(warning)) return false;
107
+ const value = warning as Record<string, unknown>;
108
+ return exactKeys(value, ['action', 'code', 'severity'])
109
+ && typeof value.action === 'string' && typeof value.code === 'string' && typeof value.severity === 'string';
110
+ })
111
+ || (result.issue !== null && (
112
+ !result.issue || typeof result.issue !== 'object' || Array.isArray(result.issue)
113
+ || !exactKeys(result.issue as Record<string, unknown>, ['number', 'url'])
114
+ || !Number.isSafeInteger((result.issue as { number?: unknown }).number)
115
+ || ((result.issue as { number: number }).number) <= 0
116
+ || typeof (result.issue as { url?: unknown }).url !== 'string'
117
+ ))
118
+ || (result.error !== null && !validTaskCreateError(result.error))
119
+ || ('control' in result && !validTaskCreateControl(result.control))) {
120
+ return invalidTaskCreateResult();
121
+ }
122
+ if (result.status === 'blocked' && !(result.error as TaskCreateError | null)?.retryable) return invalidTaskCreateResult();
123
+ if (result.status === 'failed' && !(result.error as TaskCreateError | null)) return invalidTaskCreateResult();
124
+ return result as unknown as TaskCreateResult;
125
+ }
126
+
127
+ export function projectTaskCreateResult(result: TaskCreateResult, control: TaskCreateControl): TaskCreateResult {
128
+ return { ...result, control };
129
+ }
130
+
131
+ export function taskCreateExitCode(result: Pick<TaskCreateResult, 'status'>): number {
132
+ return result.status === 'blocked' ? 2 : result.status === 'failed' ? 1 : 0;
133
+ }
134
+
135
+ export function taskCreateFailure(
136
+ error: TaskCreateError,
137
+ control?: TaskCreateControl
138
+ ): TaskCreateResult {
139
+ return {
140
+ status: error.retryable ? 'blocked' : 'failed',
141
+ changed: false,
142
+ task: { id: null, shortId: null },
143
+ issue: null,
144
+ operations: [],
145
+ warnings: [],
146
+ error,
147
+ ...(control ? { control } : {})
148
+ };
149
+ }
150
+
151
+ export function taskCreateOutputUnavailableResult(requestId: string): TaskCreateResult {
152
+ return taskCreateFailure({
153
+ code: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE',
154
+ message: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: task-create result output was not retained; inspect domain state before retrying',
155
+ retryable: false
156
+ }, {
157
+ requestId,
158
+ accepted: true,
159
+ recovery: 'inspect-domain-state'
160
+ });
161
+ }
162
+
45
163
  async function verifyCreatedTask(repoRoot: string, taskId: string): Promise<{ operation: TaskCreateOperation; error: TaskCreateResult['error'] }> {
46
164
  const verification = await verifyTaskEvent(
47
165
  { taskRef: taskId, event: 'create-task.completed' },
@@ -91,10 +209,7 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
91
209
  } catch (error) {
92
210
  const message = error instanceof Error ? error.message : String(error);
93
211
  const code = /^([A-Z][A-Z0-9_]+)/.exec(message)?.[1] ?? 'TASK_CREATE_FAILED';
94
- return {
95
- status: 'failed', changed: false, task: { id: null, shortId: null }, issue: null,
96
- operations: [], warnings: [], error: { code, message, retryable: code === 'TASK_CREATE_LOCK_TIMEOUT' }
97
- };
212
+ return taskCreateFailure({ code, message, retryable: code === 'TASK_CREATE_LOCK_TIMEOUT' });
98
213
  }
99
214
 
100
215
  const operations: TaskCreateOperation[] = [{
@@ -168,4 +283,14 @@ async function createTask(value: unknown, options: CreateTaskOptions): Promise<T
168
283
  }
169
284
 
170
285
  export { createTask };
171
- export type { CreateTaskOptions, TaskCreateDependencies, TaskCreateOperation, TaskCreateResult, TaskCreateStatus, TaskCreateWarning };
286
+ export type {
287
+ CreateTaskOptions,
288
+ TaskCreateControl,
289
+ TaskCreateDependencies,
290
+ TaskCreateError,
291
+ TaskCreateOperation,
292
+ TaskCreateRecovery,
293
+ TaskCreateResult,
294
+ TaskCreateStatus,
295
+ TaskCreateWarning
296
+ };
@@ -7,6 +7,7 @@ import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.ts';
7
7
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
8
8
  import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.ts';
9
9
  import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.ts';
10
+ import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS } from './qualification-audit.ts';
10
11
 
11
12
  const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'] as const;
12
13
  const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'] as const;
@@ -178,6 +179,25 @@ function renderList(items: readonly string[]): string {
178
179
  return items.map((item) => `- ${item}`).join('\n');
179
180
  }
180
181
 
182
+ function tableCell(value: string): string {
183
+ return value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ');
184
+ }
185
+
186
+ function renderTable(columns: readonly string[], rows: readonly (readonly string[])[]): string {
187
+ return [
188
+ `| ${columns.join(' | ')} |`,
189
+ `| ${columns.map(() => '---').join(' | ')} |`,
190
+ ...rows.map((row) => `| ${row.map(tableCell).join(' | ')} |`)
191
+ ].join('\n');
192
+ }
193
+
194
+ function appendCanonicalRows(content: string, columns: readonly string[], rows: readonly (readonly string[])[]): string {
195
+ if (rows.length === 0) return content;
196
+ const table = renderTable(columns, rows);
197
+ const separator = `| ${columns.map(() => '---').join(' | ')} |`;
198
+ return content.replace(separator, `${separator}\n${table.split('\n').slice(2).join('\n')}`);
199
+ }
200
+
181
201
  function replaceEmptySubsection(content: string, heading: string, items: readonly string[]): string {
182
202
  if (items.length === 0) return content;
183
203
  return content.replace(`${heading}\n`, `${heading}\n\n${renderList(items)}\n`);
@@ -231,6 +251,13 @@ function renderTask(params: Readonly<{
231
251
  ['### 验收标准', 'acceptanceCriteria'], ['### 未决事项', 'openQuestions']
232
252
  ];
233
253
  for (const [heading, key] of sections) content = replaceEmptySubsection(content, heading, candidate.taskInput[key]);
254
+ const constraintIds = candidate.taskInput.constraints.map((_, index) => `C-${index + 1}`);
255
+ content = appendCanonicalRows(content, CONSTRAINT_COLUMNS, candidate.taskInput.constraints.map((statement, index) => [
256
+ constraintIds[index]!, statement, 'assumption', 'create-task', 'taskInput', 'create-task input', '', ''
257
+ ]));
258
+ content = appendCanonicalRows(content, CANDIDATE_COLUMNS, candidate.taskInput.alternatives.map((statement, index) => [
259
+ String.fromCharCode(65 + index), statement, 'pending', constraintIds.join(','), 'requires qualification', 'create-task input'
260
+ ]));
234
261
  content = content.replace('- **关联 Issue**:#XXX', '- **关联 Issue**:N/A');
235
262
  content = content.replace('- **关联 PR**:#XXX', '- **关联 PR**:N/A');
236
263
  content = content.replace('- **分支**:`feature/xxx`', `- **分支**:\`${branch}\``);