@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
@@ -1,7 +1,7 @@
1
1
  import { execFileSync, spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
- import type { ExecFileSyncOptions, StdioOptions, SpawnSyncOptions, SpawnSyncReturns } from 'node:child_process';
4
+ import type { ExecFileSyncOptions, StdioOptions, SpawnSyncReturns } from 'node:child_process';
5
5
  import { getAdapter } from './engines/index.ts';
6
6
 
7
7
  const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
@@ -1,11 +1,8 @@
1
- import { spawnSync } from 'node:child_process';
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
- import { isRemovedHashShortIdInput } from '../task/short-id.ts';
5
3
  import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
6
4
 
7
5
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
8
- const SHORT_ID_RE = /^\d+$/;
9
6
  export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'] as const;
10
7
  export type TaskWorkspaceState = typeof TASK_WORKSPACE_STATES[number];
11
8
  export type TaskWorkspace = Readonly<{
@@ -15,39 +12,10 @@ export type TaskWorkspace = Readonly<{
15
12
  taskMd: string;
16
13
  }>;
17
14
 
18
- function resolveShortIdStrict(arg: string, repoRoot: string): string {
19
- const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
20
- if (!fs.existsSync(scriptPath)) {
21
- throw new Error(
22
- `Short id '${arg}' provided but task-short-id.js script is missing at ${scriptPath}`
23
- );
24
- }
25
- const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
26
- if (result.status !== 0) {
27
- throw new Error(
28
- `Short id '${arg}' not found in active task registry: ${(result.stderr || '').trim()}`
29
- );
30
- }
31
- return result.stdout.trim();
32
- }
33
-
34
15
  function stripQuotes(value: string): string {
35
16
  return value.replace(/^(["'])(.*)\1$/, '$2');
36
17
  }
37
18
 
38
- function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
39
- const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
40
- for (const state of TASK_WORKSPACE_STATES) {
41
- const taskPath = state === 'archive'
42
- ? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
43
- : path.join(taskRoot, state, taskId, 'task.md');
44
- if (fs.existsSync(taskPath)) {
45
- return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
46
- }
47
- }
48
- throw new Error(`Task not found: ${taskId}`);
49
- }
50
-
51
19
  function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
52
20
  const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
53
21
  const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
@@ -103,22 +71,6 @@ function resolveBranchFromTaskContent(content: string, taskId: string): string {
103
71
  throw new Error(`Task ${taskId} has no branch field in task.md`);
104
72
  }
105
73
 
106
- export function resolveTaskBranch(arg: string, repoRoot: string): string {
107
- if (isRemovedHashShortIdInput(arg)) {
108
- throw new Error(`Invalid task short id '${arg}': task short ids must use bare digits`);
109
- }
110
- if (SHORT_ID_RE.test(arg)) {
111
- const taskId = resolveShortIdStrict(arg, repoRoot);
112
- const { content } = readTaskContent(repoRoot, taskId);
113
- return resolveBranchFromTaskContent(content, taskId);
114
- }
115
- if (!TASK_ID_RE.test(arg)) {
116
- return arg;
117
- }
118
- const { content } = readTaskContent(repoRoot, arg);
119
- return resolveBranchFromTaskContent(content, arg);
120
- }
121
-
122
74
  export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWorkspace {
123
75
  if (!TASK_ID_RE.test(taskId)) throw new Error(`Invalid task id: ${taskId}`);
124
76
  const matches = taskWorkspaceCandidates(repoRoot, taskId);
@@ -1,6 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import { createSandboxCapabilityPlan } from './agent-client-reconciler.ts';
3
- import { safeNameCandidates, sanitizeBranchName } from './constants.ts';
3
+ import { safeNameCandidates } from './constants.ts';
4
4
  import { hostJoin } from './engines/wsl2-paths.ts';
5
5
  import type { SandboxTool, SandboxToolInstall } from './tool-types.ts';
6
6
 
@@ -287,10 +287,6 @@ export function resolveTools(config: ToolsConfig): SandboxTool[] {
287
287
  return [...createSandboxCapabilityPlan(config).tools];
288
288
  }
289
289
 
290
- export function toolConfigDir(tool: SandboxTool, project: string, branch: string): string {
291
- return hostJoin(tool.sandboxBase, project, sanitizeBranchName(branch));
292
- }
293
-
294
290
  export function toolConfigDirCandidates(tool: SandboxTool, project: string, branch: string): string[] {
295
291
  return safeNameCandidates(branch).map((name) => hostJoin(tool.sandboxBase, project, name));
296
292
  }
@@ -59,7 +59,7 @@ export type SandboxControlSetup = Readonly<{
59
59
  controlRootId: string;
60
60
  }>;
61
61
 
62
- export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine' | 'authorityEvidence'> & {
62
+ export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine'> & {
63
63
  engine: string;
64
64
  }>;
65
65
 
@@ -355,15 +355,13 @@ export function finalizeSandboxControlManifest(
355
355
  engine: string;
356
356
  id: string;
357
357
  labels: Readonly<Record<string, string>>;
358
- authorityEvidence: SandboxControlManifest['authorityEvidence'];
359
358
  }>
360
359
  ): SandboxControlManifest {
361
360
  if (!identity.engine || !identity.id) throw new Error('SANDBOX_CONTROL_CONTAINER_ID_INVALID');
362
361
  const manifest: SandboxControlManifest = {
363
362
  ...setup.manifestDraft,
364
363
  engine: identity.engine,
365
- containerIdentity: { id: identity.id, labels: { ...identity.labels } },
366
- authorityEvidence: identity.authorityEvidence
364
+ containerIdentity: { id: identity.id, labels: { ...identity.labels } }
367
365
  };
368
366
  const persistedManifest = {
369
367
  ...manifest,
@@ -32,10 +32,6 @@ export function markdownMessage(markdown: string): DisplayMessage {
32
32
  return { kind: 'markdown', markdown };
33
33
  }
34
34
 
35
- export function tableMessage(columns: string[], rows: string[][], title?: string): DisplayMessage {
36
- return { kind: 'table', title, columns, rows };
37
- }
38
-
39
35
  export function statusCard(
40
36
  title: string,
41
37
  tone: DisplayTone,
@@ -59,16 +55,6 @@ export function streamEvent(
59
55
  return message;
60
56
  }
61
57
 
62
- export function commandResult(
63
- title: string,
64
- exitCode: number | null,
65
- signal: NodeJS.Signals | null,
66
- stdout?: string,
67
- stderr?: string
68
- ): DisplayMessage {
69
- return { kind: 'command-result', title, exitCode, signal, stdout, stderr };
70
- }
71
-
72
58
  export function normalizeOutbound(message: OutboundMessage): DisplayMessage {
73
59
  return typeof message === 'string' ? textMessage(message) : message;
74
60
  }
@@ -12,7 +12,7 @@ import {
12
12
  removePidFileIfMatches,
13
13
  writePidRecord
14
14
  } from './process-state.ts';
15
- import type { PidRecord, ProcessIdentity, ProcessState } from './process-state.ts';
15
+ import type { PidRecord, ProcessIdentity } from './process-state.ts';
16
16
 
17
17
  export { isProcessAlive } from './process-state.ts';
18
18
 
@@ -2,7 +2,8 @@ import fs from 'node:fs';
2
2
 
3
3
  import { normalizeAgentToken } from '../agent-clients/tokens.ts';
4
4
  import { resolveTaskRef } from './resolve-ref.ts';
5
- import { locateActivityLog, appendActivityEntry } from './activity-log.ts';
5
+ import { locateActivityLog, appendActivityEntry, inspectActivityLog } from './activity-log.ts';
6
+ import { lifecycleExecutions, prReviewExecution, serializeLifecycleExecution } from './lifecycle-execution.ts';
6
7
  import {
7
8
  assertWritableInventory,
8
9
  buildArtifactLinkSection,
@@ -82,6 +83,7 @@ export type PrReviewActivityResult = {
82
83
  artifact: string | null;
83
84
  operations: readonly TaskOperationSummary[];
84
85
  error: { code: string; message: string } | null;
86
+ warnings?: readonly { code: string; message: string }[];
85
87
  };
86
88
 
87
89
  export type PrReviewInspectionResult = {
@@ -102,7 +104,6 @@ type RoundState = ArtifactRecord & {
102
104
  };
103
105
 
104
106
  const HEAD_RE = /^[0-9a-f]{40}$/;
105
- const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
106
107
  const VERDICTS: ReadonlySet<string> = new Set(['approved', 'changes-requested', 'commented']);
107
108
  const OUTCOMES: ReadonlySet<string> = new Set(['aborted', 'superseded']);
108
109
  const SUCCESS_STATUSES: ReadonlySet<PrReviewArtifactStatus> = new Set(['applied', 'no-op']);
@@ -181,48 +182,28 @@ function readSnapshot(taskRef: string, repoRoot?: string):
181
182
  } catch (error) {
182
183
  return { ok: false, code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), taskId: resolved.taskId };
183
184
  }
184
- const activity = locateActivityLog(content);
185
- if (!activity) return { ok: false, code: 'ACTIVITY_SECTION_MISSING', message: 'task has no unique Activity Log section', taskId: resolved.taskId };
186
-
187
- const knownRounds = new Set(inventory.artifacts.map((artifact) => artifact.round));
188
- for (const entry of activity.entries) {
189
- const match = REVIEW_ACTION_RE.exec(entry.step);
190
- if (match && !knownRounds.has(Number(match[1]))) {
191
- return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `Activity Log round ${match[1]} has no canonical pr-review artifact`, taskId: resolved.taskId };
192
- }
193
- }
194
-
185
+ let pr;
186
+ try { pr = prReviewExecution(content); }
187
+ catch (error) { return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: String(error), taskId: resolved.taskId }; }
195
188
  const states: RoundState[] = [];
196
189
  for (const identity of inventory.artifacts) {
197
190
  const parsed = parseArtifactRecord(identity);
198
191
  if ('code' in parsed) return { ok: false, ...parsed, taskId: resolved.taskId };
199
- const base = `Review PR (Round ${identity.round})`;
200
- const started = activity.entries.filter((entry) => entry.step === `${base} [started]`);
201
- const terminal = activity.entries.filter((entry) => entry.step === base);
202
- if (started.length > 1 || terminal.length > 1) {
203
- return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} has duplicate started or terminal rows`, taskId: resolved.taskId };
204
- }
205
- if (terminal.length === 1 && started.length === 0 && !terminal[0]!.note.startsWith('receipt ')) {
206
- return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} has a terminal row without a matching started row`, taskId: resolved.taskId };
192
+ const started = pr.open?.artifact === identity.name ? pr.open : null;
193
+ const terminals = pr.terminals.filter((entry) => entry.artifact === identity.name);
194
+ if (terminals.length > 1) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `duplicate PR review terminal for ${identity.name}`, taskId: resolved.taskId };
195
+ const terminal = terminals[0];
196
+ if (terminal) {
197
+ const matches = (terminal.note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
198
+ || (terminal.note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
199
+ || (terminal.note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
200
+ || (terminal.note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
201
+ if (!matches) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `terminal note conflicts with artifact status ${parsed.status}`, taskId: resolved.taskId };
207
202
  }
208
- if (terminal.length === 1) {
209
- const note = terminal[0]!.note;
210
- const terminalMatchesStatus =
211
- (note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
212
- || (note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
213
- || (note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
214
- || (note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
215
- if (!terminalMatchesStatus) {
216
- return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} terminal note conflicts with artifact status ${parsed.status}`, taskId: resolved.taskId };
217
- }
218
- }
219
- states.push({
220
- ...parsed,
221
- startedAgent: started[0]?.agent ?? null,
222
- terminalAgent: terminal[0]?.agent ?? null,
223
- terminalNote: terminal[0]?.note ?? null
224
- });
203
+ if ((started && started.head !== parsed.head) || (terminal && terminal.head !== parsed.head)) return { ok: false, code: 'ACTIVITY_IDENTITY_MISMATCH', message: `head does not match ${identity.name}`, taskId: resolved.taskId };
204
+ states.push({ ...parsed, startedAgent: started?.agent ?? null, terminalAgent: terminal?.agent ?? null, terminalNote: terminal?.note ?? null });
225
205
  }
206
+ if ((pr.open && !states.some((state) => state.identity.name === pr.open!.artifact)) || pr.terminals.some((entry) => !states.some((state) => state.identity.name === entry.artifact))) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: 'PR review execution references a missing artifact', taskId: resolved.taskId };
226
207
 
227
208
  const openStates = states.filter((state) => state.startedAgent !== null && state.terminalNote === null);
228
209
  const preparedStates = states.filter((state) => state.status === 'pending' && state.startedAgent === null && state.terminalNote === null);
@@ -353,11 +334,15 @@ function applyActivityIntent(intent: PrReviewActivityIntent, normalizedAgent: st
353
334
  } catch (error) {
354
335
  return failed(intent, 'METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), state.taskId);
355
336
  }
356
- const section = locateActivityLog(state.content)!;
357
- const mutations: TaskMutation[] = [{
358
- kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading,
359
- body: appendActivityEntry(section, { time: metadata.timestamp, step, agent: normalizedAgent, note })
360
- }];
337
+ const section = locateActivityLog(state.content);
338
+ const pr = prReviewExecution(state.content);
339
+ if (intent.kind === 'pr-review-start') pr.open = { artifact: intent.artifact, head: intent.head, agent: normalizedAgent, startedAt: metadata.timestamp };
340
+ else {
341
+ pr.open = null;
342
+ pr.terminals.push({ artifact: intent.artifact, head: intent.head, agent: normalizedAgent, note });
343
+ }
344
+ const mutations: TaskMutation[] = [{ kind: 'frontmatter', lifecycleExecutionDomain: 'pr-review', set: { lifecycle_execution: serializeLifecycleExecution(lifecycleExecutions(state.content), state.content, pr) } }];
345
+ if (section) mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body: appendActivityEntry(section, { time: metadata.timestamp, step, agent: normalizedAgent, note }) });
361
346
  if (linkArtifact) {
362
347
  const link = buildArtifactLinkSection(state.content, roundState.identity);
363
348
  mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
@@ -399,7 +384,14 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
399
384
  if (resolved.state !== 'active') {
400
385
  return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
401
386
  }
402
- return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
387
+ let warnings: { code: string; message: string }[] = [];
388
+ try {
389
+ const inspected = inspectActivityLog(fs.readFileSync(resolved.taskMdPath, 'utf8'));
390
+ warnings = !inspected.section ? [{ code: 'ACTIVITY_LOG_UNAVAILABLE', message: 'Activity Log is missing or ambiguous' }] : inspected.invalidEntries.length ? [{ code: 'ACTIVITY_LOG_INVALID_ENTRIES', message: 'Activity Log contains malformed entries' }] : [];
391
+ } catch { /* The operation reports read errors. */ }
392
+ const result = applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
393
+ if (result.status === 'failed') return result;
394
+ return warnings.length ? { ...result, warnings } : result;
403
395
  }
404
396
 
405
397
  export type { ActivityIntentOptions };
@@ -3,13 +3,10 @@ const ENTRY_RE = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*
3
3
  const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
4
4
  const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
5
5
  const ATTEMPT_RE = /(?:^|;\s*)attempt=([A-Za-z0-9-]{8,64})(?:;|$)/;
6
- const COMMIT_STARTED_RE = /^started; attempt=([A-Za-z0-9-]{8,64}); baseline=([a-f0-9]{40,64}); agent=([a-z-]+)$/;
7
- const LIFECYCLE_STEP_RE = /^(?:Complete Manual Validation|(?:Analyze Task|Review Analysis|Plan Task|Review Plan|Code Task|Review Code|Run Manual Validation) \(Round [1-9]\d*(?:(?:, fix for review-code(?:-r[2-9]|-r[1-9]\d+)?\.md)|(?:, decision II-[1-9]\d*))?\))$/;
8
6
 
9
7
  type LogEntry = { time: string; step: string; agent: string; note: string };
10
8
  type StepRow = { step: string; agent: string; started: string; done: string; note: string; attempt?: string };
11
9
  type ActivityLogSection = { heading: string; body: string; entries: LogEntry[] };
12
- type CommitAttempt = Readonly<{ attempt: string; baseline: string; agent: string }>;
13
10
 
14
11
  function parseActivityLog(content: string): { sectionFound: boolean; entries: LogEntry[] } {
15
12
  const section = locateActivityLog(content);
@@ -37,6 +34,13 @@ function inspectActivityLog(content: string): { section: ActivityLogSection | nu
37
34
  return { section: { heading: heading.text, body, entries }, invalidEntries };
38
35
  }
39
36
 
37
+ function activityWarnings(content: string): { code: string; message: string }[] {
38
+ const inspected = inspectActivityLog(content);
39
+ if (!inspected.section) return [{ code: 'ACTIVITY_LOG_UNAVAILABLE', message: 'Activity Log is missing or ambiguous; repair the section to resume logging' }];
40
+ if (inspected.invalidEntries.length) return [{ code: 'ACTIVITY_LOG_INVALID_ENTRIES', message: 'Activity Log contains malformed entries; repair the entries' }];
41
+ return [];
42
+ }
43
+
40
44
  function locateActivityLog(content: string): ActivityLogSection | null {
41
45
  const { section } = inspectActivityLog(content);
42
46
  if (!section) return null;
@@ -53,15 +57,6 @@ function activityAttempt(note: string): string | null {
53
57
  return ATTEMPT_RE.exec(note)?.[1] ?? null;
54
58
  }
55
59
 
56
- function parseCommitAttemptStarted(note: string): CommitAttempt | null {
57
- const match = COMMIT_STARTED_RE.exec(note);
58
- return match ? { attempt: match[1]!, baseline: match[2]!, agent: match[3]! } : null;
59
- }
60
-
61
- function commitAttemptStartedNote(attempt: CommitAttempt): string {
62
- return `started; attempt=${attempt.attempt}; baseline=${attempt.baseline}; agent=${attempt.agent}`;
63
- }
64
-
65
60
  function pairEntries(entries: LogEntry[]): StepRow[] {
66
61
  const rows: StepRow[] = [];
67
62
  const open = new Map<string, StepRow[]>();
@@ -101,22 +96,14 @@ function startedBackedRows(rows: StepRow[]): StepRow[] {
101
96
  return rows.filter((row) => row.started !== '');
102
97
  }
103
98
 
104
- function hasOpenLifecycleExecution(content: string): boolean {
105
- const section = locateActivityLog(content);
106
- if (!section) return false;
107
- return pairEntries(section.entries).some((row) => row.done === '' && LIFECYCLE_STEP_RE.test(row.step));
108
- }
109
-
110
99
  export {
111
100
  activityAttempt,
101
+ activityWarnings,
112
102
  appendActivityEntry,
113
- hasOpenLifecycleExecution,
114
- commitAttemptStartedNote,
115
103
  inspectActivityLog,
116
104
  locateActivityLog,
117
105
  pairEntries,
118
106
  parseActivityLog,
119
- parseCommitAttemptStarted,
120
107
  startedBackedRows
121
108
  };
122
- export type { ActivityLogSection, CommitAttempt, LogEntry, StepRow };
109
+ export type { ActivityLogSection, LogEntry, StepRow };
@@ -98,7 +98,7 @@ export function executeArtifactCommand(
98
98
  matchingInitializedSkeleton = existing.includes(`<!-- artifact-context:${resolved.taskId}:${family}:${parsed.round} -->`);
99
99
  } catch { /* an absent candidate is created only for the selected identity */ }
100
100
  if ((context.status !== 'ready' && !matchingInitializedSkeleton) || (context.next?.name !== artifact
101
- && !hasOpenArtifactRound(fs.readFileSync(resolved.taskMdPath, 'utf8'), family, parsed.round)
101
+ && !hasOpenArtifactRound(taskDir, fs.readFileSync(resolved.taskMdPath, 'utf8'), family, parsed.round)
102
102
  && !matchingInitializedSkeleton)) {
103
103
  return fail('ARTIFACT_INIT_CONTEXT_INVALID', context.error?.message ?? `artifact '${artifact}' is not the next ${family} artifact`);
104
104
  }
@@ -4,13 +4,13 @@ import path from 'node:path';
4
4
  import { ARTIFACT_FAMILY_CATALOG, artifactName, parseArtifactName } from './artifact-name.ts';
5
5
  import { resolveTaskRef } from './resolve-ref.ts';
6
6
  import type { ResolveTaskRefErrorCode, TaskWorkspaceState } from './resolve-ref.ts';
7
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
7
+ import { lifecycleExecutions } from './lifecycle-execution.ts';
8
8
  import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.ts';
9
9
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
10
10
  import { parseVerdict } from './review-artifacts.ts';
11
11
  import { extractSection, findSectionHeading } from './sections.ts';
12
12
  import { receiptForOutput, receiptsForOutput, sha256File } from './artifact-receipts.ts';
13
- import { artifactCompletionOrder, hasArtifactCompletionFact, hasArtifactCompletionLog, parseCompletionFacts } from './completion-facts.ts';
13
+ import { hasArtifactCompletionRecord, hasValidArtifactCompletion } from './completion-facts.ts';
14
14
  import { inspectReviewIdentity } from './review-identity.ts';
15
15
  import type { ArtifactFamily, ArtifactFamilySpec } from './artifact-name.ts';
16
16
  import { parseLifecyclePathDecision } from './lifecycle-path.ts';
@@ -21,35 +21,29 @@ import type { ArtifactSelection } from './artifact-selection.ts';
21
21
  import { executionInputsMatchLatest } from './execution-inputs.ts';
22
22
 
23
23
  const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
24
- const ARTIFACT_STEPS: Readonly<Record<string, string>> = {
25
- analysis: 'Analyze Task', 'review-analysis': 'Review Analysis',
26
- plan: 'Plan Task', 'review-plan': 'Review Plan',
27
- code: 'Code Task', 'review-code': 'Review Code'
28
- };
29
-
30
24
  /** Init, repair and finalization share the same authoritative round identity. */
31
- export function hasOpenArtifactRound(content: string, family: string, round: number): boolean {
32
- return openArtifactRounds(content, family).includes(round);
25
+ export function hasOpenArtifactRound(taskDir: string, content: string, family: ArtifactFamily, round: number): boolean {
26
+ return openArtifactRounds(taskDir, content, family).includes(round);
33
27
  }
34
28
 
35
- function openArtifactRounds(content: string, family: string): number[] {
36
- const activity = locateActivityLog(content);
37
- const step = ARTIFACT_STEPS[family];
38
- if (!activity || !step) return [];
39
- const qualifier = family === 'code'
40
- ? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d*))?'
41
- : '';
42
- const expected = new RegExp(`^${step} \\(Round ([1-9]\\d*)${qualifier}\\)$`);
43
- return [...new Set(startedBackedRows(pairEntries(activity.entries))
44
- .filter((row) => expected.test(row.step) && !row.done)
45
- .map((row) => Number(expected.exec(row.step)?.[1])))].sort((left, right) => left - right);
29
+ function openArtifactRounds(taskDir: string, content: string, family: ArtifactFamily): number[] {
30
+ const executionFamily = family === 'analysis' ? 'analyze' : family;
31
+ const event = completionEvent(family);
32
+ return [...new Set(lifecycleExecutions(content)
33
+ .filter((item) => item.family === executionFamily && item.round !== undefined)
34
+ .flatMap((item) => {
35
+ const round = item.round!;
36
+ const name = artifactName(family, round);
37
+ if (!event || !hasArtifactCompletionRecord(content, name, event)) return [round];
38
+ if (!hasValidArtifactCompletion(content, taskDir, name, event)) throw new Error(`completion evidence for started ${family} Round ${round} is invalid`);
39
+ return [];
40
+ }))].sort((left, right) => left - right);
46
41
  }
47
42
 
48
- function hasCompletedArtifactRound(content: string, family: string, round: number, name: string): boolean {
43
+ function hasCompletedArtifactRound(taskDir: string, content: string, family: string, round: number, name: string): boolean {
49
44
  const event = completionEvent(family);
50
45
  return Boolean(event && parseArtifactName(name)?.round === round
51
- && artifactCompletionOrder(content, name, event) !== null
52
- && hasArtifactCompletionFact(content, name, event));
46
+ && hasValidArtifactCompletion(content, taskDir, name, event));
53
47
  }
54
48
 
55
49
  function completionEvent(family: string): string | null {
@@ -238,7 +232,14 @@ function inspectArtifactDirectory(
238
232
  }
239
233
  artifacts.sort((left, right) => left.round - right.round);
240
234
  knownArtifacts.sort((left, right) => left.round - right.round);
241
- const openRounds = openArtifactRounds(taskContent, family);
235
+ let openRounds: number[];
236
+ try { openRounds = openArtifactRounds(taskDir, taskContent, family); }
237
+ catch (error) {
238
+ return failure(requestRef, family, {
239
+ code: 'ARTIFACT_SELECTION_INDETERMINATE',
240
+ message: error instanceof Error ? error.message : String(error)
241
+ }, { taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null });
242
+ }
242
243
  const knownRounds = new Set(knownArtifacts.map((item) => item.round));
243
244
  for (const round of openRounds) knownRounds.add(round);
244
245
  const max = Math.max(...knownRounds, 0);
@@ -248,11 +249,8 @@ function inspectArtifactDirectory(
248
249
  }
249
250
  const event = completionEvent(family);
250
251
  const completed = event
251
- ? artifacts.flatMap((artifact) => {
252
- const order = artifactCompletionOrder(taskContent, artifact.name, event);
253
- return order !== null && hasArtifactCompletionFact(taskContent, artifact.path, event)
254
- ? [{ artifact, order }] : [];
255
- }).sort((left, right) => left.order - right.order).map(({ artifact }) => artifact)
252
+ ? artifacts.filter((artifact) => hasValidArtifactCompletion(taskContent, taskDir, artifact.name, event))
253
+ .sort((left, right) => left.round - right.round)
256
254
  : [];
257
255
  if (openRounds.length > 1) diagnostics.push(diagnostic('MULTIPLE_OPEN_ROUNDS', family, null, `multiple incomplete ${family} rounds are open: ${openRounds.join(', ')}`));
258
256
  const latest = completed.at(-1) ?? null;
@@ -387,8 +385,8 @@ function hasSelectionEvidence(
387
385
  const reportContent = fs.readFileSync(context.latest.path, 'utf8');
388
386
  const identity = inspectReviewIdentity(context.taskDir!, reportContent, options.repoRoot);
389
387
  return identity.status !== 'matched'
390
- || !hasArtifactCompletionFact(content, context.latest.path, 'review-code.completed')
391
- || !hasArtifactCompletionLog(content, context.latest.name, 'review-code.completed');
388
+ || !context.taskDir
389
+ || !hasValidArtifactCompletion(content, context.taskDir, context.latest.name, 'review-code.completed');
392
390
  } catch {
393
391
  return true;
394
392
  }
@@ -428,13 +426,10 @@ function attachArtifactSelection(
428
426
  ...context, status: 'failed', selection: null, error: { code, message }
429
427
  });
430
428
  if (!context.next) return fail('ARTIFACT_SELECTION_INDETERMINATE', 'artifact inventory did not provide a writable identity');
431
- let taskContent: string;
432
- try {
433
- taskContent = fs.readFileSync(path.join(context.taskDir, 'task.md'), 'utf8');
434
- } catch (error) {
435
- return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
436
- }
437
- const openRounds = openArtifactRounds(taskContent, family);
429
+ const state = readArtifactSelectionState(context.taskDir, family);
430
+ if (!state.ok) return fail('ARTIFACT_SELECTION_INDETERMINATE', state.message);
431
+ const taskContent = state.content;
432
+ const openRounds = state.openRounds;
438
433
  if (openRounds.length > 1) return fail('ARTIFACT_TOPOLOGY_CONFLICT', `multiple incomplete ${family} rounds are open: ${openRounds.join(', ')}`);
439
434
  const openRound = openRounds[0] ?? null;
440
435
  const selectedIdentity = openRound === null ? context.next : {
@@ -459,6 +454,17 @@ function attachArtifactSelection(
459
454
  }
460
455
  }
461
456
 
457
+ function readArtifactSelectionState(taskDir: string, family: ArtifactFamily):
458
+ | { ok: true; content: string; openRounds: number[] }
459
+ | { ok: false; message: string } {
460
+ try {
461
+ const content = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
462
+ return { ok: true, content, openRounds: openArtifactRounds(taskDir, content, family) };
463
+ } catch (error) {
464
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
465
+ }
466
+ }
467
+
462
468
  function resolveArtifactContextFromTask(
463
469
  taskRef: string,
464
470
  family: string,
@@ -531,14 +537,6 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
531
537
  return resolveArtifactContextFromTask(taskRef, family, options);
532
538
  }
533
539
 
534
- export function resolveArtifactContextForResolvedTask(
535
- resolvedTask: ResolvedTaskRef,
536
- family: string,
537
- options: InspectOptions = {}
538
- ): ArtifactContextResult {
539
- return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
540
- }
541
-
542
540
  function resolveCodeContext(
543
541
  inventory: ArtifactInventoryResult,
544
542
  options: InspectOptions,
@@ -555,8 +553,7 @@ function resolveCodeContext(
555
553
  const taskContent = fs.readFileSync(path.join(inventory.taskDir!, 'task.md'), 'utf8');
556
554
  const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
557
555
  if (hasPendingImplementation && !reviewCode.latest) {
558
- const candidate = reviewCode.artifacts.at(-1)?.name ?? 'review-code.md';
559
- return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `cannot find completed Activity Log identity for ${candidate}`);
556
+ return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', 'no valid review-code completion fact is available for pending implementation inputs');
560
557
  }
561
558
  } catch (error) {
562
559
  return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', error instanceof Error ? error.message : String(error));
@@ -575,12 +572,10 @@ function resolveCodeContext(
575
572
  const review = reviewCode.latest;
576
573
  const openInputs = review ? [...inputs, review] : inputs;
577
574
  const openVerdict = review ? parseVerdict(review.path) : null;
578
- const openRow = locateActivityLog(taskContent)
579
- ? startedBackedRows(pairEntries(locateActivityLog(taskContent)!.entries))
580
- .find((row) => row.step.startsWith(`Code Task (Round ${openRound}`) && !row.done)
581
- : null;
582
- const decisionInput = /, decision (II-[1-9]\d*)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
583
- const fixInput = /, fix for (review-code(?:-r\d+)?\.md)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
575
+ const openRow = lifecycleExecutions(taskContent)
576
+ .find((row) => row.family === 'code' && row.round === openRound);
577
+ const decisionInput = openRow?.implementationInput ?? null;
578
+ const fixInput = openRow?.fixFor ?? null;
584
579
  const mode = decisionInput ? 'decision'
585
580
  : fixInput || (openVerdict?.ok && openVerdict.verdict === 'Changes Requested') ? 'fix'
586
581
  : 'init';
@@ -617,7 +612,7 @@ function resolveCodeContext(
617
612
  if (!review) return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', 'latest review-code artifact is required');
618
613
  const verdict = parseVerdict(review.path);
619
614
  if (!verdict.ok) return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, review.name);
620
- const decision = resolveDecisionImplementationInput(inventory, review);
615
+ const decision = resolveDecisionImplementationInput(reviewCode, review);
621
616
  if ('error' in decision) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', decision.error, review.name);
622
617
  if (decision.input) {
623
618
  return withCodeMode(
@@ -680,12 +675,10 @@ function resolveDecisionImplementationInput(
680
675
  catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
681
676
  const pending = rows.filter((row) => row.needsImplementation && row.status === 'pending');
682
677
  if (pending.length === 0) return { input: null };
683
- const activity = locateActivityLog(content);
684
- if (!activity) return { error: 'task has no unique Activity Log section' };
685
- const completed = activity.entries.some((entry) =>
686
- /^Review Code \(Round \d+\)$/.test(entry.step) && entry.note.includes(`→ ${review.name}`)
687
- );
688
- if (!completed) return { error: `cannot find completed Activity Log identity for ${review.name}` };
678
+ if (inventory.latest?.name !== review.name
679
+ || !inventory.completed.some((artifact) => artifact.name === review.name)) {
680
+ return { error: `Review Code completion facts do not validate ${review.name}` };
681
+ }
689
682
  try { return { input: selectPendingImplementationInput(rows) }; }
690
683
  catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
691
684
  }
@@ -720,14 +713,12 @@ export function validateArtifactPublication(taskDir: string, family: ArtifactFam
720
713
  if (existing) {
721
714
  try {
722
715
  const task = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
723
- if (hasCompletedArtifactRound(task, family, existing.round, name)) {
716
+ if (hasCompletedArtifactRound(taskDir, task, family, existing.round, name)) {
724
717
  return { code: 'ARTIFACT_IDENTITY_INVALID', message: `completed artifact '${name}' is immutable` };
725
718
  }
726
719
  const event = completionEvent(family);
727
- const frontmatter = parseTypedTaskFrontmatter(task);
728
- const completionFactExists = event !== null && parseCompletionFacts(frontmatter.completion_facts)
729
- .some((fact) => fact.event === event && fact.output === name);
730
- if (event && completionFactExists && artifactCompletionOrder(task, name, event) !== null) {
720
+ const completionFactExists = event !== null && hasArtifactCompletionRecord(task, name, event);
721
+ if (event && completionFactExists) {
731
722
  return { code: 'ARTIFACT_IDENTITY_INVALID', message: `completion evidence for '${name}' does not match its current content` };
732
723
  }
733
724
  } catch (error) {
@@ -44,12 +44,5 @@ export function parseArtifactName(name: string): { family: ArtifactFamily; round
44
44
  return { family: match[1] as ArtifactFamily, round, name };
45
45
  }
46
46
 
47
- export function maxArtifactRound(names: readonly string[], family: string): number {
48
- return names.reduce((max, name) => {
49
- const identity = parseArtifactName(name);
50
- return identity?.family === family ? Math.max(max, identity.round) : max;
51
- }, 0);
52
- }
53
-
54
47
  export { ARTIFACT_FAMILY_CATALOG };
55
48
  export type { ArtifactFamily, ArtifactFamilySpec };