@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
@@ -35,19 +35,20 @@ import { captureTaskWriteMetadata, writeTask } from './write.ts';
35
35
  import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
36
36
  import { allowsManualOverride } from './guard-override.ts';
37
37
  import {
38
- LOCAL_ARTIFACT_REQUIRED_PATTERNS,
39
- LOCAL_ARTIFACT_REQUIRED_SECTIONS,
40
38
  consumeLocalArtifactFinalizationIntent,
41
39
  readLocalArtifactFinalizationIntent,
42
40
  validateLocalArtifact
43
41
  } from './local-artifact-finalization.ts';
44
42
  import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
45
- import { loadVerificationConfig } from './verification-config.ts';
46
43
  import { buildLifecycleFacts, canStart } from './capabilities.ts';
47
44
  import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
48
45
  import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.ts';
49
46
  import type { InvalidationTargetKind } from './invalidation.ts';
50
47
  import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.ts';
48
+ import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest, validateQualificationAudit } from './qualification-audit.ts';
49
+ import type { QualificationAudit, UpstreamRelation } from './qualification-audit.ts';
50
+ import { getArtifactSchema } from './artifact-schema.ts';
51
+ import { inspectArtifactContract } from './artifact-operations.ts';
51
52
 
52
53
  const eventCatalog = [
53
54
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
@@ -117,30 +118,6 @@ const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[]
117
118
  'validation-run.completed': { required: ['artifact'], optional: ['round'] }
118
119
  };
119
120
 
120
- function localArtifactValidationConfig(repoRoot: string, family: LocalArtifactFamily) {
121
- const fallback = {
122
- requiredSections: LOCAL_ARTIFACT_REQUIRED_SECTIONS[family],
123
- requiredPatterns: LOCAL_ARTIFACT_REQUIRED_PATTERNS
124
- };
125
- const skillName = family === 'analysis' ? 'analyze-task' : family === 'plan' ? 'plan-task' : 'code-task';
126
- try {
127
- const artifact = loadVerificationConfig(repoRoot, skillName).checks.artifact;
128
- if (!artifact || typeof artifact !== 'object' || Array.isArray(artifact)) return fallback;
129
- const sections = artifact.required_sections;
130
- const patterns = artifact.required_patterns;
131
- return {
132
- requiredSections: Array.isArray(sections)
133
- ? sections.filter((value): value is string => typeof value === 'string')
134
- : fallback.requiredSections,
135
- requiredPatterns: Array.isArray(patterns)
136
- ? patterns.filter((value): value is string => typeof value === 'string')
137
- : fallback.requiredPatterns
138
- };
139
- } catch {
140
- return fallback;
141
- }
142
- }
143
-
144
121
  function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | null {
145
122
  if (!eventCatalog.includes(request.event as TaskEventName)) return { code: 'EVENT_UNKNOWN', message: `unknown task event '${request.event}'` };
146
123
  if (!request.taskRef || !request.agent) return { code: 'EVENT_PAYLOAD_INVALID', message: 'taskRef and agent are required' };
@@ -347,6 +324,25 @@ function reviewInputFamily(family: EventFamily): ArtifactFamily {
347
324
  return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
348
325
  }
349
326
 
327
+ function relationForStartedInput(family: EventFamily, input: ArtifactIdentity): UpstreamRelation['relation'] {
328
+ if (family.startsWith('review-')) {
329
+ return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
330
+ }
331
+ const consumer = family === 'analyze' ? 'analysis' : family;
332
+ if ((consumer === 'plan' && input.family === 'analysis') || (consumer === 'code' && input.family === 'plan')) return 'required-input';
333
+ return 'review-context';
334
+ }
335
+
336
+ function qualificationRelationsForStarted(family: EventFamily, inputs: readonly ArtifactIdentity[]): UpstreamRelation[] {
337
+ return inputs.map((input) => ({
338
+ upstreamFamily: input.family as UpstreamRelation['upstreamFamily'],
339
+ upstreamArtifact: input.name,
340
+ upstreamRound: input.round,
341
+ upstreamSha256: sha256File(input.path),
342
+ relation: relationForStartedInput(family, input)
343
+ }));
344
+ }
345
+
350
346
  function lifecycleAction(family: EventFamily): LifecycleAction {
351
347
  return family === 'analyze' ? 'analysis' : family;
352
348
  }
@@ -381,49 +377,118 @@ function invalidationMutationForCompletion(
381
377
  code: ['review-code']
382
378
  };
383
379
  const sourceFamily = family as 'analyze' | 'plan' | 'code';
380
+ const sourceArtifactFamily = sourceFamily === 'analyze' ? 'analysis' : sourceFamily;
384
381
  let receipts: readonly ArtifactReceipt[] = [];
385
382
  try {
386
383
  receipts = parseArtifactReceipts(content).rows;
387
384
  } catch (error) {
388
385
  return { error: error instanceof Error ? error.message : String(error) };
389
386
  }
390
- const targets = downstream[sourceFamily].flatMap((targetFamily: string) => {
391
- const names = fs.readdirSync(taskDir).filter((name) => {
392
- const pattern = targetFamily === 'review-analysis' ? /^review-analysis(?:-r\d+)?\.md$/
393
- : targetFamily === 'review-plan' ? /^review-plan(?:-r\d+)?\.md$/
394
- : targetFamily === 'review-code' ? /^review-code(?:-r\d+)?\.md$/
395
- : new RegExp(`^${targetFamily}(?:-r\\d+)?\\.md$`);
396
- return pattern.test(name);
397
- });
398
- return names.flatMap((name) => {
399
- try {
400
- const stat = fs.lstatSync(path.join(taskDir, name));
401
- if (!stat.isFile() || stat.isSymbolicLink()) return [];
402
- const targetRound = parseArtifactName(name)?.round ?? 1;
403
- const targetSha256 = sha256File(path.join(taskDir, name));
404
- const shapes: Array<{ targetKind: InvalidationTargetKind; targetFamily: string; targetArtifact: string; targetRound: number; targetSha256: string }> = [
405
- { targetKind: 'artifact', targetFamily, targetArtifact: name, targetRound, targetSha256 }
406
- ];
407
- const receipt = receipts.find((candidate) => candidate.output === name);
408
- if (receipt) shapes.push({
409
- targetKind: 'receipt', targetFamily, targetArtifact: name, targetRound,
410
- targetSha256: receipt.inputSha256
411
- });
412
- if (targetFamily.startsWith('review-')) {
413
- shapes.push({ targetKind: 'approval', targetFamily, targetArtifact: name, targetRound, targetSha256 });
414
- }
415
- if (targetFamily === 'review-code') {
416
- shapes.push({ targetKind: 'reviewed-snapshot', targetFamily, targetArtifact: name, targetRound, targetSha256 });
387
+ const inventory = fs.readdirSync(taskDir).flatMap((name) => {
388
+ const identity = parseArtifactName(name);
389
+ if (!identity || !(ARTIFACT_FAMILIES as readonly string[]).includes(identity.family)) return [];
390
+ try {
391
+ const stat = fs.lstatSync(path.join(taskDir, name));
392
+ if (!stat.isFile() || stat.isSymbolicLink()) return [];
393
+ return [{ family: identity.family, name, round: identity.round, sha256: sha256File(path.join(taskDir, name)) }];
394
+ } catch (error) {
395
+ throw new Error(`cannot inspect invalidation artifact '${name}': ${error instanceof Error ? error.message : String(error)}`);
396
+ }
397
+ });
398
+ const taskQualification = parseTaskQualification(content);
399
+ if (!taskQualification.ok) return { error: taskQualification.message };
400
+ const nodeMap = new Map(inventory.map((node) => [`${node.family}/${node.name}`, node]));
401
+ const audits = new Map<string, QualificationAudit>();
402
+ let taskInputChanged = false;
403
+ let graphUsable = taskQualification.qualification.present;
404
+ if (graphUsable) {
405
+ for (const node of inventory) {
406
+ const artifactContent = fs.readFileSync(path.join(taskDir, node.name), 'utf8');
407
+ const parsedAudit = parseQualificationAudit(artifactContent);
408
+ if (!parsedAudit.ok || !parsedAudit.audit.present || !parsedAudit.audit.snapshot) { graphUsable = false; break; }
409
+ audits.set(`${node.family}/${node.name}`, parsedAudit.audit);
410
+ if (parsedAudit.audit.snapshot.taskInputDigest !== taskQualification.qualification.taskInputDigest) taskInputChanged = true;
411
+ if (parsedAudit.audit.snapshot.nonConstraintInputDigest !== taskQualification.qualification.nonConstraintInputDigest) {
412
+ graphUsable = false;
413
+ break;
414
+ }
415
+ if (parsedAudit.audit.snapshot.upstreamArtifactDigest !== upstreamArtifactDigest(parsedAudit.audit.upstreamRelations)) {
416
+ graphUsable = false;
417
+ break;
418
+ }
419
+ for (const relation of parsedAudit.audit.upstreamRelations) {
420
+ const upstream = nodeMap.get(`${relation.upstreamFamily}/${relation.upstreamArtifact}`);
421
+ if (!upstream || upstream.round !== relation.upstreamRound || upstream.sha256 !== relation.upstreamSha256) { graphUsable = false; break; }
422
+ }
423
+ if (!graphUsable) break;
424
+ }
425
+ }
426
+ const selected = new Set<string>();
427
+ if (graphUsable) {
428
+ const changedConstraints = new Set<string>();
429
+ for (const audit of audits.values()) {
430
+ for (const dependency of audit.constraintDependencies) {
431
+ const current = taskQualification.qualification.constraints.find((row) => row.constraintId === dependency.constraintId);
432
+ if (!current || current.digest !== dependency.constraintDigest) changedConstraints.add(dependency.constraintId);
433
+ }
434
+ }
435
+ const currentCandidates = new Map(taskQualification.qualification.candidates.map((candidate) => [candidate.candidateId, candidate]));
436
+ const candidateSnapshotMatches = [...audits.values()].every((audit) => audit.candidateQualifications.length === currentCandidates.size && audit.candidateQualifications.every((row) => {
437
+ const current = currentCandidates.get(row.candidateId);
438
+ return Boolean(current && current.status === row.status && current.impact === row.impact
439
+ && current.evidence === row.evidence && current.constraintIds.join(',') === row.constraintIds.join(','));
440
+ }));
441
+ // A task-input digest also covers candidates. A narrowed invalidation graph
442
+ // is safe only when the old audit rows show that the candidate projection
443
+ // stayed unchanged and the change is explained by at least one constraint.
444
+ if (taskInputChanged && (changedConstraints.size === 0 || !candidateSnapshotMatches)) graphUsable = false;
445
+ for (const node of inventory) {
446
+ const audit = audits.get(`${node.family}/${node.name}`)!;
447
+ if (audit.constraintDependencies.some((dependency) => changedConstraints.has(dependency.constraintId))) selected.add(`${node.family}/${node.name}`);
448
+ if (audit.upstreamRelations.some((relation) => relation.upstreamFamily === sourceArtifactFamily)) selected.add(`${node.family}/${node.name}`);
449
+ }
450
+ // Relation rows point from a consumer to its actual upstream artifact. Walk
451
+ // that reverse edge so review-only seeds and multi-hop consumers receive the
452
+ // same derived targets as ordinary executor seeds.
453
+ let changed = true;
454
+ while (changed) {
455
+ changed = false;
456
+ for (const node of inventory) {
457
+ const key = `${node.family}/${node.name}`;
458
+ const audit = audits.get(key)!;
459
+ if (audit.upstreamRelations.some((relation) => selected.has(`${relation.upstreamFamily}/${relation.upstreamArtifact}`)) && !selected.has(key)) {
460
+ selected.add(key);
461
+ changed = true;
417
462
  }
418
- return shapes.map((targetShape) => ({
419
- ...targetShape,
420
- targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending' as const,
421
- reasonCode: 'upstream-replaced', updatedAt: timestamp
422
- }));
423
- } catch (error) {
424
- throw new Error(`cannot inspect invalidation target '${name}': ${error instanceof Error ? error.message : String(error)}`);
425
463
  }
426
- });
464
+ }
465
+ // A relation cycle cannot establish a safe downstream closure.
466
+ const visiting = new Set<string>();
467
+ const visited = new Set<string>();
468
+ const hasCycle = (key: string): boolean => {
469
+ if (visiting.has(key)) return true;
470
+ if (visited.has(key)) return false;
471
+ visiting.add(key);
472
+ const cycle = audits.get(key)?.upstreamRelations.some((relation) => hasCycle(`${relation.upstreamFamily}/${relation.upstreamArtifact}`)) ?? false;
473
+ visiting.delete(key); visited.add(key);
474
+ return cycle;
475
+ };
476
+ if ([...nodeMap.keys()].some(hasCycle)) graphUsable = false;
477
+ }
478
+ const reasonCode = graphUsable ? 'qualification-changed' : 'upstream-replaced';
479
+ const targetNodes = (graphUsable
480
+ ? inventory.filter((node) => selected.has(`${node.family}/${node.name}`))
481
+ : inventory.filter((node) => downstream[sourceFamily].includes(node.family)))
482
+ .filter((node) => !(node.family === FAMILY[family].artifact && node.name === artifact.name));
483
+ const targets = targetNodes.flatMap((node) => {
484
+ const shapes: Array<{ targetKind: InvalidationTargetKind; targetFamily: string; targetArtifact: string; targetRound: number; targetSha256: string }> = [
485
+ { targetKind: 'artifact', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 }
486
+ ];
487
+ const receipt = receipts.find((candidate) => candidate.output === node.name);
488
+ if (receipt) shapes.push({ targetKind: 'receipt', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: receipt.inputSha256 });
489
+ if (node.family.startsWith('review-')) shapes.push({ targetKind: 'approval', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
490
+ if (node.family === 'review-code') shapes.push({ targetKind: 'reviewed-snapshot', targetFamily: node.family, targetArtifact: node.name, targetRound: node.round, targetSha256: node.sha256 });
491
+ return shapes.map((targetShape) => ({ ...targetShape, targetId: targetIdFor('pending', targetShape), operationId: 'pending', status: 'pending' as const, reasonCode, updatedAt: timestamp }));
427
492
  });
428
493
  if (targets.length === 0) return { mutation: null };
429
494
  const source = {
@@ -598,6 +663,30 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
598
663
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
599
664
  if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
600
665
  completedArtifact = validated.artifact;
666
+ if (eventIdentity.family.startsWith('review-')) {
667
+ let reviewContent: string;
668
+ try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
669
+ catch (error) {
670
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read qualification audit from ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
671
+ }
672
+ const schema = getArtifactSchema(eventIdentity.family);
673
+ const structure = schema ? inspectArtifactContract(reviewContent, schema) : null;
674
+ if (structure && !structure.ok) {
675
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `shared artifact structure invalid: ${structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
676
+ }
677
+ const expected = expectedQualificationRelations(content, eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code');
678
+ if (!expected.ok) {
679
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${expected.code}: ${expected.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
680
+ }
681
+ const qualification = validateQualificationAudit(content, reviewContent, {
682
+ family: eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code',
683
+ artifact: completedArtifact.name,
684
+ expectedUpstreamRelations: expected.relations
685
+ });
686
+ if (!qualification.ok) {
687
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${qualification.code}: ${qualification.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
688
+ }
689
+ }
601
690
  if (eventIdentity.family === 'analyze' || eventIdentity.family === 'plan' || eventIdentity.family === 'code') {
602
691
  const localFamily = eventIdentity.family === 'analyze'
603
692
  ? 'analysis'
@@ -607,11 +696,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
607
696
  catch (error) {
608
697
  return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
609
698
  }
610
- const validationConfig = localArtifactValidationConfig(resolved.repoRoot, localFamily);
611
699
  const local = validateLocalArtifact(artifactContent, {
612
700
  family: localFamily,
613
- requiredSections: validationConfig.requiredSections,
614
- requiredPatterns: validationConfig.requiredPatterns
701
+ taskContent: content,
702
+ artifact: completedArtifact.name
615
703
  });
616
704
  if (!local.ok) {
617
705
  return failed(normalized, {
@@ -730,6 +818,13 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
730
818
  const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
731
819
  const frontmatterSet: Record<string, string> = { current_step: step, assigned_to: normalized.agent };
732
820
  let frontmatterRemove: string[] | undefined;
821
+ if (eventIdentity.phase === 'started' && artifactContext) {
822
+ try {
823
+ frontmatterSet.qualification_input_relations = JSON.stringify(qualificationRelationsForStarted(eventIdentity.family, artifactContext.inputs));
824
+ } catch (error) {
825
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot capture qualification input relations: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
826
+ }
827
+ }
733
828
  if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
734
829
  const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
735
830
  if (!planInput) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started plan input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
@@ -754,6 +849,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
754
849
  } else if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-')) {
755
850
  frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
756
851
  }
852
+ if (eventIdentity.phase === 'completed') frontmatterRemove = [...(frontmatterRemove ?? []), 'qualification_input_relations'];
757
853
  if (eventIdentity.phase === 'started' && normalized.implementationInput) frontmatterSet.last_reviewed_commit = '';
758
854
  const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
759
855
  { kind: 'frontmatter', set: frontmatterSet, remove: frontmatterRemove }
package/lib/task/index.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import {
2
+ PUBLIC_CLI_ROUTE_SELECTORS,
3
+ PUBLIC_CLI_SELECTOR_ALIASES
4
+ } from '../internal/cli-route-inventory.ts';
5
+
1
6
  const USAGE = `Usage: ai task <command> [options]
2
7
 
3
8
  Commands:
@@ -30,6 +35,7 @@ Examples:
30
35
  ai task status --task 11
31
36
 
32
37
  Run 'ai task <command> --help' for details.`;
38
+ const TASK_OPERATIONS = PUBLIC_CLI_ROUTE_SELECTORS.task;
33
39
 
34
40
  export async function runTask(args: string[]): Promise<void> {
35
41
  const [subcommand, ...rest] = args;
@@ -45,14 +51,21 @@ export async function runTask(args: string[]): Promise<void> {
45
51
  return;
46
52
  }
47
53
 
48
- switch (subcommand) {
54
+ const routeSelector: string = PUBLIC_CLI_SELECTOR_ALIASES.task[subcommand as keyof typeof PUBLIC_CLI_SELECTOR_ALIASES.task] ?? subcommand;
55
+ if (!TASK_OPERATIONS.includes(routeSelector as typeof TASK_OPERATIONS[number])) {
56
+ process.stderr.write(`Unknown task command: ${subcommand}\n\n`);
57
+ process.stdout.write(`${USAGE}\n`);
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+
62
+ switch (routeSelector) {
49
63
  case 'cat': {
50
64
  const { cat } = await import('./commands/cat.ts');
51
65
  cat(rest);
52
66
  break;
53
67
  }
54
- case 'decisions':
55
- case 'd': {
68
+ case 'decisions': {
56
69
  const { decisions } = await import('./commands/decisions.ts');
57
70
  decisions(rest);
58
71
  break;