@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,132 @@
1
+ import type { SpawnSyncReturns } from 'node:child_process';
2
+ import { commandForEngine, runProbe } from '../shell.ts';
3
+ import { commandForSandboxAuthority, type SandboxAuthorityEvidenceV1 } from './authority.ts';
4
+
5
+ type ProbeResult = SpawnSyncReturns<string | Buffer>;
6
+ type Probe = (cmd: string, args: string[], options?: { timeout?: number }) => ProbeResult;
7
+
8
+ export type ExactContainerDiscovery =
9
+ | Readonly<{ state: 'absent'; id: string; evidence: 'exact-empty' }>
10
+ | Readonly<{
11
+ state: 'found';
12
+ id: string;
13
+ running: boolean;
14
+ labels: Readonly<Record<string, string>>;
15
+ evidence: 'exact-present';
16
+ }>
17
+ | Readonly<{ state: 'conflict'; reason: string }>
18
+ | Readonly<{ state: 'unknown'; reason: string }>;
19
+
20
+ export type ExactContainerDiscoveryOptions = Readonly<{
21
+ probe?: Probe;
22
+ timeoutMs?: number;
23
+ authority?: SandboxAuthorityEvidenceV1;
24
+ }>;
25
+
26
+ const FULL_CONTAINER_ID = /^[a-f0-9]{64}$/u;
27
+
28
+ function outputText(value: string | Buffer | null | undefined): string {
29
+ return typeof value === 'string' ? value : value?.toString('utf8') ?? '';
30
+ }
31
+
32
+ function unknown(reason: string): ExactContainerDiscovery {
33
+ return { state: 'unknown', reason };
34
+ }
35
+
36
+ export function buildExactContainerListArgs(id: string): string[] {
37
+ if (!FULL_CONTAINER_ID.test(id)) throw new Error('SANDBOX_CONTAINER_ID_NOT_FULL');
38
+ return ['container', 'ls', '--all', '--no-trunc', '--filter', `id=${id}`, '--format', '{{.ID}}'];
39
+ }
40
+
41
+ function buildExactContainerInspectArgs(id: string): string[] {
42
+ return ['container', 'inspect', '--format', '{{json .}}', id];
43
+ }
44
+
45
+ function runDockerProbe(
46
+ engine: string,
47
+ args: string[],
48
+ options: ExactContainerDiscoveryOptions
49
+ ): ProbeResult {
50
+ const command = options.authority
51
+ ? commandForSandboxAuthority(options.authority, 'docker', args)
52
+ : commandForEngine(engine, 'docker', args);
53
+ return (options.probe ?? runProbe)(
54
+ command.cmd,
55
+ command.args,
56
+ options.timeoutMs === undefined ? {} : { timeout: options.timeoutMs }
57
+ );
58
+ }
59
+
60
+ function parseExactList(stdout: string, expectedId: string): 'empty' | 'present' | 'conflict' | string {
61
+ const rows = stdout.split(/\r?\n/u).map((row) => row.trim()).filter(Boolean);
62
+ if (rows.length === 0) return 'empty';
63
+ if (rows.length !== 1) return 'exact container query returned multiple rows';
64
+ if (!FULL_CONTAINER_ID.test(rows[0]!)) return 'exact container query returned a malformed ID';
65
+ if (rows[0] !== expectedId) return 'conflict';
66
+ return 'present';
67
+ }
68
+
69
+ function parseExactInspect(stdout: string, expectedId: string): ExactContainerDiscovery {
70
+ let value: unknown;
71
+ try {
72
+ value = JSON.parse(stdout);
73
+ } catch {
74
+ return unknown('exact container inspection returned malformed JSON');
75
+ }
76
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
77
+ return unknown('exact container inspection returned an incomplete object');
78
+ }
79
+ const inspected = value as {
80
+ Id?: unknown;
81
+ State?: { Running?: unknown };
82
+ Config?: { Labels?: unknown };
83
+ };
84
+ if (inspected.Id !== expectedId || typeof inspected.State?.Running !== 'boolean'
85
+ || !inspected.Config?.Labels || typeof inspected.Config.Labels !== 'object'
86
+ || Array.isArray(inspected.Config.Labels)) {
87
+ return unknown('exact container inspection identity is incomplete');
88
+ }
89
+ const rawLabels = inspected.Config.Labels as Record<string, unknown>;
90
+ if (Object.values(rawLabels).some((value) => typeof value !== 'string')) {
91
+ return unknown('exact container inspection labels are malformed');
92
+ }
93
+ return {
94
+ state: 'found',
95
+ id: expectedId,
96
+ running: inspected.State.Running,
97
+ labels: { ...rawLabels as Record<string, string> },
98
+ evidence: 'exact-present'
99
+ };
100
+ }
101
+
102
+ export function discoverExactContainer(
103
+ engine: string,
104
+ id: string,
105
+ options: ExactContainerDiscoveryOptions = {}
106
+ ): ExactContainerDiscovery {
107
+ if (!FULL_CONTAINER_ID.test(id)) return unknown('container identity is not a full 64-character ID');
108
+ let listed: ProbeResult;
109
+ try {
110
+ listed = runDockerProbe(engine, buildExactContainerListArgs(id), options);
111
+ } catch {
112
+ return unknown('exact container query failed before producing a result');
113
+ }
114
+ if (listed.status !== 0) {
115
+ return unknown(`exact container query exited with ${listed.status ?? 'unknown status'}`);
116
+ }
117
+ const listState = parseExactList(outputText(listed.stdout), id);
118
+ if (listState === 'empty') return { state: 'absent', id, evidence: 'exact-empty' };
119
+ if (listState === 'conflict') return { state: 'conflict', reason: 'exact container query returned a different ID' };
120
+ if (listState !== 'present') return unknown(listState);
121
+
122
+ let inspected: ProbeResult;
123
+ try {
124
+ inspected = runDockerProbe(engine, buildExactContainerInspectArgs(id), options);
125
+ } catch {
126
+ return unknown('exact container inspection failed before producing a result');
127
+ }
128
+ if (inspected.status !== 0) {
129
+ return unknown(`exact container inspection exited with ${inspected.status ?? 'unknown status'}`);
130
+ }
131
+ return parseExactInspect(outputText(inspected.stdout).trim(), id);
132
+ }
@@ -1,3 +1,5 @@
1
+ import { PUBLIC_CLI_ROUTE_SELECTORS } from '../internal/cli-route-inventory.ts';
2
+
1
3
  const USAGE = `Usage: ai sandbox <command> [options]
2
4
 
3
5
  Commands:
@@ -26,6 +28,7 @@ Commands:
26
28
  vm status|start|stop Manage the sandbox VM (macOS) or check the backend (Windows)
27
29
 
28
30
  Run 'ai sandbox <command> --help' for details.`;
31
+ const SANDBOX_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS.sandbox;
29
32
 
30
33
  export async function runSandbox(args: string[]): Promise<void> {
31
34
  const [subcommand, ...rest] = args;
@@ -41,6 +44,10 @@ export async function runSandbox(args: string[]): Promise<void> {
41
44
  return;
42
45
  }
43
46
 
47
+ if (!SANDBOX_OPERATIONS.includes(subcommand as typeof SANDBOX_OPERATIONS[number])) {
48
+ throw new Error(`Unknown sandbox command: ${subcommand}`);
49
+ }
50
+
44
51
  switch (subcommand) {
45
52
  case 'create': {
46
53
  const { create } = await import('./commands/create.ts');
@@ -1,3 +1,4 @@
1
+ import { prepareCompletedReentry, publishCompletedReentry, waitForCompletedReentry } from './control/completed-reentry.ts';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { spawn } from 'node:child_process';
@@ -653,7 +654,7 @@ function recoveryTaskSources(repoRoot: string, taskId: string): {
653
654
  return { mountPaths, accessiblePaths: [completedSource] };
654
655
  }
655
656
 
656
- function startCompletedSandboxContainer(params: {
657
+ export function startCompletedSandboxContainer(params: {
657
658
  config: SandboxConfig;
658
659
  engine: string;
659
660
  container: string;
@@ -1250,7 +1251,17 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
1250
1251
  throw new Error('Explicit container recreation requested.');
1251
1252
  }
1252
1253
  if (!params.row.running) {
1254
+ let completedReentry: { manifest: SandboxControlManifest; evidence: Awaited<ReturnType<typeof prepareCompletedReentry>> } | null = null;
1253
1255
  if (params.reentry === 'completed' && params.workspace?.mode === 'task-bound') {
1256
+ const control = sandboxControlPaths({
1257
+ base: params.config.controlBase, project: params.config.project,
1258
+ container: params.row.name, identity: params.workspace
1259
+ });
1260
+ const manifest = readSandboxControlManifest(control.manifestPath);
1261
+ if (manifest.taskId !== params.workspace.taskId || manifest.branch !== params.branch) {
1262
+ throw new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
1263
+ }
1264
+ completedReentry = { manifest, evidence: await prepareCompletedReentry(manifest) };
1254
1265
  startCompletedSandboxContainer({
1255
1266
  config: params.config,
1256
1267
  engine: params.engine,
@@ -1315,6 +1326,15 @@ export async function ensureSandboxReady(params: EnsureSandboxReadyParams): Prom
1315
1326
  if (final.findings.length > 0) {
1316
1327
  throw new Error(describeFindings(final.findings));
1317
1328
  }
1329
+ if (completedReentry) {
1330
+ const { manifest, evidence } = completedReentry;
1331
+ const current = await prepareCompletedReentry(manifest);
1332
+ if (JSON.stringify(current) !== JSON.stringify(evidence)) {
1333
+ throw new Error('SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID');
1334
+ }
1335
+ publishCompletedReentry(manifest, evidence);
1336
+ await waitForCompletedReentry(manifest, evidence);
1337
+ }
1318
1338
  return { container: params.row.name, path: 'recovered', warnings };
1319
1339
  }
1320
1340
 
@@ -57,7 +57,7 @@ export type SandboxControlSetup = Readonly<{
57
57
  generation: string;
58
58
  }>;
59
59
 
60
- export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine'> & {
60
+ export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine' | 'authorityEvidence'> & {
61
61
  engine: string;
62
62
  }>;
63
63
 
@@ -256,13 +256,19 @@ export function materializeSandboxControl(params: Readonly<{
256
256
 
257
257
  export function finalizeSandboxControlManifest(
258
258
  setup: SandboxControlSetup,
259
- identity: Readonly<{ engine: string; id: string; labels: Readonly<Record<string, string>> }>
259
+ identity: Readonly<{
260
+ engine: string;
261
+ id: string;
262
+ labels: Readonly<Record<string, string>>;
263
+ authorityEvidence: SandboxControlManifest['authorityEvidence'];
264
+ }>
260
265
  ): SandboxControlManifest {
261
266
  if (!identity.engine || !identity.id) throw new Error('SANDBOX_CONTROL_CONTAINER_ID_INVALID');
262
267
  const manifest: SandboxControlManifest = {
263
268
  ...setup.manifestDraft,
264
269
  engine: identity.engine,
265
- containerIdentity: { id: identity.id, labels: { ...identity.labels } }
270
+ containerIdentity: { id: identity.id, labels: { ...identity.labels } },
271
+ authorityEvidence: identity.authorityEvidence
266
272
  };
267
273
  const temporary = `${setup.manifestPath}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
268
274
  try {
@@ -436,7 +436,10 @@ function verifyWorktreePermit(
436
436
  ? inspectRecoveredWorktree(permit.snapshot.worktree, permit.snapshot.recovery, options)
437
437
  : inspectWorktree(permit.snapshot.worktree, options);
438
438
  if (current.status === 'failed') throw new Error(`Unable to verify worktree permit: ${current.message}`);
439
- if (current.snapshot.identity !== permit.snapshot.identity || (permit.mode === 'clean' && current.status !== 'clean')) {
439
+ const sameTarget = current.snapshot.worktree === permit.snapshot.worktree
440
+ && current.snapshot.branch === permit.snapshot.branch;
441
+ if (!sameTarget || (permit.mode === 'clean'
442
+ && (current.snapshot.identity !== permit.snapshot.identity || current.status !== 'clean'))) {
440
443
  throw new Error(`Worktree changed after authorization: ${permit.snapshot.worktree}`);
441
444
  }
442
445
  return current.snapshot;
@@ -1,5 +1,8 @@
1
1
  import { runDaemon } from './daemon.ts';
2
2
  import { start, stop, status, logs } from './process-control.ts';
3
+ import { PUBLIC_CLI_ROUTE_SELECTORS } from '../internal/cli-route-inventory.ts';
4
+
5
+ const SERVER_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS.server;
3
6
 
4
7
  const USAGE = `Usage: ai server <command> [options]
5
8
 
@@ -25,6 +28,13 @@ export async function runServer(args: string[]): Promise<void> {
25
28
  return;
26
29
  }
27
30
 
31
+ if (!SERVER_OPERATIONS.includes(subcommand as typeof SERVER_OPERATIONS[number])) {
32
+ process.stderr.write(`Unknown server command: ${subcommand}\n`);
33
+ process.stdout.write(`${USAGE}\n`);
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+
28
38
  switch (subcommand) {
29
39
  case 'start':
30
40
  await start({ foreground: rest.includes('--foreground') });
@@ -10,21 +10,11 @@ import { extractSection, findSectionHeading } from './sections.ts';
10
10
  import { receiptForOutput, sha256File } from './artifact-receipts.ts';
11
11
  import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.ts';
12
12
  import type { InvalidationDocument } from './invalidation.ts';
13
+ import { validateQualificationAudit } from './qualification-audit.ts';
14
+ import { ARTIFACT_FAMILY_CATALOG } from './artifact-schema.ts';
15
+ import type { ArtifactFamily, ArtifactFamilySpec } from './artifact-schema.ts';
13
16
 
14
- const artifactFamilyCatalog = [
15
- { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
16
- { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
17
- { family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
18
- { family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
19
- { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
20
- { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
21
- { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
22
- { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
23
- { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
24
- ] as const;
25
-
26
- type ArtifactFamily = (typeof artifactFamilyCatalog)[number]['family'];
27
- type ArtifactFamilySpec = (typeof artifactFamilyCatalog)[number];
17
+ const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
28
18
  type ArtifactIdentity = {
29
19
  family: ArtifactFamily;
30
20
  round: number;
@@ -340,6 +330,7 @@ const OPTIONAL_CONTEXT: Partial<Record<ArtifactFamily, { family: ArtifactFamily;
340
330
  'manual-validation': { family: 'review-code' },
341
331
  'validation-run': { family: 'review-code' }
342
332
  };
333
+ const QUALIFICATION_RECOVERY_OPTIONAL_CONTEXT_CONSUMERS = new Set<ArtifactFamily>(['analysis', 'plan']);
343
334
 
344
335
  function resolveArtifactContext(taskRef: string, family: string, options: InspectOptions = {}): ArtifactContextResult {
345
336
  const inventory = inspectTaskArtifacts(taskRef, family, options);
@@ -354,6 +345,8 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
354
345
  if (input.status === 'failed' || !input.latest) {
355
346
  return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
356
347
  }
348
+ const qualificationError = qualificationErrorForArtifact(input.latest);
349
+ if (qualificationError) return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
357
350
  inputs.push(input.latest);
358
351
  }
359
352
  const optional = OPTIONAL_CONTEXT[inventory.family as ArtifactFamily];
@@ -363,12 +356,27 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
363
356
  if (optional.requireReference && !context.reviewedInput) {
364
357
  return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: `${context.latest.name} has no valid reviewed input` } };
365
358
  }
359
+ const qualificationError = qualificationErrorForArtifact(context.latest);
360
+ if (qualificationError && !QUALIFICATION_RECOVERY_OPTIONAL_CONTEXT_CONSUMERS.has(inventory.family as ArtifactFamily)) {
361
+ return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_REFERENCE_INVALID', message: qualificationError } };
362
+ }
366
363
  inputs.push(context.latest);
367
364
  }
368
365
  }
369
366
  return { ...inventory, inputs, codeMode: null };
370
367
  }
371
368
 
369
+ function qualificationErrorForArtifact(artifact: ArtifactIdentity): string | null {
370
+ try {
371
+ const taskContent = fs.readFileSync(path.join(path.dirname(artifact.path), 'task.md'), 'utf8');
372
+ const artifactContent = fs.readFileSync(artifact.path, 'utf8');
373
+ const result = validateQualificationAudit(taskContent, artifactContent);
374
+ return result.ok ? null : `${result.code}: ${result.message}`;
375
+ } catch (error) {
376
+ return error instanceof Error ? error.message : String(error);
377
+ }
378
+ }
379
+
372
380
  function resolveCodeContext(inventory: ArtifactInventoryResult, options: InspectOptions): ArtifactContextResult {
373
381
  const taskRef = inventory.requestRef;
374
382
  const plan = inspectTaskArtifacts(taskRef, 'plan', options);
@@ -379,6 +387,16 @@ function resolveCodeContext(inventory: ArtifactInventoryResult, options: Inspect
379
387
  const codeMax = latestCode?.round ?? 0;
380
388
  const reviewMax = reviewCode.latest?.round ?? 0;
381
389
  const inputs = [plan.latest];
390
+ const planQualificationError = qualificationErrorForArtifact(plan.latest);
391
+ if (planQualificationError) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', planQualificationError);
392
+ if (latestCode) {
393
+ const qualificationError = qualificationErrorForArtifact(latestCode);
394
+ if (qualificationError) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, latestCode.name);
395
+ }
396
+ if (reviewCode.latest) {
397
+ const qualificationError = qualificationErrorForArtifact(reviewCode.latest);
398
+ if (qualificationError) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, reviewCode.latest.name);
399
+ }
382
400
  if (!latestCode) {
383
401
  if (!reviewPlan.latest || reviewPlan.reviewedInput?.name !== plan.latest.name) {
384
402
  return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest plan '${plan.latest.name}' requires a matching approved review-plan`);
@@ -398,6 +416,10 @@ function resolveCodeContext(inventory: ArtifactInventoryResult, options: Inspect
398
416
  if (reviewPlan.latest && !reviewPlan.reviewedInput) {
399
417
  return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `${reviewPlan.latest.name} has no valid reviewed input`, reviewPlan.latest.name);
400
418
  }
419
+ if (reviewPlan.latest) {
420
+ const qualificationError = qualificationErrorForArtifact(reviewPlan.latest);
421
+ if (qualificationError) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', qualificationError, reviewPlan.latest.name);
422
+ }
401
423
  if (reviewPlan.latest && reviewPlan.reviewedInput?.name === plan.latest.name) {
402
424
  const verdict = parseVerdict(reviewPlan.latest.path);
403
425
  if (!verdict.ok) return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, reviewPlan.latest.name);
@@ -409,8 +431,8 @@ function resolveCodeContext(inventory: ArtifactInventoryResult, options: Inspect
409
431
  `Latest ${reviewPlan.latest.name} approves plan content not captured by the latest code input receipt. Entering replan-driven init.`);
410
432
  }
411
433
  }
412
- if (reviewMax < codeMax) {
413
- const expected = artifactName('review-code', codeMax);
434
+ if (!reviewCode.latest || reviewCode.reviewedInput?.name !== latestCode.name) {
435
+ const expected = reviewCode.next?.name ?? artifactName('review-code', reviewMax + 1);
414
436
  return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `${expected} is required before another code round`, expected);
415
437
  }
416
438
  const review = reviewCode.latest;