@fitlab-ai/agent-infra 0.12.1 → 0.12.2

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 (303) hide show
  1. package/README.md +0 -2
  2. package/README.zh-CN.md +0 -2
  3. package/dist/bin/internal-cli.js +2 -17
  4. package/dist/lib/agent-clients/adapter.js +0 -13
  5. package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
  6. package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
  7. package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
  8. package/dist/lib/agent-clients/adapters/codex.js +0 -52
  9. package/dist/lib/agent-clients/adapters/opencode.js +0 -5
  10. package/dist/lib/agent-clients/adapters/traecli.js +0 -5
  11. package/dist/lib/agent-clients/config.js +19 -54
  12. package/dist/lib/agent-clients/registry.js +1 -12
  13. package/dist/lib/agent-clients/schema.js +0 -18
  14. package/dist/lib/agent-clients/tokens.js +1 -2
  15. package/dist/lib/agent-clients/types.js +0 -1
  16. package/dist/lib/decide.js +2 -0
  17. package/dist/lib/git/worktree-identity.js +1 -9
  18. package/dist/lib/internal/agent-client.js +2 -62
  19. package/dist/lib/internal/cli-route-inventory.js +1 -5
  20. package/dist/lib/internal/manual-validation.js +18 -10
  21. package/dist/lib/internal/task-create.js +1 -1
  22. package/dist/lib/internal/task-event.js +1 -8
  23. package/dist/lib/internal/task-finalization.js +0 -1
  24. package/dist/lib/internal/task-lifecycle.js +2 -2
  25. package/dist/lib/internal/task-operation-registry.js +4 -20
  26. package/dist/lib/internal/task-review.js +4 -4
  27. package/dist/lib/platform/comment-safety.js +1 -7
  28. package/dist/lib/platform/in-label-sync.js +1 -14
  29. package/dist/lib/platform/issue-comments.js +1 -16
  30. package/dist/lib/platform/issues.js +1 -1
  31. package/dist/lib/platform/operation-coordinator.js +4 -2
  32. package/dist/lib/platform/pr-change-report.js +1 -4
  33. package/dist/lib/platform/pr-checks.js +2 -8
  34. package/dist/lib/platform/provider-validation.js +0 -20
  35. package/dist/lib/platform/pull-requests.js +73 -84
  36. package/dist/lib/platform/releases.js +1 -43
  37. package/dist/lib/platform/verification-required.js +0 -2
  38. package/dist/lib/platform/verification-sync.js +1 -79
  39. package/dist/lib/process-data/normalize.js +1 -185
  40. package/dist/lib/process-data/privacy.js +1 -16
  41. package/dist/lib/process-data/sources.js +2 -279
  42. package/dist/lib/render.js +0 -12
  43. package/dist/lib/run/index.js +15 -10
  44. package/dist/lib/run/skills.js +0 -3
  45. package/dist/lib/sandbox/commands/create.js +40 -53
  46. package/dist/lib/sandbox/commands/list-running.js +0 -18
  47. package/dist/lib/sandbox/commands/start.js +7 -8
  48. package/dist/lib/sandbox/constants.js +0 -6
  49. package/dist/lib/sandbox/control/client.js +0 -1
  50. package/dist/lib/sandbox/control/container-identity.js +1 -16
  51. package/dist/lib/sandbox/control/executor.js +0 -4
  52. package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
  53. package/dist/lib/sandbox/control/lifecycle.js +21 -37
  54. package/dist/lib/sandbox/control/native-file-lock.js +0 -3
  55. package/dist/lib/sandbox/control/server.js +18 -38
  56. package/dist/lib/sandbox/control/state.js +1 -9
  57. package/dist/lib/sandbox/dockerfile.js +0 -6
  58. package/dist/lib/sandbox/engine.js +0 -14
  59. package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
  60. package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
  61. package/dist/lib/sandbox/recovery.js +24 -26
  62. package/dist/lib/sandbox/removal.js +11 -519
  63. package/dist/lib/sandbox/task-resolver.js +0 -41
  64. package/dist/lib/sandbox/tools.js +1 -4
  65. package/dist/lib/sandbox/workspace-view.js +1 -2
  66. package/dist/lib/server/display.js +0 -6
  67. package/dist/lib/task/activity-intent.js +45 -40
  68. package/dist/lib/task/activity-log.js +9 -16
  69. package/dist/lib/task/artifact-command.js +1 -1
  70. package/dist/lib/task/artifact-lifecycle.js +61 -63
  71. package/dist/lib/task/artifact-name.js +0 -6
  72. package/dist/lib/task/artifact-operations.js +3 -3
  73. package/dist/lib/task/capabilities.js +4 -5
  74. package/dist/lib/task/commands/log.js +0 -6
  75. package/dist/lib/task/commands/status.js +17 -60
  76. package/dist/lib/task/commit-operation.js +28 -39
  77. package/dist/lib/task/completion-facts.js +56 -43
  78. package/dist/lib/task/control-authority.js +23 -256
  79. package/dist/lib/task/control-recovery.js +3 -5
  80. package/dist/lib/task/create.js +2 -4
  81. package/dist/lib/task/decision-intents.js +8 -9
  82. package/dist/lib/task/delivery-target.js +1 -40
  83. package/dist/lib/task/events.js +84 -107
  84. package/dist/lib/task/finalization.js +44 -8
  85. package/dist/lib/task/ledger-intents.js +1 -1
  86. package/dist/lib/task/lifecycle-execution.js +38 -33
  87. package/dist/lib/task/lifecycle.js +49 -41
  88. package/dist/lib/task/local-artifact-finalization.js +1 -4
  89. package/dist/lib/task/manual-validation-evidence.js +1 -37
  90. package/dist/lib/task/platform-operation-recovery.js +67 -24
  91. package/dist/lib/task/pr-delivery-fact.js +2 -5
  92. package/dist/lib/task/qualification-confirmation.js +5 -7
  93. package/dist/lib/task/resolve-ref.js +1 -25
  94. package/dist/lib/task/review-command.js +2 -2
  95. package/dist/lib/task/review-finalization.js +1 -21
  96. package/dist/lib/task/review-fingerprint.js +0 -4
  97. package/dist/lib/task/secure-fs.js +0 -56
  98. package/dist/lib/task/short-id.js +1 -39
  99. package/dist/lib/task/skill-scope.js +0 -1
  100. package/dist/lib/task/verification-engine.js +58 -282
  101. package/dist/lib/task/verification.js +6 -3
  102. package/dist/lib/task/write.js +11 -1
  103. package/lib/agent-clients/adapter.ts +0 -76
  104. package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
  105. package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
  106. package/lib/agent-clients/adapters/claude-code.ts +0 -9
  107. package/lib/agent-clients/adapters/codex.ts +0 -56
  108. package/lib/agent-clients/adapters/opencode.ts +0 -5
  109. package/lib/agent-clients/adapters/traecli.ts +0 -5
  110. package/lib/agent-clients/config.ts +20 -61
  111. package/lib/agent-clients/registry.ts +1 -34
  112. package/lib/agent-clients/schema.ts +0 -18
  113. package/lib/agent-clients/tokens.ts +1 -2
  114. package/lib/agent-clients/types.ts +0 -15
  115. package/lib/decide.ts +1 -0
  116. package/lib/git/worktree-identity.ts +0 -10
  117. package/lib/internal/agent-client.ts +2 -70
  118. package/lib/internal/cli-route-inventory.ts +1 -4
  119. package/lib/internal/manual-validation.ts +16 -9
  120. package/lib/internal/task-create.ts +0 -1
  121. package/lib/internal/task-event.ts +1 -5
  122. package/lib/internal/task-finalization.ts +0 -1
  123. package/lib/internal/task-lifecycle.ts +3 -5
  124. package/lib/internal/task-operation-registry.ts +3 -19
  125. package/lib/internal/task-review.ts +4 -4
  126. package/lib/platform/comment-safety.ts +0 -10
  127. package/lib/platform/github-provider.ts +0 -2
  128. package/lib/platform/in-label-sync.ts +0 -16
  129. package/lib/platform/issue-comments.ts +0 -21
  130. package/lib/platform/issues.ts +0 -2
  131. package/lib/platform/operation-coordinator.ts +4 -2
  132. package/lib/platform/pr-change-report.ts +0 -5
  133. package/lib/platform/pr-checks.ts +1 -10
  134. package/lib/platform/pr-review.ts +1 -1
  135. package/lib/platform/provider-bridge.ts +0 -1
  136. package/lib/platform/provider-loader.ts +1 -5
  137. package/lib/platform/provider-validation.ts +1 -49
  138. package/lib/platform/pull-requests.ts +64 -99
  139. package/lib/platform/releases.ts +1 -40
  140. package/lib/platform/verification-required.ts +0 -2
  141. package/lib/platform/verification-sync.ts +0 -94
  142. package/lib/process-data/normalize.ts +1 -186
  143. package/lib/process-data/privacy.ts +2 -19
  144. package/lib/process-data/sources.ts +3 -283
  145. package/lib/render.ts +0 -15
  146. package/lib/run/index.ts +13 -7
  147. package/lib/run/skills.ts +0 -4
  148. package/lib/sandbox/commands/create.ts +37 -74
  149. package/lib/sandbox/commands/list-running.ts +0 -19
  150. package/lib/sandbox/commands/start.ts +7 -10
  151. package/lib/sandbox/constants.ts +0 -8
  152. package/lib/sandbox/control/client.ts +0 -1
  153. package/lib/sandbox/control/container-identity.ts +1 -16
  154. package/lib/sandbox/control/executor.ts +1 -5
  155. package/lib/sandbox/control/identity-sentinel.ts +0 -8
  156. package/lib/sandbox/control/lifecycle.ts +18 -51
  157. package/lib/sandbox/control/native-file-lock.ts +0 -8
  158. package/lib/sandbox/control/protocol.ts +0 -2
  159. package/lib/sandbox/control/server.ts +13 -39
  160. package/lib/sandbox/control/state.ts +1 -10
  161. package/lib/sandbox/dockerfile.ts +0 -11
  162. package/lib/sandbox/engine.ts +0 -17
  163. package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
  164. package/lib/sandbox/engines/wsl2-paths.ts +0 -4
  165. package/lib/sandbox/recovery.ts +24 -26
  166. package/lib/sandbox/removal.ts +11 -696
  167. package/lib/sandbox/shell.ts +1 -1
  168. package/lib/sandbox/task-resolver.ts +0 -48
  169. package/lib/sandbox/tools.ts +1 -5
  170. package/lib/sandbox/workspace-view.ts +2 -4
  171. package/lib/server/display.ts +0 -14
  172. package/lib/server/process-control.ts +1 -1
  173. package/lib/task/activity-intent.ts +36 -44
  174. package/lib/task/activity-log.ts +9 -22
  175. package/lib/task/artifact-command.ts +1 -1
  176. package/lib/task/artifact-lifecycle.ts +58 -67
  177. package/lib/task/artifact-name.ts +0 -7
  178. package/lib/task/artifact-operations.ts +3 -3
  179. package/lib/task/capabilities.ts +5 -6
  180. package/lib/task/commands/log.ts +0 -7
  181. package/lib/task/commands/status.ts +14 -79
  182. package/lib/task/commit-operation.ts +29 -51
  183. package/lib/task/completion-facts.ts +45 -42
  184. package/lib/task/control-authority.ts +27 -317
  185. package/lib/task/control-recovery.ts +2 -5
  186. package/lib/task/create.ts +2 -5
  187. package/lib/task/decision-intents.ts +5 -5
  188. package/lib/task/delivery-target.ts +0 -40
  189. package/lib/task/events.ts +89 -123
  190. package/lib/task/finalization.ts +41 -9
  191. package/lib/task/ledger-intents.ts +1 -1
  192. package/lib/task/lifecycle-execution.ts +37 -89
  193. package/lib/task/lifecycle.ts +48 -44
  194. package/lib/task/local-artifact-finalization.ts +0 -7
  195. package/lib/task/manual-validation-evidence.ts +1 -47
  196. package/lib/task/platform-operation-recovery.ts +50 -26
  197. package/lib/task/pr-delivery-fact.ts +2 -11
  198. package/lib/task/qualification-confirmation.ts +6 -6
  199. package/lib/task/resolve-ref.ts +0 -23
  200. package/lib/task/review-command.ts +2 -2
  201. package/lib/task/review-finalization.ts +1 -27
  202. package/lib/task/review-fingerprint.ts +0 -8
  203. package/lib/task/secure-fs.ts +0 -42
  204. package/lib/task/short-id.ts +0 -43
  205. package/lib/task/skill-scope.ts +0 -1
  206. package/lib/task/verification-engine.ts +56 -287
  207. package/lib/task/verification.ts +5 -5
  208. package/lib/task/write.ts +12 -1
  209. package/package.json +2 -2
  210. package/templates/.agents/README.en.md +1 -3
  211. package/templates/.agents/README.zh-CN.md +0 -2
  212. package/templates/.agents/rules/README.en.md +0 -1
  213. package/templates/.agents/rules/README.zh-CN.md +0 -1
  214. package/templates/.agents/rules/commit-and-pr.en.md +1 -1
  215. package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
  216. package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
  217. package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
  218. package/templates/.agents/rules/pr-sync.en.md +1 -1
  219. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
  220. package/templates/.agents/rules/task-management.en.md +3 -3
  221. package/templates/.agents/rules/task-management.zh-CN.md +3 -3
  222. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  223. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  224. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
  225. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
  226. package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
  227. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
  228. package/templates/.agents/skills/commit/SKILL.en.md +2 -2
  229. package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
  230. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
  231. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
  232. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  233. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  234. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  235. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  236. package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
  237. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
  238. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  239. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  240. package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
  241. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
  242. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  243. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  244. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
  245. package/templates/.agents/templates/task.en.md +1 -0
  246. package/templates/.agents/templates/task.zh-CN.md +1 -0
  247. package/templates/.claude/settings.json +0 -24
  248. package/templates/.codex/hooks.json +0 -50
  249. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
  250. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
  251. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
  252. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
  253. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
  254. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
  255. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
  256. package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
  257. package/dist/lib/internal/task-orchestration.js +0 -71
  258. package/dist/lib/platform/verification.js +0 -6
  259. package/dist/lib/run/prompt.js +0 -2
  260. package/dist/lib/sandbox/engines/authority.js +0 -289
  261. package/dist/lib/task/delegation-receipts.js +0 -410
  262. package/dist/lib/task/orchestration.js +0 -1233
  263. package/dist/lib/task/verification-checks.js +0 -9
  264. package/dist/lib/task/workspace-snapshot.js +0 -121
  265. package/dist/lifecycle-build-manifest.json +0 -6
  266. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
  267. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
  268. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
  269. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
  270. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
  271. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
  272. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
  273. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
  274. package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
  275. package/lib/internal/task-orchestration.ts +0 -74
  276. package/lib/platform/verification.ts +0 -11
  277. package/lib/run/prompt.ts +0 -1
  278. package/lib/sandbox/engines/authority.ts +0 -365
  279. package/lib/task/delegation-receipts.ts +0 -547
  280. package/lib/task/orchestration.ts +0 -1651
  281. package/lib/task/verification-checks.ts +0 -10
  282. package/lib/task/workspace-snapshot.ts +0 -144
  283. package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
  284. package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
  285. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
  286. package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
  287. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
  288. package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
  289. package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
  290. package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
  291. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
  292. package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
  293. package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
  294. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
  295. package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
  296. package/templates/.claude/commands/run-task.en.md +0 -8
  297. package/templates/.claude/commands/run-task.zh-CN.md +0 -8
  298. package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
  299. package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
  300. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
  301. package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
  302. package/templates/.opencode/commands/run-task.en.md +0 -11
  303. package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
@@ -124,7 +124,7 @@ function diagnostic(
124
124
  return { code, message, sectionId, line };
125
125
  }
126
126
 
127
- function sectionHeadings(schema: ArtifactSchema, section: ArtifactSection): readonly string[] {
127
+ function sectionHeadings(section: ArtifactSection): readonly string[] {
128
128
  return [section.headings.zh, section.headings.en];
129
129
  }
130
130
 
@@ -152,7 +152,7 @@ function inspectArtifactStructure(
152
152
 
153
153
  const headingsBySection = new Map<string, typeof scanned.headings>();
154
154
  for (const section of schema.sections) {
155
- const aliases = sectionHeadings(schema, section);
155
+ const aliases = sectionHeadings(section);
156
156
  const exact = scanned.headings.filter((heading) => heading.level === 2 && aliases.includes(heading.text));
157
157
  const punctuated = scanned.headings.filter((heading) => heading.level === 2 && aliases.some((alias) => heading.text === `${alias}:` || heading.text === `${alias}:`));
158
158
  headingsBySection.set(section.id, exact.length > 0 ? exact : punctuated);
@@ -283,7 +283,7 @@ function inspectArtifactStateCheck(
283
283
 
284
284
  const scanned = scanVisibleMarkdown(content);
285
285
  const headings = scanned.headings.filter((heading) => (
286
- heading.level === 2 && sectionHeadings(schema, stateCheck).includes(heading.text)
286
+ heading.level === 2 && sectionHeadings(stateCheck).includes(heading.text)
287
287
  ));
288
288
  if (headings.length !== 1) {
289
289
  return { ok: true, family: schema.family, semanticDigest: canonicalSemanticDigest(content), diagnostics: [] };
@@ -9,13 +9,13 @@ import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.
9
9
  import { parseReworkIntentDocument } from './rework-intent.ts';
10
10
  import type { ReworkIntent, ReworkTarget } from './rework-intent.ts';
11
11
  import { sha256File } from './artifact-receipts.ts';
12
- import { hasOpenLifecycleExecution } from './activity-log.ts';
12
+ import { lifecycleExecutions } from './lifecycle-execution.ts';
13
13
  import { parseQualificationAudit, parseTaskQualification } from './qualification-audit.ts';
14
14
  import type { QualificationAudit, TaskQualification } from './qualification-audit.ts';
15
15
  import { parseLifecyclePathDecision, pathIncludes } from './lifecycle-path.ts';
16
16
  import type { LifecyclePathState } from './lifecycle-path.ts';
17
17
  import { parseImplementationInputs } from './implementation-inputs.ts';
18
- import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.ts';
18
+ import { hasValidArtifactCompletion } from './completion-facts.ts';
19
19
  import { inspectReviewIdentity } from './review-identity.ts';
20
20
  import { inspectArtifactDirectory } from './artifact-lifecycle.ts';
21
21
  import { executionInputsMatchLatest as executionReceiptsMatchLatest } from './execution-inputs.ts';
@@ -25,7 +25,7 @@ const ARTIFACT_AUDIT_FAMILIES = new Set(['analysis', 'review-analysis', 'plan',
25
25
  type LifecycleAction =
26
26
  | 'analysis' | 'review-analysis' | 'plan' | 'review-plan'
27
27
  | 'code' | 'review-code' | 'manual-validation' | 'validation-run';
28
- type TriggerInitiator = 'human' | 'model' | 'orchestrator';
28
+ type TriggerInitiator = 'human' | 'model';
29
29
  type TriggerReason = 'user-request' | 'new-requirement' | 'upstream-fact-doubt' | 'review-finding' | 'manual-review-supplement' | 'retry' | 'validation-rerun';
30
30
  type ExplicitTrigger = {
31
31
  initiator: TriggerInitiator;
@@ -395,8 +395,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
395
395
  if (family === 'review-code') {
396
396
  const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
397
397
  reviewCodeIdentityValid = identity.status === 'matched'
398
- && hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
399
- && hasArtifactCompletionLog(content, latest, 'review-code.completed');
398
+ && hasValidArtifactCompletion(content, taskDir, latest, 'review-code.completed');
400
399
  }
401
400
  const parsed = parseReviewSummary(reviewContent);
402
401
  if (!parsed.ok) continue;
@@ -455,7 +454,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
455
454
  reviewedInputs, executionInputs, artifactHashes,
456
455
  reworkIntents: rework.intents,
457
456
  unresolvedLedger,
458
- executionBusy: executionBusy || hasOpenLifecycleExecution(content),
457
+ executionBusy: lifecycleExecutions(content).some((item) => item.family !== 'other') || executionBusy,
459
458
  recommendedAction: null,
460
459
  qualificationStale: qualificationStaleArtifacts.length > 0,
461
460
  qualificationStaleArtifacts
@@ -91,13 +91,6 @@ function isHumanCountField(field: string): boolean {
91
91
  return fieldNumber(field, 'manual-validation') !== undefined || fieldNumber(field, 'human-decision') !== undefined;
92
92
  }
93
93
 
94
- // A step is human-executed when its agent token is not a known AI token.
95
- // Known long names classify as AI; empty or unknown tokens classify as non-AI
96
- // (human).
97
- export function isHumanAgent(agent: string): boolean {
98
- return classifyAgent(agent).status !== 'ai';
99
- }
100
-
101
94
  // Fold the two human counts into a review row's verdict NOTE: comma-joined, right
102
95
  // after the blockers/major/minor list and before the ` → artifact` link, mirroring
103
96
  // the review count line. Review done notes already carry `Manual-validation` as a
@@ -9,8 +9,8 @@ import { parseTaskFrontmatter, extractTitle, type Frontmatter } from '../frontma
9
9
  import { loadShortIdByTaskId } from '../short-id.ts';
10
10
  import { getOpenWorkflowWarnings, formatWorkflowWarningSummary, type WorkflowWarning } from '../workflow-warnings.ts';
11
11
  import { parseActivityLog, pairEntries, startedBackedRows } from '../activity-log.ts';
12
- import { readRun, type OrchestrationRun } from '../orchestration.ts';
13
- import type { DelegationReceipt } from '../delegation-receipts.ts';
12
+ import { lifecycleExecutions } from '../lifecycle-execution.ts';
13
+ import { parseTypedTaskFrontmatter } from '../frontmatter.ts';
14
14
  import { statusCard, type DisplayMessage } from '../../server/display.ts';
15
15
  import { buildLifecycleFacts, recommendNext } from '../capabilities.ts';
16
16
 
@@ -181,6 +181,15 @@ function parseActivityTime(value: string): number {
181
181
  return Number.isFinite(epoch) ? epoch : Number.NaN;
182
182
  }
183
183
 
184
+ function collectStructuredWorkflow(content: string, now: Date): WorkflowInfo {
185
+ try {
186
+ const current = lifecycleExecutions(content).filter((row) => row.family !== 'other').at(-1);
187
+ if (!current) return { state: 'idle', step: DASH, agent: DASH, startedAt: DASH, doneAt: DASH, stale: DASH };
188
+ const started = parseActivityTime(current.startedAt);
189
+ return { state: 'in-progress', step: current.family, agent: current.agent, startedAt: current.startedAt, doneAt: DASH, stale: Number.isFinite(started) ? (now.getTime() - started > STALE_MS ? 'yes' : 'no') : 'unknown' };
190
+ } catch { return { state: 'unknown', step: DASH, agent: DASH, startedAt: DASH, doneAt: DASH, stale: DASH }; }
191
+ }
192
+
184
193
  function collectWorkflow(content: string, now: Date = new Date()): WorkflowInfo {
185
194
  const parsed = parseActivityLog(content);
186
195
  if (!parsed.sectionFound || parsed.entries.length === 0) {
@@ -222,19 +231,6 @@ type RuntimeInfo = {
222
231
  log: string;
223
232
  };
224
233
 
225
- type PendingDelegationInfo = Readonly<Pick<
226
- DelegationReceipt,
227
- 'id' | 'role' | 'stage' | 'round' | 'artifact' | 'client' | 'status'
228
- >>;
229
-
230
- type OrchestrationInfo = Readonly<{
231
- status: 'absent' | OrchestrationRun['status'];
232
- runId: string;
233
- nextStage: string;
234
- pause: Readonly<{ code: string; message: string; recoverable: boolean }> | null;
235
- pendingDelegation: PendingDelegationInfo | null;
236
- }>;
237
-
238
234
  type ManagedRunRecord = {
239
235
  run_id: string;
240
236
  engine: string;
@@ -339,39 +335,6 @@ function collectRuntime(taskDir: string, workflow: WorkflowInfo, run: Runner): R
339
335
  };
340
336
  }
341
337
 
342
- function pendingDelegationInfo(receipt: DelegationReceipt | null): PendingDelegationInfo | null {
343
- if (!receipt) return null;
344
- return {
345
- id: receipt.id,
346
- role: receipt.role,
347
- stage: receipt.stage,
348
- round: receipt.round,
349
- artifact: receipt.artifact,
350
- client: receipt.client,
351
- status: receipt.status
352
- };
353
- }
354
-
355
- function collectOrchestration(taskDir: string): OrchestrationInfo {
356
- const run = readRun(taskDir);
357
- if (!run) {
358
- return {
359
- status: 'absent',
360
- runId: DASH,
361
- nextStage: DASH,
362
- pause: null,
363
- pendingDelegation: null
364
- };
365
- }
366
- return {
367
- status: run.status,
368
- runId: run.runId,
369
- nextStage: run.nextStage ?? DASH,
370
- pause: run.pause,
371
- pendingDelegation: pendingDelegationInfo(run.pendingDelegation)
372
- };
373
- }
374
-
375
338
  type StatusModel = {
376
339
  taskId: string;
377
340
  shortId: string;
@@ -381,7 +344,6 @@ type StatusModel = {
381
344
  artifacts: { count: number; groups: { stage: string; files: string[] }[] };
382
345
  workflow: WorkflowInfo;
383
346
  runtime: RuntimeInfo;
384
- orchestration: OrchestrationInfo;
385
347
  recommendation?: string;
386
348
  recommendationReason?: string;
387
349
  lifecyclePath?: string;
@@ -439,26 +401,6 @@ function renderStatus(model: StatusModel): string[] {
439
401
  ['reason', model.recommendationReason ?? DASH]
440
402
  ]));
441
403
 
442
- lines.push(
443
- '',
444
- 'Orchestration',
445
- ...renderPairs([
446
- ['status', model.orchestration.status],
447
- ['run_id', model.orchestration.runId],
448
- ['next_stage', model.orchestration.nextStage],
449
- ['pause_code', model.orchestration.pause?.code ?? DASH],
450
- ['pause_message', model.orchestration.pause?.message ?? DASH],
451
- ['recoverable', model.orchestration.pause ? String(model.orchestration.pause.recoverable) : DASH],
452
- ['pending_id', model.orchestration.pendingDelegation?.id ?? DASH],
453
- ['pending_role', model.orchestration.pendingDelegation?.role ?? DASH],
454
- ['pending_stage', model.orchestration.pendingDelegation?.stage ?? DASH],
455
- ['pending_round', model.orchestration.pendingDelegation ? String(model.orchestration.pendingDelegation.round) : DASH],
456
- ['pending_artifact', model.orchestration.pendingDelegation?.artifact ?? DASH],
457
- ['pending_client', model.orchestration.pendingDelegation?.client ?? DASH],
458
- ['pending_status', model.orchestration.pendingDelegation?.status ?? DASH]
459
- ])
460
- );
461
-
462
404
  lines.push(
463
405
  '',
464
406
  'Workflow',
@@ -504,9 +446,6 @@ function renderStatus(model: StatusModel): string[] {
504
446
  }
505
447
 
506
448
  function modelTone(model: StatusModel): 'info' | 'success' | 'warning' | 'danger' | 'running' {
507
- if (model.orchestration.status === 'paused') return 'warning';
508
- if (model.orchestration.status === 'running') return 'running';
509
- if (model.orchestration.status === 'completed') return 'success';
510
449
  if (model.workflow.state === 'in-progress') return model.workflow.stale === 'yes' ? 'warning' : 'running';
511
450
  if (model.metadata.some(([key, value]) => key === 'status' && value === 'completed')) return 'success';
512
451
  if (model.metadata.some(([key, value]) => key === 'status' && (value === 'blocked' || value === 'cancelled'))) {
@@ -520,7 +459,8 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
520
459
  const fm = parseTaskFrontmatter(content);
521
460
  const run = input.run ?? makeRunner(input.repoRoot);
522
461
  const artifacts = enumerateArtifacts(input.taskDir);
523
- const workflow = collectWorkflow(content, input.now);
462
+ const workflow = parseTypedTaskFrontmatter(content).lifecycle_execution !== undefined
463
+ ? collectStructuredWorkflow(content, input.now ?? new Date()) : collectWorkflow(content, input.now);
524
464
  const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
525
465
  const next = facts.ok ? recommendNext(facts.facts) : null;
526
466
  const recommendation = next?.action ?? DASH;
@@ -534,7 +474,6 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
534
474
  artifacts: { count: artifacts.length, groups: groupArtifacts(artifacts) },
535
475
  workflow,
536
476
  runtime: collectRuntime(input.taskDir, workflow, run),
537
- orchestration: collectOrchestration(input.taskDir),
538
477
  recommendation,
539
478
  recommendationReason: next?.reasonCode ?? 'TASK_CAPABILITY_FACTS_INVALID',
540
479
  lifecyclePath: facts.ok && facts.facts.pathState?.status === 'valid' ? facts.facts.pathState.decision.path : facts.ok ? facts.facts.pathState?.status ?? DASH : DASH,
@@ -572,9 +511,6 @@ function statusModelToDisplay(model: StatusModel): DisplayMessage {
572
511
  `Task ${model.taskId} (${model.shortId})`,
573
512
  modelTone(model),
574
513
  [
575
- ['orchestration', model.orchestration.status],
576
- ['run', model.orchestration.runId],
577
- ['next', model.orchestration.nextStage],
578
514
  ['workflow', model.workflow.state],
579
515
  ['step', model.workflow.step],
580
516
  ['runtime', model.runtime.status],
@@ -622,9 +558,8 @@ export {
622
558
  collectGit,
623
559
  collectWorkflow,
624
560
  collectRuntime,
625
- collectOrchestration,
626
561
  renderStatus,
627
562
  statusModelToDisplay,
628
563
  METADATA_KEYS
629
564
  };
630
- export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, OrchestrationInfo, PendingDelegationInfo, StatusModel, BuildStatusModelInput };
565
+ export type { Runner, GitInfo, WorkflowInfo, RuntimeInfo, StatusModel, BuildStatusModelInput };
@@ -8,9 +8,9 @@ import { checkpointCommitMatches } from './commit-identity.ts';
8
8
  import { resolveTaskContext, resolveTaskRef } from './resolve-ref.ts';
9
9
  import { commitPushDecision } from './commit-policy.ts';
10
10
  import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
11
- import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
11
+ import { activityWarnings, appendActivityEntry, locateActivityLog } from './activity-log.ts';
12
12
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
13
- import type { TaskMutation } from './write.ts';
13
+ import type { TaskMutation, TaskWriteOptions } from './write.ts';
14
14
  import {
15
15
  checkpointIntentDigest,
16
16
  readCheckpointIntent,
@@ -20,7 +20,7 @@ import {
20
20
  writeCheckpointIntent
21
21
  } from './commit-intent.ts';
22
22
 
23
- type CommitExecutionMode = 'direct' | 'orchestrated';
23
+ type CommitExecutionMode = 'direct';
24
24
 
25
25
  type CommitOperationInput = Readonly<{
26
26
  cwd: string;
@@ -68,7 +68,7 @@ const HEAD_REF = /^refs\/heads\/(.+)$/;
68
68
  type CommitDelivery = Readonly<{ mode: 'local' } | { mode: 'push'; remote: string; refs: readonly string[] }>;
69
69
 
70
70
  function isCommitExecutionMode(value: unknown): value is CommitExecutionMode {
71
- return value === 'direct' || value === 'orchestrated';
71
+ return value === 'direct';
72
72
  }
73
73
 
74
74
  function gitText(repoRoot: string, args: readonly string[]): string {
@@ -193,55 +193,42 @@ function validateCommon(input: CommitOperationInput, branch: string | null): Rea
193
193
  return null;
194
194
  }
195
195
 
196
+ // Log presentation only; checkpoint identity and recovery never depend on this projection.
197
+ function commitLogProjection(activity: ReturnType<typeof locateActivityLog>, timestamp: string, agent: string, commitNote: string): TaskMutation[] {
198
+ if (!activity) return [];
199
+ let lastCommit = -1;
200
+ for (const [index, entry] of activity.entries.entries()) {
201
+ if (entry.step === 'Commit' && entry.note === commitNote) lastCommit = index;
202
+ }
203
+ const reviewedAfter = activity.entries.slice(lastCommit + 1).some((entry) => /^Review Code \(Round \d+\)$/.test(entry.step));
204
+ if (lastCommit >= 0 && !reviewedAfter) return [];
205
+ return [{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading,
206
+ body: appendActivityEntry(activity, { time: timestamp, step: 'Commit', agent, note: commitNote }) }];
207
+ }
208
+
196
209
  function syncTaskCommit(
197
210
  input: CommitOperationInput,
198
211
  task: BoundTask,
199
- committedHead: string
212
+ committedHead: string,
213
+ writeOptions: TaskWriteOptions = {}
200
214
  ): OperationWarning | null {
201
215
  let content: string;
202
216
  try {
203
217
  content = fs.readFileSync(task.taskMdPath, 'utf8');
204
218
  const activity = locateActivityLog(content);
205
- if (!activity) return warning(
206
- 'TASK_STATUS_SYNC_FAILED',
207
- 'task has no unique Activity Log section',
208
- true,
209
- 'task',
210
- task.taskId,
211
- 'ACTION_REQUIRED'
212
- );
213
219
  const metadata = captureTaskWriteMetadata();
214
220
  const commitNote = gitText(task.repoRoot, ['show', '-s', '--format=%h %s', committedHead]);
215
- let matchingCommitIndex = -1;
216
- for (let index = activity.entries.length - 1; index >= 0; index -= 1) {
217
- const entry = activity.entries[index]!;
218
- if (entry.step === 'Commit' && entry.note === commitNote) {
219
- matchingCommitIndex = index;
220
- break;
221
- }
222
- }
223
- const reviewedAfterCommit = matchingCommitIndex >= 0 && activity.entries
224
- .slice(matchingCommitIndex + 1)
225
- .some((entry) => /^Review Code \(Round \d+\)$/.test(entry.step));
226
- const alreadyLogged = matchingCommitIndex >= 0 && !reviewedAfterCommit;
227
- const mutations: TaskMutation[] = [];
228
- if (!alreadyLogged) mutations.push({
229
- kind: 'section',
230
- aliases: ['活动日志', 'Activity Log'],
231
- heading: activity.heading,
232
- body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Commit', agent: input.agent!, note: commitNote })
233
- });
221
+ const mutations = commitLogProjection(activity, metadata.timestamp, input.agent!, commitNote);
234
222
  const frontmatter: Record<string, string> = {
235
223
  assigned_to: input.agent!,
236
224
  checkpoint_commit: committedHead
237
225
  };
238
226
  mutations.push({ kind: 'frontmatter', set: frontmatter });
239
- if (mutations.length === 0) return null;
240
227
  const written = writeTask({
241
228
  taskRef: task.taskId,
242
229
  expectedState: 'active',
243
230
  mutations
244
- }, { repoRoot: task.repoRoot, metadataProvider: () => metadata });
231
+ }, { ...writeOptions, repoRoot: task.repoRoot, metadataProvider: () => metadata });
245
232
  if (written.status === 'failed') return warning(
246
233
  'TASK_STATUS_SYNC_FAILED',
247
234
  written.error.message,
@@ -250,7 +237,8 @@ function syncTaskCommit(
250
237
  task.taskId,
251
238
  'ACTION_REQUIRED'
252
239
  );
253
- return null;
240
+ const logWarning = activityWarnings(content)[0];
241
+ return logWarning ? warning(logWarning.code, logWarning.message, true, 'task', task.taskId, 'IMPORTANT') : null;
254
242
  } catch (error) {
255
243
  return warning(
256
244
  'TASK_STATUS_SYNC_FAILED',
@@ -281,18 +269,9 @@ function checkpointTimestamp(): string {
281
269
  return new Date().toISOString();
282
270
  }
283
271
 
284
- function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask | null, mode: CommitExecutionMode): CommitOperationResult {
272
+ function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask | null, mode: CommitExecutionMode, writeOptions: TaskWriteOptions): CommitOperationResult {
285
273
  const inspected = inspectGitWorkflow(input.cwd);
286
274
  if (!inspected.snapshot) return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
287
- if (mode === 'orchestrated') return failure(
288
- input,
289
- mode,
290
- task?.taskId ?? null,
291
- 'ORCHESTRATED_COMMIT_REMOVED',
292
- 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task',
293
- 'blocked',
294
- inspected.snapshot
295
- );
296
275
  const branch = inspected.snapshot.branch;
297
276
  const commonError = validateCommon(input, branch);
298
277
  if (commonError) return failure(input, mode, task?.taskId ?? null, commonError.code, commonError.message, 'blocked', inspected.snapshot);
@@ -466,13 +445,13 @@ function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask
466
445
  }
467
446
  if (task && (committed.status === 'applied' || committed.status === 'no-op')) {
468
447
  const committedHead = checkpointCommittedHead ?? committed.snapshot?.head ?? gitText(task.repoRoot, ['rev-parse', 'HEAD']);
469
- const taskWarning = syncTaskCommit(input, task, committedHead);
448
+ const taskWarning = syncTaskCommit(input, task, committedHead, writeOptions);
470
449
  if (taskWarning) {
471
450
  warnings.push(taskWarning);
472
451
  status = 'applied';
473
452
  result = 'committed_with_warnings';
474
453
  }
475
- if (delivery.mode === 'local' && !taskWarning && pendingIntent && checkpointCommittedHead) {
454
+ if (delivery.mode === 'local' && taskWarning?.code !== 'TASK_STATUS_SYNC_FAILED' && pendingIntent && checkpointCommittedHead) {
476
455
  const synced = updateCheckpointIntent(pendingIntent, {
477
456
  state: 'synced', committedHead: checkpointCommittedHead, updatedAt: checkpointTimestamp()
478
457
  });
@@ -493,7 +472,7 @@ function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask
493
472
  };
494
473
  }
495
474
 
496
- function executeCommitOperation(input: CommitOperationInput): CommitOperationResult {
475
+ function executeCommitOperation(input: CommitOperationInput, writeOptions: TaskWriteOptions = {}): CommitOperationResult {
497
476
  if (input.mode !== undefined && !isCommitExecutionMode(input.mode)) return {
498
477
  status: 'blocked',
499
478
  changed: false,
@@ -503,7 +482,7 @@ function executeCommitOperation(input: CommitOperationInput): CommitOperationRes
503
482
  warnings: [],
504
483
  snapshot: null,
505
484
  operations: [],
506
- error: { code: 'COMMIT_MODE_INVALID', message: 'commit mode must be direct or orchestrated' }
485
+ error: { code: 'COMMIT_MODE_INVALID', message: 'commit mode must be direct' }
507
486
  };
508
487
  const mode: CommitExecutionMode = input.mode ?? 'direct';
509
488
  let normalizedInput: CommitOperationInput;
@@ -515,9 +494,8 @@ function executeCommitOperation(input: CommitOperationInput): CommitOperationRes
515
494
  const resolved = resolveBoundTask(normalizedInput);
516
495
  if (resolved.kind === 'error') return resolved.result;
517
496
  const task = resolved.kind === 'bound' ? resolved.task : null;
518
- if (mode === 'orchestrated' && !task) return failure(normalizedInput, mode, null, 'ORCHESTRATION_TASK_REQUIRED', 'orchestrated commit requires a task context', 'blocked');
519
497
  try {
520
- return executeCommitOperationCore(normalizedInput, task, mode);
498
+ return executeCommitOperationCore(normalizedInput, task, mode, writeOptions);
521
499
  } catch (error) {
522
500
  const code = 'COMMIT_OPERATION_FAILED';
523
501
  return failure(normalizedInput, mode, task?.taskId ?? null, code, error instanceof Error ? error.message : String(error), 'blocked');
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
 
3
4
  import { canonicalSemanticDigest } from './artifact-operations.ts';
4
- import { inspectActivityLog, pairEntries } from './activity-log.ts';
5
5
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
6
6
  import { parseArtifactName } from './artifact-name.ts';
7
7
  import { sha256File } from './artifact-receipts.ts';
@@ -35,54 +35,57 @@ export function parseCompletionFacts(value: unknown): CompletionFact[] {
35
35
  }
36
36
  }
37
37
 
38
- export function hasArtifactCompletionFact(taskContent: string, artifactPath: string, event: string): boolean {
38
+ function completionEvent(family: string): string | null {
39
+ if (family === 'validation-run') return 'validation-run.completed';
40
+ if (family === 'analysis') return 'analyze.completed';
41
+ if (family === 'review-analysis') return 'review-analysis.completed';
42
+ if (family === 'plan') return 'plan.completed';
43
+ if (family === 'review-plan') return 'review-plan.completed';
44
+ if (family === 'code') return 'code.completed';
45
+ if (family === 'review-code') return 'review-code.completed';
46
+ return null;
47
+ }
48
+
49
+ /** Validates an artifact's own completion fact and output evidence. */
50
+ export function hasValidArtifactCompletion(taskContent: string, taskDir: string, artifact: string, event: string): boolean {
39
51
  try {
40
- const frontmatter = parseTypedTaskFrontmatter(taskContent);
41
- const output = artifactPath.split(/[\\/]/u).at(-1) ?? '';
42
- const content = fs.readFileSync(artifactPath, 'utf8');
43
- const outputSha256 = sha256File(artifactPath);
44
- const semanticDigest = canonicalSemanticDigest(content);
45
- return parseCompletionFacts(frontmatter.completion_facts).some((fact) =>
46
- fact.event === event && fact.output === output
47
- && fact.outputSha256 === outputSha256 && fact.semanticDigest === semanticDigest
48
- && fact.requestId.length > 0
49
- );
52
+ const fact = findCompletionFact(taskContent, artifact, event);
53
+ return Boolean(fact && validateOutput(taskDir, artifact, fact));
50
54
  } catch {
51
55
  return false;
52
56
  }
53
57
  }
54
58
 
55
- export function hasArtifactCompletionLog(taskContent: string, artifact: string, event: string): boolean {
56
- return artifactCompletionOrder(taskContent, artifact, event) !== null;
59
+ /** Distinguishes an invalidated completion fact from a round that has never completed. */
60
+ export function hasArtifactCompletionRecord(taskContent: string, artifact: string, event: string): boolean {
61
+ try {
62
+ const raw = parseTypedTaskFrontmatter(taskContent).completion_facts;
63
+ if (raw === undefined) return false;
64
+ if (typeof raw !== 'string') return true;
65
+ if (raw.trim() === '') return false;
66
+ const parsed: unknown = JSON.parse(raw);
67
+ if (!Array.isArray(parsed)) return true;
68
+ return parsed.some((fact) => fact && typeof fact === 'object' && !Array.isArray(fact)
69
+ && (fact as { event?: unknown }).event === event
70
+ && (fact as { output?: unknown }).output === artifact);
71
+ } catch {
72
+ return true;
73
+ }
57
74
  }
58
75
 
59
- /** Returns the stable Activity Log position of a paired completion row. */
60
- export function artifactCompletionOrder(taskContent: string, artifact: string, event: string): number | null {
76
+ function findCompletionFact(taskContent: string, artifact: string, event: string): CompletionFact | null {
61
77
  const identity = parseArtifactName(artifact);
62
- if (!identity) return null;
63
- const family = event === 'analysis.completed' || event === 'analyze.completed' ? 'analysis'
64
- : event === 'review-analysis.completed' ? 'review-analysis'
65
- : event === 'plan.completed' ? 'plan'
66
- : event === 'review-plan.completed' ? 'review-plan'
67
- : event === 'code.completed' ? 'code'
68
- : event === 'review-code.completed' ? 'review-code' : null;
69
- if (!family || family !== identity.family) return null;
70
- const action = family === 'analysis' ? 'Analyze Task'
71
- : family === 'review-analysis' ? 'Review Analysis'
72
- : family === 'plan' ? 'Plan Task'
73
- : family === 'review-plan' ? 'Review Plan'
74
- : family === 'code' ? 'Code Task' : 'Review Code';
75
- const { section, invalidEntries } = inspectActivityLog(taskContent);
76
- if (!section || invalidEntries.length > 0) return null;
77
- const ordered = section.entries.map((entry, sourceOrder) => ({ entry, sourceOrder }))
78
- .sort((left, right) => Date.parse(left.entry.time.replace(' ', 'T')) - Date.parse(right.entry.time.replace(' ', 'T')) || left.sourceOrder - right.sourceOrder);
79
- const entries = ordered.map(({ entry }) => entry);
80
- const roundPattern = new RegExp(`^${action} \\(Round ${identity.round}(?:, [^)]+)?\\)$`);
81
- const row = pairEntries(entries).find((candidate) => candidate.started !== ''
82
- && candidate.done !== '' && roundPattern.test(candidate.step)
83
- && candidate.note.includes(`→ ${artifact}`));
84
- if (!row) return null;
85
- const doneIndex = ordered.findIndex(({ entry }) => entry.time === row.done
86
- && entry.step === row.step && entry.note === row.note);
87
- return doneIndex < 0 ? null : doneIndex;
78
+ if (!identity || completionEvent(identity.family) !== event) return null;
79
+ const fact = parseCompletionFacts(parseTypedTaskFrontmatter(taskContent).completion_facts)
80
+ .find((candidate) => candidate.event === event && candidate.output === artifact);
81
+ return fact && fact.requestId.length > 0 ? fact : null;
82
+ }
83
+
84
+ function validateOutput(taskDir: string, artifact: string, fact: CompletionFact): boolean {
85
+ const outputPath = path.join(taskDir, artifact);
86
+ const stat = fs.lstatSync(outputPath);
87
+ if (!stat.isFile() || stat.isSymbolicLink()) return false;
88
+ const content = fs.readFileSync(outputPath, 'utf8');
89
+ return sha256File(outputPath) === fact.outputSha256
90
+ && canonicalSemanticDigest(content) === fact.semanticDigest;
88
91
  }