@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,148 @@
1
+ import fs from 'node:fs';
2
+ import { CONSTRAINT_COLUMNS, CANDIDATE_COLUMNS, parseTaskQualification } from './qualification-audit.js';
3
+ import { resolveTaskRef } from './resolve-ref.js';
4
+ import { captureTaskWriteMetadata, writeTask } from './write.js';
5
+ function failed(code, message, taskId = null) {
6
+ return { status: 'failed', changed: false, taskId, operations: [], error: { code, message } };
7
+ }
8
+ function validSingleLine(value) {
9
+ return typeof value === 'string' && Boolean(value.trim()) && !/[\r\n]/.test(value);
10
+ }
11
+ function assertProposalKeys(value, keys, label) {
12
+ if (!value || typeof value !== 'object' || Array.isArray(value))
13
+ throw new Error(`${label} must be an object`);
14
+ const record = value;
15
+ const unexpected = Object.keys(record).find((key) => !keys.includes(key));
16
+ if (unexpected)
17
+ throw new Error(`${label} does not accept '${unexpected}'`);
18
+ return record;
19
+ }
20
+ function parseConstraintProposal(value) {
21
+ const row = assertProposalKeys(value, ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'derivedFrom', 'approvalEvidence'], 'constraint proposal');
22
+ for (const key of ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'approvalEvidence']) {
23
+ if (typeof row[key] !== 'string' || /[\r\n]/.test(row[key]))
24
+ throw new Error(`constraint proposal '${key}' must be a single-line string`);
25
+ }
26
+ if (!/^C-[1-9]\d*$/.test(row.constraintId))
27
+ throw new Error(`constraint proposal id '${row.constraintId}' is invalid`);
28
+ if (row.status === 'confirmed' || !['derived', 'assumption', 'open', 'conflicted', 'superseded'].includes(row.status))
29
+ throw new Error('proposal cannot write confirmed constraint status');
30
+ if (row.approvalEvidence !== '')
31
+ throw new Error('proposal cannot write approval_evidence');
32
+ if (!Array.isArray(row.derivedFrom) || row.derivedFrom.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item)))
33
+ throw new Error('constraint proposal derivedFrom is invalid');
34
+ return {
35
+ constraintId: row.constraintId, statement: row.statement, status: row.status,
36
+ authority: row.authority, source: row.source, evidence: row.evidence,
37
+ derivedFrom: [...row.derivedFrom], approvalEvidence: ''
38
+ };
39
+ }
40
+ function parseCandidateProposal(value) {
41
+ const row = assertProposalKeys(value, ['candidateId', 'statement', 'status', 'constraintIds', 'impact', 'evidence'], 'candidate proposal');
42
+ for (const key of ['candidateId', 'statement', 'status', 'impact', 'evidence'])
43
+ if (!validSingleLine(row[key]))
44
+ throw new Error(`candidate proposal '${key}' must be a non-empty single-line string`);
45
+ if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(row.candidateId))
46
+ throw new Error(`candidate proposal id '${row.candidateId}' is invalid`);
47
+ if (row.status !== 'pending')
48
+ throw new Error('proposal can only write pending candidate status');
49
+ if (!Array.isArray(row.constraintIds) || row.constraintIds.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item)))
50
+ throw new Error('candidate proposal constraintIds is invalid');
51
+ return row;
52
+ }
53
+ function proposalValues(request) {
54
+ if (!/^[a-f0-9]{64}$/i.test(request.expectedTaskInputDigest))
55
+ throw new Error('expectedTaskInputDigest must be a sha256 digest');
56
+ if (Object.keys(request).some((key) => !['taskRef', 'expectedTaskInputDigest', 'constraints', 'candidates', 'dryRun'].includes(key)))
57
+ throw new Error('proposal contains an unsupported field');
58
+ if (!Array.isArray(request.constraints) && request.constraints !== undefined)
59
+ throw new Error('constraints must be an array');
60
+ if (!Array.isArray(request.candidates) && request.candidates !== undefined)
61
+ throw new Error('candidates must be an array');
62
+ return {
63
+ constraints: (request.constraints ?? []).map(parseConstraintProposal),
64
+ candidates: (request.candidates ?? []).map(parseCandidateProposal)
65
+ };
66
+ }
67
+ function applyQualificationProposal(request, options = {}) {
68
+ if (!request || typeof request !== 'object' || !validSingleLine(request.taskRef))
69
+ return failed('QUALIFICATION_PROPOSAL_INVALID', 'taskRef is required');
70
+ let values;
71
+ try {
72
+ values = proposalValues(request);
73
+ }
74
+ catch (error) {
75
+ return failed('QUALIFICATION_PROPOSAL_INVALID', error instanceof Error ? error.message : String(error));
76
+ }
77
+ if (values.constraints.length === 0 && values.candidates.length === 0)
78
+ return failed('QUALIFICATION_PROPOSAL_INVALID', 'proposal must contain a constraint or candidate');
79
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
80
+ if (!resolved.ok)
81
+ return failed(resolved.code, resolved.message, resolved.taskId);
82
+ if (resolved.state !== 'active')
83
+ return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
84
+ let content;
85
+ try {
86
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
87
+ }
88
+ catch (error) {
89
+ return failed('TASK_READ_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
90
+ }
91
+ const parsed = parseTaskQualification(content);
92
+ if (!parsed.ok)
93
+ return failed(parsed.code, parsed.message, resolved.taskId);
94
+ if (!parsed.qualification.present)
95
+ return failed('QUALIFICATION_TASK_CONTRACT_MISSING', 'task qualification contract is missing', resolved.taskId);
96
+ const currentConstraintMap = new Map(parsed.qualification.constraints.map((row) => [row.constraintId, row]));
97
+ const currentCandidateMap = new Map(parsed.qualification.candidates.map((row) => [row.candidateId, row]));
98
+ const proposalAlreadyApplied = values.constraints.every((row) => {
99
+ const current = currentConstraintMap.get(row.constraintId);
100
+ return current && current.statement === row.statement && current.status === row.status && current.authority === row.authority
101
+ && current.source === row.source && current.evidence === row.evidence && current.derivedFrom.join(',') === row.derivedFrom.join(',') && current.approvalEvidence === '';
102
+ }) && values.candidates.every((row) => {
103
+ const current = currentCandidateMap.get(row.candidateId);
104
+ return current && current.statement === row.statement && current.status === row.status && current.constraintIds.join(',') === row.constraintIds.join(',')
105
+ && current.impact === row.impact && current.evidence === row.evidence;
106
+ });
107
+ if (parsed.qualification.taskInputDigest !== request.expectedTaskInputDigest) {
108
+ return proposalAlreadyApplied
109
+ ? { status: 'no-op', changed: false, taskId: resolved.taskId, operations: [], error: null }
110
+ : failed('QUALIFICATION_DIGEST_CONFLICT', 'task qualification input changed before proposal was applied', resolved.taskId);
111
+ }
112
+ for (const row of values.constraints) {
113
+ const current = currentConstraintMap.get(row.constraintId);
114
+ if (current?.status === 'confirmed')
115
+ return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `confirmed constraint '${row.constraintId}' must be superseded or revoked through task-qualification`, resolved.taskId);
116
+ }
117
+ const knownConstraintIds = new Set([...parsed.qualification.constraints.map((row) => row.constraintId), ...values.constraints.map((row) => row.constraintId)]);
118
+ for (const row of values.constraints)
119
+ for (const parent of row.derivedFrom)
120
+ if (!knownConstraintIds.has(parent))
121
+ return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `constraint proposal '${row.constraintId}' references unknown '${parent}'`, resolved.taskId);
122
+ for (const row of values.candidates)
123
+ for (const id of row.constraintIds)
124
+ if (!knownConstraintIds.has(id))
125
+ return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `candidate proposal '${row.candidateId}' references unknown '${id}'`, resolved.taskId);
126
+ const mutations = [];
127
+ for (const row of values.constraints) {
128
+ mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: CONSTRAINT_COLUMNS, keyColumn: 'constraint_id', key: row.constraintId, values: { statement: row.statement, status: row.status, authority: row.authority, source: row.source, evidence: row.evidence, derived_from: row.derivedFrom.join(','), approval_evidence: '' } });
129
+ }
130
+ for (const row of values.candidates) {
131
+ mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['候选与否决方案', 'Candidate and Rejected Options', 'Candidates and Rejected Options', 'Candidates and Rejected Alternatives'], columns: CANDIDATE_COLUMNS, keyColumn: 'candidate_id', key: row.candidateId, values: { statement: row.statement, status: row.status, constraint_ids: row.constraintIds.join(','), impact: row.impact, evidence: row.evidence } });
132
+ }
133
+ // Only the submitted rows are mutated; no actor, confirmation, or QCR fields
134
+ // can be smuggled into this internal proposal operation.
135
+ let metadata;
136
+ try {
137
+ metadata = (options.metadataProvider ?? captureTaskWriteMetadata)();
138
+ }
139
+ catch (error) {
140
+ return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
141
+ }
142
+ const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
143
+ if (result.status === 'failed')
144
+ return failed(result.error.code, result.error.message, result.taskId);
145
+ return { status: result.status, changed: result.changed, taskId: result.taskId, operations: result.operations, error: null };
146
+ }
147
+ export { applyQualificationProposal };
148
+ //# sourceMappingURL=qualification-intents.js.map
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { scanVisibleMarkdown } from './decision-details.js';
3
4
  const SUMMARY_HEADINGS = new Set(['审查摘要', 'Review Summary']);
4
5
  const PLACEHOLDERS = [
5
6
  '{unresolved-blockers}',
@@ -36,42 +37,40 @@ function normalizeVerdict(raw) {
36
37
  return '';
37
38
  }
38
39
  function extractSection(content, names) {
39
- const lines = content.split(/\r?\n/);
40
40
  const nameSet = new Set(names);
41
- const start = lines.findIndex((line) => {
42
- const match = line.trim().match(/^##\s+(.+?)\s*$/);
43
- return match ? nameSet.has(match[1]) : false;
44
- });
45
- if (start === -1)
41
+ const scanned = scanVisibleMarkdown(content);
42
+ const heading = scanned.headings.find((item) => item.level === 2 && nameSet.has(item.text));
43
+ if (!heading)
46
44
  return '';
47
- const sectionLines = [];
48
- for (let index = start + 1; index < lines.length; index += 1) {
49
- if (/^##\s+/.test(lines[index]))
50
- break;
51
- sectionLines.push(lines[index]);
52
- }
53
- return sectionLines.join('\n');
45
+ const next = scanned.headings.find((item) => item.start > heading.start && item.level <= 2);
46
+ return content.slice(heading.end, next?.start ?? content.length);
54
47
  }
55
48
  function summaryBounds(content) {
56
- const headings = [...content.matchAll(/^##\s+(.+?)\s*$/gm)]
57
- .map((match) => ({ index: match.index, end: match.index + match[0].length, name: match[1] }));
58
- const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
49
+ const scanned = scanVisibleMarkdown(content);
50
+ const summaries = scanned.headings.filter((heading) => heading.level === 2 && SUMMARY_HEADINGS.has(heading.text));
59
51
  if (summaries.length !== 1)
60
52
  return null;
61
53
  const summary = summaries[0];
62
- const next = headings.find((heading) => heading.index > summary.index);
63
- return { start: summary.end, end: next?.index ?? content.length };
54
+ const next = scanned.headings.find((heading) => heading.start > summary.start && heading.level <= 2);
55
+ return { start: summary.end, end: next?.start ?? content.length };
64
56
  }
65
57
  function uniqueLine(content, bounds, pattern) {
66
- const section = content.slice(bounds.start, bounds.end);
67
- const matches = [...section.matchAll(pattern)];
58
+ const matches = [];
59
+ for (const line of scanVisibleMarkdown(content).lines) {
60
+ if (line.start < bounds.start || line.start >= bounds.end)
61
+ continue;
62
+ pattern.lastIndex = 0;
63
+ const match = pattern.exec(line.text);
64
+ if (match)
65
+ matches.push({ value: match[1], index: line.start + match.index, length: match[0].length });
66
+ }
68
67
  if (matches.length !== 1)
69
68
  return null;
70
69
  const match = matches[0];
71
70
  return {
72
- value: match[1],
73
- start: bounds.start + match.index,
74
- end: bounds.start + match.index + match[0].length
71
+ value: match.value,
72
+ start: match.index,
73
+ end: match.index + match.length
75
74
  };
76
75
  }
77
76
  function parseCounts(value) {
@@ -10,6 +10,9 @@ import { resolveTaskRef } from './resolve-ref.js';
10
10
  import { validateLifecycleExecution } from './lifecycle-execution.js';
11
11
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
12
12
  import { allowsManualOverride } from './guard-override.js';
13
+ import { getArtifactSchema } from './artifact-schema.js';
14
+ import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
15
+ import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
13
16
  const STAGES = {
14
17
  analysis: { family: 'review-analysis', action: 'Review Analysis' },
15
18
  plan: { family: 'review-plan', action: 'Review Plan' },
@@ -22,7 +25,7 @@ const DEFAULT_FILE_SYSTEM = {
22
25
  renameSync: (from, to) => fs.renameSync(from, to),
23
26
  unlinkSync: (file) => fs.unlinkSync(file)
24
27
  };
25
- function failed(request, code, message, taskId = null, stageStatus = null) {
28
+ function failed(request, code, message, taskId = null, stageStatus = null, extra = {}) {
26
29
  return {
27
30
  status: 'failed',
28
31
  changed: false,
@@ -32,8 +35,13 @@ function failed(request, code, message, taskId = null, stageStatus = null) {
32
35
  stage: request.stage,
33
36
  artifact: request.artifact,
34
37
  stageStatus,
38
+ artifactSha256: null,
39
+ semanticDigest: null,
40
+ repairable: false,
41
+ operation: null,
35
42
  operations: [],
36
- error: { code, message }
43
+ error: { code, message },
44
+ ...extra
37
45
  };
38
46
  }
39
47
  function openReviewRound(content, action, round) {
@@ -86,6 +94,73 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
86
94
  catch (error) {
87
95
  return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
88
96
  }
97
+ const schema = getArtifactSchema(spec.family);
98
+ const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
99
+ const artifactSha256 = sha256Content(artifactContent);
100
+ const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
101
+ let repairIntent;
102
+ try {
103
+ repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
104
+ }
105
+ catch (error) {
106
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
107
+ artifactSha256,
108
+ semanticDigest: artifactSemanticDigest
109
+ });
110
+ }
111
+ if (structure && !structure.ok) {
112
+ const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
113
+ if (repairable) {
114
+ if (repairIntent && (repairIntent.state !== 'awaiting-repair' ||
115
+ repairIntent.artifactSha256 !== artifactSha256 ||
116
+ repairIntent.semanticDigest !== structure.semanticDigest)) {
117
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
118
+ artifactSha256,
119
+ semanticDigest: structure.semanticDigest
120
+ });
121
+ }
122
+ try {
123
+ if (!repairIntent) {
124
+ writeArtifactRepairIntent(resolved.repoRoot, {
125
+ version: 1,
126
+ taskId: resolved.taskId,
127
+ family: spec.family,
128
+ artifact: request.artifact,
129
+ state: 'awaiting-repair',
130
+ baselineSemanticDigest: structure.semanticDigest,
131
+ artifactSha256,
132
+ semanticDigest: structure.semanticDigest
133
+ });
134
+ }
135
+ }
136
+ catch (error) {
137
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
138
+ artifactSha256,
139
+ semanticDigest: structure.semanticDigest,
140
+ operation: structure.repair
141
+ });
142
+ }
143
+ }
144
+ return failed(request, 'REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), resolved.taskId, null, {
145
+ artifactSha256,
146
+ semanticDigest: structure.semanticDigest,
147
+ repairable,
148
+ operation: structure.repair
149
+ });
150
+ }
151
+ if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
152
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
153
+ artifactSha256,
154
+ semanticDigest: artifactSemanticDigest
155
+ });
156
+ }
157
+ if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
158
+ && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
159
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
160
+ artifactSha256,
161
+ semanticDigest: artifactSemanticDigest
162
+ });
163
+ }
89
164
  if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
90
165
  return failed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
91
166
  }
@@ -127,8 +202,27 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
127
202
  return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
128
203
  }
129
204
  if (!transformed.changed) {
205
+ if (repairIntent?.state === 'awaiting-repair') {
206
+ try {
207
+ writeArtifactRepairIntent(resolved.repoRoot, {
208
+ ...repairIntent,
209
+ state: 'passed',
210
+ artifactSha256,
211
+ semanticDigest: artifactSemanticDigest
212
+ });
213
+ }
214
+ catch (error) {
215
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
216
+ artifactSha256,
217
+ semanticDigest: artifactSemanticDigest
218
+ });
219
+ }
220
+ }
130
221
  return {
131
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
222
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
223
+ artifactSha256,
224
+ semanticDigest: artifactSemanticDigest
225
+ }),
132
226
  status: 'no-op',
133
227
  error: null
134
228
  };
@@ -136,7 +230,10 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
136
230
  const operations = [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }];
137
231
  if (request.dryRun) {
138
232
  return {
139
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
233
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
234
+ artifactSha256: sha256Content(transformed.content),
235
+ semanticDigest: canonicalSemanticDigest(transformed.content)
236
+ }),
140
237
  status: 'planned',
141
238
  changed: true,
142
239
  operations,
@@ -164,6 +261,22 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
164
261
  cleanupTemp(fileSystem, tempPath);
165
262
  return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
166
263
  }
264
+ if (repairIntent?.state === 'awaiting-repair') {
265
+ try {
266
+ writeArtifactRepairIntent(resolved.repoRoot, {
267
+ ...repairIntent,
268
+ state: 'passed',
269
+ artifactSha256: sha256Content(transformed.content),
270
+ semanticDigest: canonicalSemanticDigest(transformed.content)
271
+ });
272
+ }
273
+ catch (error) {
274
+ return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
275
+ artifactSha256: sha256Content(transformed.content),
276
+ semanticDigest: canonicalSemanticDigest(transformed.content)
277
+ });
278
+ }
279
+ }
167
280
  return {
168
281
  status: 'applied',
169
282
  changed: true,
@@ -173,6 +286,10 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
173
286
  stage,
174
287
  artifact: request.artifact,
175
288
  stageStatus,
289
+ artifactSha256: sha256Content(transformed.content),
290
+ semanticDigest: canonicalSemanticDigest(transformed.content),
291
+ repairable: false,
292
+ operation: null,
176
293
  operations,
177
294
  error: null
178
295
  };
@@ -99,17 +99,19 @@ function matchingSections(content, aliases) {
99
99
  const allowed = new Set(aliases);
100
100
  return linesOf(content)
101
101
  .map((line, index, lines) => {
102
- const match = /^##\s+(.+?)\s*$/.exec(line.text);
103
- if (!match?.[1] || !allowed.has(match[1]))
102
+ const match = /^(#{2,3})\s+(.+?)\s*$/.exec(line.text);
103
+ if (!match?.[2] || !allowed.has(match[2]))
104
104
  return null;
105
+ const level = match[1].length;
105
106
  let end = content.length;
106
107
  for (let i = index + 1; i < lines.length; i += 1) {
107
- if (/^##\s+/.test(lines[i].text)) {
108
+ const nextHeading = /^(#{2,3})\s+/.exec(lines[i].text);
109
+ if (nextHeading && nextHeading[1].length <= level) {
108
110
  end = lines[i].start;
109
111
  break;
110
112
  }
111
113
  }
112
- return { heading: match[1], line, bodyStart: line.end, end };
114
+ return { heading: match[2], line, bodyStart: line.end, end };
113
115
  })
114
116
  .filter((match) => match !== null);
115
117
  }
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { getArtifactSchema } from './artifact-schema.js';
3
4
  function loadVerificationConfig(repositoryRoot, skillName) {
4
5
  const verifyPath = path.join(repositoryRoot, '.agents', 'skills', skillName, 'config', 'verify.json');
5
6
  if (!fs.existsSync(verifyPath))
@@ -8,6 +9,16 @@ function loadVerificationConfig(repositoryRoot, skillName) {
8
9
  if (!parsed.checks || typeof parsed.checks !== 'object' || Array.isArray(parsed.checks)) {
9
10
  throw new Error(`config/verify.json has invalid checks for skill '${skillName}'`);
10
11
  }
12
+ const artifact = parsed.checks.artifact;
13
+ if (artifact && typeof artifact === 'object' && !Array.isArray(artifact)) {
14
+ const schema = artifact.schema;
15
+ if (schema !== undefined && (typeof schema !== 'string' || !getArtifactSchema(schema))) {
16
+ throw new Error(`config/verify.json has unknown artifact schema '${String(schema)}' for skill '${skillName}'`);
17
+ }
18
+ if (schema !== undefined && 'required_patterns' in artifact) {
19
+ throw new Error(`config/verify.json must not duplicate required_patterns for schema '${String(schema)}' in skill '${skillName}'`);
20
+ }
21
+ }
11
22
  return { ...(parsed.skill ? { skill: parsed.skill } : {}), checks: parsed.checks };
12
23
  }
13
24
  export { loadVerificationConfig };
@@ -22,6 +22,9 @@ import { OrchestrationStateError, readRun } from "./orchestration.js";
22
22
  import { resolveDeliveryTarget } from "./delivery-target.js";
23
23
  import { readPrDeliveryFact } from "./pr-delivery-fact.js";
24
24
  import { validateLocalArtifact } from "./local-artifact-finalization.js";
25
+ import { validateQualificationAudit } from "./qualification-audit.js";
26
+ import { getArtifactSchema } from "./artifact-schema.js";
27
+ import { inspectArtifactContract } from "./artifact-operations.js";
25
28
  const TASK_ENUMS = {
26
29
  type: ["feature", "bugfix", "refactor", "docs", "chore"],
27
30
  workflow: ["feature-development", "bug-fix", "refactoring"],
@@ -520,21 +523,57 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }) {
520
523
  return failResult("artifact", `Artifact is empty: ${path.basename(artifactPath)}`);
521
524
  }
522
525
  const content = fs.readFileSync(artifactPath, "utf8");
523
- const requiredSections = config.required_sections || [];
526
+ let taskContent = "";
527
+ const taskPath = path.join(taskDir, "task.md");
528
+ if (fs.existsSync(taskPath)) {
529
+ try {
530
+ taskContent = fs.readFileSync(taskPath, "utf8");
531
+ }
532
+ catch (error) {
533
+ return failResult("artifact", `Cannot read task qualification contract: ${error instanceof Error ? error.message : String(error)}`);
534
+ }
535
+ }
536
+ if (taskContent && /^review-(?:analysis|plan|code)$/.test(skillName)) {
537
+ const qualification = validateQualificationAudit(taskContent, content, { artifact: path.basename(artifactPath) });
538
+ if (!qualification.ok)
539
+ return failResult("artifact", `${path.basename(artifactPath)} has qualification audit errors: ${qualification.code}: ${qualification.message}`);
540
+ }
541
+ const schemaFamily = skillName === "analyze-task"
542
+ ? "analysis"
543
+ : skillName === "review-analysis"
544
+ ? "review-analysis"
545
+ : skillName === "plan-task"
546
+ ? "plan"
547
+ : skillName === "review-plan"
548
+ ? "review-plan"
549
+ : skillName === "code-task"
550
+ ? "code"
551
+ : skillName === "review-code" ? "review-code" : null;
552
+ const schema = schemaFamily ? getArtifactSchema(schemaFamily) : null;
524
553
  const localFamily = skillName === "analyze-task"
525
554
  ? "analysis"
526
555
  : skillName === "plan-task" ? "plan" : skillName === "code-task" ? "code" : null;
556
+ if (schema) {
557
+ const structure = inspectArtifactContract(content, schema);
558
+ if (!structure.ok && !localFamily) {
559
+ return failResult("artifact", `${path.basename(artifactPath)} has shared structural errors: ${structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}`);
560
+ }
561
+ }
527
562
  if (localFamily) {
528
563
  const local = validateLocalArtifact(content, {
529
564
  family: localFamily,
530
- requiredSections: requiredSections.filter((section) => typeof section === "string"),
531
- requiredPatterns: (config.required_patterns || []).filter((pattern) => typeof pattern === "string")
565
+ ...(taskContent ? { taskContent } : {}),
566
+ artifact: path.basename(artifactPath)
532
567
  });
533
568
  if (!local.ok) {
534
569
  return failResult("artifact", `${path.basename(artifactPath)} has local structural errors: ${local.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}`);
535
570
  }
536
- return passResult("artifact", `${path.basename(artifactPath)} passed (${requiredSections.length} sections)`);
571
+ return passResult("artifact", `${path.basename(artifactPath)} passed (${schema?.sections.length ?? 0} sections)`);
537
572
  }
573
+ if (schema) {
574
+ return passResult("artifact", `${path.basename(artifactPath)} passed (${schema.sections.length} sections)`);
575
+ }
576
+ const requiredSections = config.required_sections || [];
538
577
  const missingSections = requiredSections.filter((section) => !new RegExp(`^##\\s+${escapeRegExp(section)}\\s*$`, "m").test(content));
539
578
  if (missingSections.length > 0) {
540
579
  return failResult("artifact", `${path.basename(artifactPath)} is missing sections: ${missingSections.join(", ")}`);
@@ -686,6 +725,7 @@ function checkActivityLog({ taskDir, config }) {
686
725
  let previousTimestamp = "";
687
726
  let latestAction = "";
688
727
  let latestTimestamp = "";
728
+ const doneActions = [];
689
729
  for (const entry of entries) {
690
730
  const match = entry.match(ACTIVITY_LOG_PATTERN);
691
731
  if (!match) {
@@ -702,6 +742,16 @@ function checkActivityLog({ taskDir, config }) {
702
742
  if (!ACTIVITY_LOG_STARTED_RE.test(action)) {
703
743
  latestTimestamp = timestamp;
704
744
  latestAction = action;
745
+ doneActions.push(action);
746
+ }
747
+ }
748
+ if (config.expected_action_pattern && !new RegExp(config.expected_action_pattern).test(latestAction)) {
749
+ const expected = new RegExp(config.expected_action_pattern);
750
+ let index = doneActions.length - 1;
751
+ while (index >= 0 && doneActions[index] === 'Commit' && !expected.test(doneActions[index]))
752
+ index -= 1;
753
+ if (index >= 0 && expected.test(doneActions[index])) {
754
+ latestAction = doneActions[index];
705
755
  }
706
756
  }
707
757
  if (config.expected_action_pattern && !new RegExp(config.expected_action_pattern).test(latestAction)) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.9.13",
4
- "sourceInputHash": "5e4d3ad1c1e7e3c909489ef59ff318ea9cffe5f538ae37d83923773946357afe",
5
- "internalExecutableBuildHash": "c8cabf5c6bc776cdcb9621f712172ebda95ff9b12fb6f67213e18bc0159516fd"
3
+ "packageVersion": "0.9.14",
4
+ "sourceInputHash": "a3cf306ad6576904e6ef7d962e51245c6684540bd41358beaebf8209c00f3cfc",
5
+ "internalExecutableBuildHash": "14773c26aea5022af695f2e3ca4a39bed3c87c1eab04347aeded028745d8ded1"
6
6
  }
@@ -11,6 +11,9 @@ import { AGENT_CLIENT_IDS, isAgentClientId } from './agent-clients/types.ts';
11
11
  import type { AgentClientState } from './agent-clients/types.ts';
12
12
  import { closePrompt, multiSelect } from './prompt.ts';
13
13
  import { resolveTemplateDir } from './paths.ts';
14
+ import { PUBLIC_CLI_ROUTE_SELECTORS } from './internal/cli-route-inventory.ts';
15
+
16
+ const AGENT_CLIENT_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS['agent-client'];
14
17
 
15
18
  const USAGE = `Usage: ai agent-client <operation>
16
19
 
@@ -107,6 +110,12 @@ async function cmdAgentClient(args: string[]): Promise<number> {
107
110
  process.stdout.write(USAGE);
108
111
  return 0;
109
112
  }
113
+ if (!AGENT_CLIENT_OPERATIONS.includes(operation as typeof AGENT_CLIENT_OPERATIONS[number])) {
114
+ throw new AgentClientCommandError(
115
+ 'AGENT_CLIENT_OPERATION_INVALID',
116
+ `Unknown operation '${operation}'`
117
+ );
118
+ }
110
119
 
111
120
  if (operation === 'list') {
112
121
  if (args.length !== 1) {
@@ -9,6 +9,7 @@ import { normalizeCustomTUIs } from '../agent-clients/custom-tuis.ts';
9
9
  import { renderNextStepCommands } from '../agent-clients/next-steps.ts';
10
10
  import { getAgentClientModelSelection } from '../agent-clients/registry.ts';
11
11
  import { isAgentClientId } from '../agent-clients/types.ts';
12
+ import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.ts';
12
13
 
13
14
  const USAGE = 'Usage: agent-infra-internal agent-client <next-steps|model-selection> [options]\n';
14
15
 
@@ -35,7 +36,7 @@ function parseArgs(args: string[]): ParsedArgs | null {
35
36
  process.stdout.write(USAGE);
36
37
  return null;
37
38
  }
38
- if (args[0] !== 'next-steps') {
39
+ if (!internalHandlerRoute('agent-client', 'next-steps', args[0] ?? '')) {
39
40
  failure('AGENT_CLIENT_PAYLOAD_INVALID', "operation must be 'next-steps'");
40
41
  return null;
41
42
  }
@@ -84,7 +85,8 @@ function parseArgs(args: string[]): ParsedArgs | null {
84
85
  }
85
86
 
86
87
  function agentClient(args: string[] = []): void {
87
- if (args[0] === 'model-selection') {
88
+ if (!ensureInternalHandlerRoute('agent-client', args)) return;
89
+ if (internalHandlerRoute('agent-client', 'model-selection', args[0] ?? '')) {
88
90
  const values: Record<string, string> = {};
89
91
  const seen = new Set<string>();
90
92
  for (let index = 1; index < args.length; index += 1) {