@fitlab-ai/agent-infra 0.11.14 → 0.12.0

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 (271) hide show
  1. package/bin/cli.ts +1 -1
  2. package/dist/bin/cli.js +1 -1
  3. package/dist/bin/internal-cli.js +6 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
  6. package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
  7. package/dist/lib/decide.js +2 -7
  8. package/dist/lib/internal/cli-route-inventory.js +3 -0
  9. package/dist/lib/internal/manual-validation.js +8 -16
  10. package/dist/lib/internal/platform-comment.js +3 -2
  11. package/dist/lib/internal/task-activity.js +1 -12
  12. package/dist/lib/internal/task-event.js +1 -12
  13. package/dist/lib/internal/task-finalization.js +2 -2
  14. package/dist/lib/internal/task-ledger.js +1 -12
  15. package/dist/lib/internal/task-operation-registry.js +2 -1
  16. package/dist/lib/internal/task-qualification.js +6 -15
  17. package/dist/lib/internal/task-review.js +3 -16
  18. package/dist/lib/internal/task-short-id.js +2 -1
  19. package/dist/lib/internal/task-verify.js +1 -11
  20. package/dist/lib/internal/task-warning.js +1 -12
  21. package/dist/lib/internal/task-workspace.js +43 -0
  22. package/dist/lib/merge.js +4 -3
  23. package/dist/lib/platform/pr-change-report.js +10 -3
  24. package/dist/lib/platform/pr-summary.js +6 -15
  25. package/dist/lib/platform/pull-requests.js +3 -15
  26. package/dist/lib/pr-review/evidence-grading.js +3 -2
  27. package/dist/lib/process-data/index.js +18 -6
  28. package/dist/lib/process-data/sources.js +6 -0
  29. package/dist/lib/run/index.js +3 -2
  30. package/dist/lib/sandbox/commands/create.js +5 -1
  31. package/dist/lib/sandbox/commands/enter.js +1 -1
  32. package/dist/lib/sandbox/commands/list-running.js +2 -1
  33. package/dist/lib/sandbox/control/client.js +8 -13
  34. package/dist/lib/sandbox/control/completed-reentry.js +2 -1
  35. package/dist/lib/sandbox/control/lifecycle.js +2 -21
  36. package/dist/lib/sandbox/control/task-view.js +4 -2
  37. package/dist/lib/sandbox/mounts.js +4 -4
  38. package/dist/lib/sandbox/recovery.js +26 -16
  39. package/dist/lib/sandbox/removal.js +3 -10
  40. package/dist/lib/sandbox/task-resolver.js +9 -6
  41. package/dist/lib/sandbox/workspace-identity.js +36 -20
  42. package/dist/lib/sandbox/workspace-view.js +29 -22
  43. package/dist/lib/task/activity-intent.js +2 -12
  44. package/dist/lib/task/artifact-lifecycle.js +17 -11
  45. package/dist/lib/task/artifact-operations.js +41 -49
  46. package/dist/lib/task/capabilities.js +2 -2
  47. package/dist/lib/task/commands/ls.js +4 -3
  48. package/dist/lib/task/commands/status.js +1 -1
  49. package/dist/lib/task/commit-operation.js +6 -6
  50. package/dist/lib/task/control-authority.js +32 -32
  51. package/dist/lib/task/create-service.js +2 -4
  52. package/dist/lib/task/create.js +12 -65
  53. package/dist/lib/task/delegation-receipts.js +1 -1
  54. package/dist/lib/task/delivery.js +2 -8
  55. package/dist/lib/task/events.js +5 -31
  56. package/dist/lib/task/finalization.js +17 -22
  57. package/dist/lib/task/lifecycle.js +38 -49
  58. package/dist/lib/task/local-artifact-finalization.js +1 -2
  59. package/dist/lib/task/orchestration.js +24 -57
  60. package/dist/lib/task/platform-operation-recovery.js +1 -2
  61. package/dist/lib/task/resolve-ref.js +17 -12
  62. package/dist/lib/task/review-finalization.js +2 -35
  63. package/dist/lib/task/review-identity.js +3 -1
  64. package/dist/lib/task/short-id.js +12 -10
  65. package/dist/lib/task/verification-engine.js +19 -58
  66. package/dist/lib/task/workspace-root.js +59 -0
  67. package/dist/lib/task/workspace-snapshot.js +4 -3
  68. package/dist/lifecycle-build-manifest.json +3 -3
  69. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
  70. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
  71. package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
  72. package/lib/decide.ts +2 -7
  73. package/lib/internal/cli-route-inventory.ts +2 -0
  74. package/lib/internal/manual-validation.ts +8 -19
  75. package/lib/internal/platform-comment.ts +3 -2
  76. package/lib/internal/task-activity.ts +1 -10
  77. package/lib/internal/task-event.ts +1 -10
  78. package/lib/internal/task-finalization.ts +2 -2
  79. package/lib/internal/task-ledger.ts +1 -10
  80. package/lib/internal/task-operation-registry.ts +2 -1
  81. package/lib/internal/task-qualification.ts +5 -11
  82. package/lib/internal/task-review.ts +3 -14
  83. package/lib/internal/task-short-id.ts +2 -1
  84. package/lib/internal/task-verify.ts +1 -9
  85. package/lib/internal/task-warning.ts +1 -10
  86. package/lib/internal/task-workspace.ts +39 -0
  87. package/lib/merge.ts +4 -3
  88. package/lib/platform/pr-change-report.ts +9 -3
  89. package/lib/platform/pr-summary.ts +7 -16
  90. package/lib/platform/pull-requests.ts +3 -24
  91. package/lib/pr-review/evidence-grading.ts +3 -2
  92. package/lib/process-data/index.ts +17 -5
  93. package/lib/process-data/sources.ts +6 -0
  94. package/lib/run/index.ts +3 -2
  95. package/lib/sandbox/commands/create.ts +5 -1
  96. package/lib/sandbox/commands/enter.ts +1 -1
  97. package/lib/sandbox/commands/list-running.ts +2 -1
  98. package/lib/sandbox/control/client.ts +7 -12
  99. package/lib/sandbox/control/completed-reentry.ts +2 -1
  100. package/lib/sandbox/control/lifecycle.ts +4 -21
  101. package/lib/sandbox/control/task-view.ts +4 -2
  102. package/lib/sandbox/mounts.ts +4 -4
  103. package/lib/sandbox/recovery.ts +26 -18
  104. package/lib/sandbox/removal.ts +3 -26
  105. package/lib/sandbox/task-resolver.ts +9 -6
  106. package/lib/sandbox/workspace-identity.ts +42 -18
  107. package/lib/sandbox/workspace-view.ts +28 -20
  108. package/lib/task/activity-intent.ts +3 -17
  109. package/lib/task/artifact-lifecycle.ts +39 -11
  110. package/lib/task/artifact-operations.ts +28 -35
  111. package/lib/task/capabilities.ts +2 -2
  112. package/lib/task/commands/ls.ts +4 -3
  113. package/lib/task/commands/status.ts +1 -1
  114. package/lib/task/commit-operation.ts +5 -6
  115. package/lib/task/control-authority.ts +29 -35
  116. package/lib/task/create-service.ts +2 -4
  117. package/lib/task/create.ts +10 -51
  118. package/lib/task/delegation-receipts.ts +1 -1
  119. package/lib/task/delivery.ts +2 -10
  120. package/lib/task/events.ts +5 -32
  121. package/lib/task/finalization.ts +16 -24
  122. package/lib/task/lifecycle.ts +40 -51
  123. package/lib/task/local-artifact-finalization.ts +1 -4
  124. package/lib/task/orchestration.ts +31 -80
  125. package/lib/task/platform-operation-recovery.ts +1 -2
  126. package/lib/task/resolve-ref.ts +18 -12
  127. package/lib/task/review-finalization.ts +2 -52
  128. package/lib/task/review-identity.ts +3 -1
  129. package/lib/task/short-id.ts +15 -10
  130. package/lib/task/verification-engine.ts +20 -62
  131. package/lib/task/workspace-root.ts +78 -0
  132. package/lib/task/workspace-snapshot.ts +4 -3
  133. package/package.json +1 -1
  134. package/templates/.agents/QUICKSTART.en.md +19 -8
  135. package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
  136. package/templates/.agents/README.en.md +10 -15
  137. package/templates/.agents/README.zh-CN.md +9 -13
  138. package/templates/.agents/rules/issue-sync.en.md +1 -1
  139. package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
  140. package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
  141. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
  142. package/templates/.agents/rules/next-step-output.en.md +2 -2
  143. package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
  144. package/templates/.agents/rules/pr-sync.en.md +1 -1
  145. package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
  146. package/templates/.agents/rules/release-commands.en.md +3 -0
  147. package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
  148. package/templates/.agents/rules/task-management.en.md +0 -2
  149. package/templates/.agents/rules/task-management.zh-CN.md +0 -2
  150. package/templates/.agents/rules/task-short-id.en.md +2 -2
  151. package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
  152. package/templates/.agents/scripts/task-short-id.js +2 -8
  153. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  154. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  155. package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
  156. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
  157. package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
  158. package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
  159. package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
  160. package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
  161. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
  162. package/templates/.agents/skills/block-task/config/verify.json +0 -1
  163. package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
  164. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
  165. package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
  166. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  167. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  168. package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
  169. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
  170. package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
  171. package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
  172. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  173. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  174. package/templates/.agents/skills/commit/config/verify.json +0 -1
  175. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
  176. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
  177. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  178. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  179. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  180. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  181. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
  182. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
  183. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
  184. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
  185. package/templates/.agents/skills/complete-task/SKILL.en.md +17 -9
  186. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +18 -10
  187. package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
  188. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
  189. package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
  190. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
  191. package/templates/.agents/skills/create-pr/config/verify.json +0 -1
  192. package/templates/.agents/skills/create-pr/reference/change-report.en.md +13 -3
  193. package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +13 -3
  194. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
  195. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
  196. package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
  197. package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
  198. package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
  199. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
  200. package/templates/.agents/skills/create-task/config/verify.json +0 -1
  201. package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
  202. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  203. package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
  204. package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
  205. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
  206. package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
  207. package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
  208. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
  209. package/templates/.agents/skills/import-issue/config/verify.json +0 -1
  210. package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
  211. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
  212. package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
  213. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
  214. package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
  215. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
  216. package/templates/.agents/skills/restore-task/config/verify.json +0 -1
  217. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  218. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  219. package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
  220. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
  221. package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
  222. package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
  223. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  224. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  225. package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
  226. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
  227. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  228. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  229. package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
  230. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
  231. package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
  232. package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
  233. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
  234. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
  235. package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
  236. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
  237. package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
  238. package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
  239. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +5 -5
  240. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -5
  241. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
  242. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
  243. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +13 -2
  244. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +13 -2
  245. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  246. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  247. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  248. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  249. package/templates/.agents/skills/test/SKILL.en.md +0 -3
  250. package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
  251. package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
  252. package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
  253. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
  254. package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
  255. package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
  256. package/templates/.agents/skills/watch-pr/SKILL.en.md +21 -4
  257. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +22 -5
  258. package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
  259. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +14 -0
  260. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +15 -1
  261. package/templates/.agents/templates/handoff.en.md +1 -1
  262. package/templates/.agents/templates/handoff.zh-CN.md +1 -1
  263. package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
  264. package/templates/.agents/workflows/feature-development.en.yaml +1 -1
  265. package/templates/.agents/workflows/refactoring.en.yaml +1 -1
  266. package/dist/lib/runtime/agent-runtime.js +0 -33
  267. package/dist/lib/task/runtime-paths.js +0 -10
  268. package/dist/lib/task/task-execution-lock.js +0 -250
  269. package/lib/runtime/agent-runtime.ts +0 -41
  270. package/lib/task/runtime-paths.ts +0 -12
  271. package/lib/task/task-execution-lock.ts +0 -320
@@ -17,7 +17,6 @@ import { acquireSandboxResourceLock } from './control/native-file-lock.js';
17
17
  import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
18
18
  import { getProcessStartTime } from '../server/process-state.js';
19
19
  import { releaseStaleShortIdRegistry } from '../task/short-id.js';
20
- import { withRepositoryMutationLock } from '../task/task-execution-lock.js';
21
20
  import { fetchSandboxRows } from './commands/list-running.js';
22
21
  import { createSandboxControlBindingEvidence } from './task-cleanup.js';
23
22
  import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees, verifyWorktreePermit } from './worktree-safety.js';
@@ -665,12 +664,12 @@ async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, {
665
664
  }
666
665
  return permits;
667
666
  }
668
- async function runRmOneUnderRepositoryLock(config, tools, branch, options = {}) {
667
+ async function runRmOne(config, tools, branch, options = {}) {
669
668
  const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
670
669
  return rmOneCore(config, tools, branch, { ...options, target });
671
670
  }
672
671
  async function rmOne(config, tools, branch, options = {}) {
673
- await withRepositoryMutationLock(config.repoRoot, () => runRmOneUnderRepositoryLock(config, tools, branch, options));
672
+ await runRmOne(config, tools, branch, options);
674
673
  }
675
674
  async function removeUncheckedSandbox(config, target, options) {
676
675
  const confirm = options.prompt?.confirm ?? p.confirm;
@@ -751,9 +750,6 @@ async function rmOneCore(config, tools, branch, options = {}) {
751
750
  return removeUncheckedSandbox(config, target, options);
752
751
  }
753
752
  async function rmPurge(config, tools, prompt = {}) {
754
- return withRepositoryMutationLock(config.repoRoot, () => rmPurgeCore(config, tools, prompt));
755
- }
756
- async function rmPurgeCore(config, tools, prompt = {}) {
757
753
  const engine = detectEngine(config);
758
754
  const confirm = prompt.confirm ?? p.confirm;
759
755
  const isCancel = prompt.isCancel ?? p.isCancel;
@@ -829,9 +825,6 @@ async function rmPurgeCore(config, tools, prompt = {}) {
829
825
  p.outro(pc.green('All project sandboxes removed'));
830
826
  }
831
827
  async function rmUnbound(config, tools, options) {
832
- return withRepositoryMutationLock(config.repoRoot, () => rmUnboundCore(config, tools, options));
833
- }
834
- async function rmUnboundCore(config, tools, options) {
835
828
  const engine = detectEngine(config);
836
829
  const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
837
830
  const discovered = [...listed.running, ...listed.nonRunning];
@@ -892,7 +885,7 @@ async function rmUnboundCore(config, tools, options) {
892
885
  return;
893
886
  }
894
887
  for (const { row, cleanupTarget, target } of targets) {
895
- await runRmOneUnderRepositoryLock(config, tools, cleanupTarget.branch, {
888
+ await runRmOne(config, tools, cleanupTarget.branch, {
896
889
  assumeYes: options.assumeYes,
897
890
  target,
898
891
  cleanupTarget
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { isRemovedHashShortIdInput } from '../task/short-id.js';
5
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
5
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
6
7
  const SHORT_ID_RE = /^\d+$/;
7
8
  export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'];
@@ -20,10 +21,11 @@ function stripQuotes(value) {
20
21
  return value.replace(/^(["'])(.*)\1$/, '$2');
21
22
  }
22
23
  function readTaskContent(repoRoot, taskId) {
24
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
23
25
  for (const state of TASK_WORKSPACE_STATES) {
24
26
  const taskPath = state === 'archive'
25
- ? path.join(repoRoot, '.agents', 'workspace', state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
26
- : path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
27
+ ? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
28
+ : path.join(taskRoot, state, taskId, 'task.md');
27
29
  if (fs.existsSync(taskPath)) {
28
30
  return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
29
31
  }
@@ -31,8 +33,9 @@ function readTaskContent(repoRoot, taskId) {
31
33
  throw new Error(`Task not found: ${taskId}`);
32
34
  }
33
35
  function taskWorkspaceCandidates(repoRoot, taskId) {
36
+ const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
34
37
  const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
35
- const taskMd = path.join(repoRoot, '.agents', 'workspace', state, taskId, 'task.md');
38
+ const taskMd = path.join(taskRoot, state, taskId, 'task.md');
36
39
  if (!fs.existsSync(taskMd))
37
40
  return [];
38
41
  const content = fs.readFileSync(taskMd, 'utf8');
@@ -46,7 +49,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
46
49
  const parts = findArchiveTaskParts(repoRoot, taskId);
47
50
  if (parts.length === 0)
48
51
  return hot;
49
- const taskMd = path.join(repoRoot, '.agents', 'workspace', 'archive', ...parts, 'local', 'task.md');
52
+ const taskMd = path.join(taskRoot, 'archive', ...parts, 'local', 'task.md');
50
53
  if (!fs.existsSync(taskMd))
51
54
  return hot;
52
55
  const content = fs.readFileSync(taskMd, 'utf8');
@@ -58,7 +61,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
58
61
  }];
59
62
  }
60
63
  function findArchiveTaskParts(repoRoot, taskId) {
61
- const archiveRoot = path.join(repoRoot, '.agents', 'workspace', 'archive');
64
+ const archiveRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'archive');
62
65
  if (!fs.existsSync(archiveRoot))
63
66
  return [];
64
67
  for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
@@ -113,7 +116,7 @@ export function resolveTaskWorkspace(taskId, repoRoot) {
113
116
  return matches[0];
114
117
  }
115
118
  export function listTaskWorkspaces(repoRoot, state) {
116
- const stateRoot = path.join(repoRoot, '.agents', 'workspace', state);
119
+ const stateRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
117
120
  if (state === 'archive') {
118
121
  if (!fs.existsSync(stateRoot))
119
122
  return [];
@@ -1,8 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { isRemovedHashShortIdInput } from '../task/short-id.js';
4
- import { resolveTaskRef } from '../task/resolve-ref.js';
5
- import { resolveTaskWorkspace } from './task-resolver.js';
3
+ import { configuredShortIdLength, inspectShortIdRegistry, isRemovedHashShortIdInput, normalizeShortIdInput } from '../task/short-id.js';
4
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
5
+ import { TASK_WORKSPACE_STATES, listTaskWorkspaces, resolveTaskWorkspace } from './task-resolver.js';
6
6
  const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
7
7
  const SHORT_ID_RE = /^\d+$/;
8
8
  function stripQuotes(value) {
@@ -17,7 +17,7 @@ function taskBranch(taskMd, taskId) {
17
17
  return stripQuotes(value);
18
18
  }
19
19
  function activeRegistry(repoRoot) {
20
- const registryPath = path.join(repoRoot, '.agents', 'workspace', 'active', '.short-ids.json');
20
+ const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
21
21
  if (!fs.existsSync(registryPath))
22
22
  return {};
23
23
  const value = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
@@ -30,7 +30,7 @@ function activeRegistry(repoRoot) {
30
30
  return Object.fromEntries(Object.entries(ids).filter((entry) => SHORT_ID_RE.test(entry[0]) && typeof entry[1] === 'string' && TASK_ID_RE.test(entry[1])));
31
31
  }
32
32
  function taskIdentity(repoRoot, taskId, registry) {
33
- const taskMd = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
33
+ const taskMd = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
34
34
  if (!fs.existsSync(taskMd))
35
35
  throw new Error(`Active task not found: ${taskId}`);
36
36
  const shortIds = Object.entries(registry).filter(([, id]) => id === taskId).map(([shortId]) => shortId);
@@ -42,12 +42,30 @@ function taskIdentity(repoRoot, taskId, registry) {
42
42
  function cleanupTaskId(requestedRef, repoRoot) {
43
43
  if (TASK_ID_RE.test(requestedRef))
44
44
  return requestedRef;
45
- if (!SHORT_ID_RE.test(requestedRef))
45
+ const shortIdLength = configuredShortIdLength(repoRoot);
46
+ const normalized = normalizeShortIdInput(requestedRef, { shortIdLength });
47
+ if (normalized.kind === 'error')
48
+ throw new Error(normalized.message);
49
+ if (normalized.kind !== 'shortId')
46
50
  return null;
47
- const task = resolveTaskRef(requestedRef, { repoRoot });
48
- if (!task.ok)
49
- throw new Error(task.message);
50
- return task.taskId;
51
+ const registry = inspectShortIdRegistry(repoRoot, shortIdLength);
52
+ if (registry.status !== 'valid')
53
+ throw new Error(registry.error.message);
54
+ const match = [...registry.shortIds].find(([, shortId]) => shortId === normalized.value);
55
+ if (!match) {
56
+ throw new Error(`Short id '${normalized.value}' not found in active task registry`);
57
+ }
58
+ return match[0];
59
+ }
60
+ function cleanupTaskIdByBranch(requestedRef, repoRoot) {
61
+ const taskIds = new Set(TASK_WORKSPACE_STATES
62
+ .flatMap((state) => listTaskWorkspaces(repoRoot, state))
63
+ .filter((task) => task.branch === requestedRef)
64
+ .map((task) => task.taskId));
65
+ if (taskIds.size > 1) {
66
+ throw new Error(`SANDBOX_TASK_IDENTITY_AMBIGUOUS: branch '${requestedRef}' is bound to multiple tasks`);
67
+ }
68
+ return taskIds.values().next().value ?? null;
51
69
  }
52
70
  export function resolveSandboxTarget(requestedRef, repoRoot) {
53
71
  if (isRemovedHashShortIdInput(requestedRef)) {
@@ -64,13 +82,13 @@ export function resolveSandboxTarget(requestedRef, repoRoot) {
64
82
  if (!taskId)
65
83
  throw new Error(`Short id '${requestedRef}' not found in active task registry`);
66
84
  const workspace = taskIdentity(repoRoot, taskId, registry);
67
- return { requestedRef, branch: taskBranch(path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md'), taskId), workspace };
85
+ return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md'), taskId), workspace };
68
86
  }
69
87
  if (TASK_ID_RE.test(requestedRef)) {
70
88
  const workspace = taskIdentity(repoRoot, requestedRef, registry);
71
- return { requestedRef, branch: taskBranch(path.join(repoRoot, '.agents', 'workspace', 'active', requestedRef, 'task.md'), requestedRef), workspace };
89
+ return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', requestedRef, 'task.md'), requestedRef), workspace };
72
90
  }
73
- const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
91
+ const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
74
92
  const matches = fs.existsSync(activeRoot)
75
93
  ? fs.readdirSync(activeRoot, { withFileTypes: true }).flatMap((entry) => {
76
94
  if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name))
@@ -94,7 +112,8 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
94
112
  if (isRemovedHashShortIdInput(requestedRef)) {
95
113
  throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
96
114
  }
97
- const taskId = cleanupTaskId(requestedRef, repoRoot);
115
+ const taskId = cleanupTaskId(requestedRef, repoRoot)
116
+ ?? cleanupTaskIdByBranch(requestedRef, repoRoot);
98
117
  if (taskId) {
99
118
  let task;
100
119
  try {
@@ -123,14 +142,11 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
123
142
  taskState: task.state
124
143
  };
125
144
  }
126
- const target = resolveSandboxTarget(requestedRef, repoRoot);
127
145
  return {
128
146
  requestedRef,
129
- branch: target.branch,
130
- workspace: target.workspace.mode === 'task-bound'
131
- ? { mode: 'task-bound', taskId: target.workspace.taskId }
132
- : { mode: 'branch-only' },
133
- taskState: target.workspace.mode === 'task-bound' ? 'active' : 'branch-only'
147
+ branch: requestedRef,
148
+ workspace: { mode: 'branch-only' },
149
+ taskState: 'branch-only'
134
150
  };
135
151
  }
136
152
  export function parseSandboxWorkspaceIdentity(labels, keys) {
@@ -1,6 +1,8 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
5
+ import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
4
6
  import { resolveTaskWorkspace } from './task-resolver.js';
5
7
  import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
6
8
  export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
@@ -101,7 +103,8 @@ function assertSafeMountTargetPath(target, worktreeRoot, canonicalWorktreeRoot)
101
103
  export function assertSandboxTaskSource(repoRoot, taskId) {
102
104
  if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
103
105
  throw new Error('SANDBOX_TASK_SOURCE_INVALID');
104
- const activeSource = path.join(repoRoot, '.agents', 'workspace', 'active', taskId);
106
+ const workspaceRootPath = resolveProjectTaskWorkspaceRoot(repoRoot);
107
+ const activeSource = path.join(workspaceRootPath, 'active', taskId);
105
108
  try {
106
109
  const stat = fs.lstatSync(activeSource);
107
110
  if (!stat.isDirectory() || stat.isSymbolicLink()) {
@@ -109,7 +112,7 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
109
112
  }
110
113
  const canonical = fs.realpathSync.native(activeSource);
111
114
  const activeRoot = fs.realpathSync.native(path.dirname(activeSource));
112
- const workspaceRoot = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace'));
115
+ const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
113
116
  const relative = path.relative(workspaceRoot, canonical);
114
117
  if (path.dirname(canonical) !== activeRoot || relative.startsWith('..') || path.isAbsolute(relative)) {
115
118
  throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${activeSource} escapes the task workspace`);
@@ -129,39 +132,46 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
129
132
  throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} must be a real directory`);
130
133
  }
131
134
  const canonical = fs.realpathSync.native(source);
132
- const workspaceRoot = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace'));
135
+ const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
133
136
  const relative = path.relative(workspaceRoot, canonical);
134
137
  if (path.basename(canonical) !== taskId || relative.startsWith('..') || path.isAbsolute(relative)) {
135
138
  throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} escapes the task workspace`);
136
139
  }
137
140
  return canonical;
138
141
  }
139
- export function prepareSandboxWorkspaceMountTargets(worktreeRoot, taskId) {
140
- const resolvedWorktreeRoot = path.resolve(worktreeRoot);
141
- const canonicalWorktreeRoot = fs.realpathSync.native(resolvedWorktreeRoot);
142
- const workspaceRoot = path.resolve(resolvedWorktreeRoot, '.agents', 'workspace');
142
+ export function prepareSandboxWorkspaceMountTargets(repoRoot) {
143
+ const resolvedHome = path.resolve(process.env.HOME || os.homedir());
144
+ const canonicalHome = fs.realpathSync.native(resolvedHome);
145
+ const workspaceRoot = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot));
146
+ const relativeRoot = path.relative(resolvedHome, workspaceRoot);
147
+ if (relativeRoot.startsWith('..') || path.isAbsolute(relativeRoot)) {
148
+ throw new Error(`Sandbox workspace mount target escapes the host home: ${workspaceRoot}`);
149
+ }
143
150
  const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
144
151
  const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
145
- const completedTaskTarget = taskId ? path.join(workspaceRoot, 'completed', taskId) : null;
146
- if (taskId && !/^TASK-\d{8}-\d{6}$/.test(taskId))
147
- throw new Error('SANDBOX_TASK_SOURCE_INVALID');
148
- for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget, ...(completedTaskTarget ? [completedTaskTarget] : [])]) {
149
- assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
152
+ for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget]) {
153
+ assertSafeMountTargetPath(target, resolvedHome, canonicalHome);
150
154
  }
151
155
  fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
152
156
  for (const { hostPath } of statePaths) {
153
157
  fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
154
158
  fs.chmodSync(hostPath, 0o700);
155
159
  }
156
- if (completedTaskTarget)
157
- fs.mkdirSync(completedTaskTarget, { recursive: true, mode: 0o700 });
158
160
  fs.chmodSync(workspaceRoot, 0o700);
159
- fs.closeSync(fs.openSync(registryTarget, 'a', 0o600));
161
+ const registryFd = fs.openSync(registryTarget, 'a', 0o600);
162
+ try {
163
+ if (fs.fstatSync(registryFd).size === 0) {
164
+ fs.writeFileSync(registryFd, `${JSON.stringify({ version: 1, ids: {} })}\n`);
165
+ }
166
+ }
167
+ finally {
168
+ fs.closeSync(registryFd);
169
+ }
160
170
  }
161
171
  export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
162
172
  if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
163
173
  throw new Error('SANDBOX_TASK_SOURCE_INVALID');
164
- const workspaceRoot = path.resolve(repoRoot, '.agents', 'workspace');
174
+ const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
165
175
  fs.mkdirSync(path.join(workspaceRoot, 'completed'), { recursive: true, mode: 0o700 });
166
176
  const completedRoot = path.join(workspaceRoot, 'completed');
167
177
  const completedStat = fs.lstatSync(completedRoot);
@@ -184,17 +194,14 @@ export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
184
194
  export function sandboxCompletedTaskMountSource(repoRoot, taskId) {
185
195
  if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
186
196
  throw new Error('SANDBOX_TASK_SOURCE_INVALID');
187
- const source = path.resolve(repoRoot, '.agents', 'workspace', 'completed', taskId);
197
+ const source = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot), 'completed', taskId);
188
198
  const stat = fs.lstatSync(source);
189
199
  if (!stat.isDirectory() || stat.isSymbolicLink())
190
200
  throw new Error('SANDBOX_TASK_SOURCE_INVALID');
191
- const canonicalWorkspace = fs.realpathSync.native(path.join(repoRoot, '.agents', 'workspace'));
201
+ const canonicalWorkspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
192
202
  const canonicalSource = fs.realpathSync.native(source);
193
- const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
194
- const workspaceRelative = path.relative(canonicalRepoRoot, canonicalWorkspace);
195
203
  const relative = path.relative(canonicalWorkspace, canonicalSource);
196
- if (workspaceRelative.startsWith('..') || path.isAbsolute(workspaceRelative)
197
- || path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
204
+ if (path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
198
205
  || relative.startsWith('..') || path.isAbsolute(relative))
199
206
  throw new Error('SANDBOX_TASK_SOURCE_INVALID');
200
207
  return canonicalSource;
@@ -4,7 +4,6 @@ import { resolveTaskRef } from './resolve-ref.js';
4
4
  import { locateActivityLog, appendActivityEntry } from './activity-log.js';
5
5
  import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts } from './artifact-lifecycle.js';
6
6
  import { parseArtifactName } from './artifact-name.js';
7
- import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
8
7
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
8
  const HEAD_RE = /^[0-9a-f]{40}$/;
10
9
  const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
@@ -192,7 +191,7 @@ function terminalNote(intent) {
192
191
  }
193
192
  return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
194
193
  }
195
- function applyLocked(intent, normalizedAgent, options) {
194
+ function applyActivityIntent(intent, normalizedAgent, options) {
196
195
  const state = readSnapshot(intent.taskRef, options.repoRoot);
197
196
  if (!state.ok)
198
197
  return failed(intent, state.code, state.message, state.taskId);
@@ -297,15 +296,6 @@ export function applyPrReviewActivityIntent(intent, options = {}) {
297
296
  if (resolved.state !== 'active') {
298
297
  return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
299
298
  }
300
- if (options.lockAlreadyHeld)
301
- return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
302
- try {
303
- return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity:${intent.kind}`, () => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }), options.lockOptions);
304
- }
305
- catch (error) {
306
- if (error instanceof TaskExecutionLockError)
307
- return failed(intent, error.code, error.message, resolved.taskId);
308
- return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
309
- }
299
+ return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
310
300
  }
311
301
  //# sourceMappingURL=activity-intent.js.map
@@ -79,11 +79,11 @@ function failure(requestRef, family, error, extra = {}) {
79
79
  function diagnostic(code, family, name, message) {
80
80
  return { code, family, name, message };
81
81
  }
82
- function inspectTaskArtifacts(taskRef, family, options = {}) {
82
+ function inspectTaskArtifacts(taskRef, family, options = {}, resolvedTask) {
83
83
  const spec = familySpec(family);
84
84
  if (!spec)
85
85
  return failure(taskRef, family, { code: 'ARTIFACT_FAMILY_UNKNOWN', message: `unknown artifact family '${family}'` });
86
- const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
86
+ const resolved = resolvedTask ?? resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
87
87
  if (!resolved.ok)
88
88
  return failure(taskRef, family, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
89
89
  return inspectArtifactDirectory(resolved.taskDir, spec.family, {
@@ -399,8 +399,8 @@ function attachArtifactSelection(context, pathState, options) {
399
399
  return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
400
400
  }
401
401
  }
402
- function resolveArtifactContext(taskRef, family, options = {}) {
403
- const inventory = inspectTaskArtifacts(taskRef, family, options);
402
+ function resolveArtifactContextFromTask(taskRef, family, options, resolvedTask) {
403
+ const inventory = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
404
404
  if (inventory.status === 'failed')
405
405
  return { ...inventory, inputs: [], codeMode: null };
406
406
  const writableError = assertWritableInventory(inventory);
@@ -411,7 +411,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
411
411
  return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
412
412
  }
413
413
  if (inventory.family === 'code') {
414
- const code = resolveCodeContext(inventory, options);
414
+ const code = resolveCodeContext(inventory, options, resolvedTask);
415
415
  const selected = attachArtifactSelection(code, pathState, options);
416
416
  if (selected.status !== 'ready' || !selected.selection)
417
417
  return selected;
@@ -447,7 +447,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
447
447
  const required = REQUIRED_INPUT[inventory.family];
448
448
  const inputs = [];
449
449
  if (required) {
450
- const input = inspectTaskArtifacts(taskRef, required, options);
450
+ const input = inspectTaskArtifacts(taskRef, required, options, resolvedTask);
451
451
  if (input.status === 'failed' || !input.latest) {
452
452
  return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
453
453
  }
@@ -456,7 +456,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
456
456
  const optional = OPTIONAL_CONTEXT[inventory.family];
457
457
  if (optional) {
458
458
  for (const family of optional) {
459
- const context = inspectTaskArtifacts(taskRef, family, options);
459
+ const context = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
460
460
  if (context.status === 'ready' && context.latest)
461
461
  inputs.push(context.latest);
462
462
  }
@@ -466,16 +466,22 @@ function resolveArtifactContext(taskRef, family, options = {}) {
466
466
  }
467
467
  return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
468
468
  }
469
- function resolveCodeContext(inventory, options) {
469
+ function resolveArtifactContext(taskRef, family, options = {}) {
470
+ return resolveArtifactContextFromTask(taskRef, family, options);
471
+ }
472
+ export function resolveArtifactContextForResolvedTask(resolvedTask, family, options = {}) {
473
+ return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
474
+ }
475
+ function resolveCodeContext(inventory, options, resolvedTask) {
470
476
  const taskRef = inventory.requestRef;
471
477
  const pathState = lifecyclePathState(inventory.taskDir);
472
478
  if (pathState.status !== 'valid')
473
479
  return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
474
480
  const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
475
- const source = inspectTaskArtifacts(taskRef, inputFamily, options);
481
+ const source = inspectTaskArtifacts(taskRef, inputFamily, options, resolvedTask);
476
482
  if (source.status === 'failed' || !source.latest)
477
483
  return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
478
- const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
484
+ const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options, resolvedTask);
479
485
  try {
480
486
  const taskContent = fs.readFileSync(path.join(inventory.taskDir, 'task.md'), 'utf8');
481
487
  const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
@@ -492,7 +498,7 @@ function resolveCodeContext(inventory, options) {
492
498
  const reviewMax = reviewCode.latest?.round ?? 0;
493
499
  const inputs = [source.latest];
494
500
  const sourceReviewFamily = inputFamily === 'analysis' ? 'review-analysis' : 'review-plan';
495
- const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options);
501
+ const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options, resolvedTask);
496
502
  if (sourceReview.status === 'ready' && sourceReview.latest && sourceReview.reviewedInput?.name === source.latest.name)
497
503
  inputs.push(sourceReview.latest);
498
504
  if (inventory.openRounds.length > 0) {
@@ -5,7 +5,6 @@ import { readStableFileSync } from './secure-fs.js';
5
5
  import { parseArtifactName } from './artifact-name.js';
6
6
  import { scanVisibleMarkdown } from './markdown.js';
7
7
  import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
8
- import { withTaskExecutionLock } from './task-execution-lock.js';
9
8
  function sha256Content(content) {
10
9
  return createHash('sha256').update(content, 'utf8').digest('hex');
11
10
  }
@@ -239,57 +238,50 @@ function initializeArtifactSkeleton(request) {
239
238
  if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir))
240
239
  return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
241
240
  try {
242
- return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.init', () => {
243
- try {
244
- const existing = fs.lstatSync(target);
245
- if (existing.isSymbolicLink() || !existing.isFile())
246
- return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
247
- return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
248
- }
249
- catch (error) {
250
- if (error.code !== 'ENOENT')
251
- return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
252
- }
253
- const skeleton = renderArtifactSkeleton({
254
- taskId: request.taskId,
255
- family: request.family,
256
- artifact: request.artifact,
257
- ...(request.locale ? { locale: request.locale } : {})
258
- });
259
- const content = skeleton.trimEnd();
260
- const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
261
- try {
262
- fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
263
- try {
264
- fs.lstatSync(target);
265
- fs.unlinkSync(tempPath);
266
- return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
267
- }
268
- catch (error) {
269
- if (error.code !== 'ENOENT')
270
- throw error;
271
- }
272
- fs.renameSync(tempPath, target);
273
- }
274
- catch (error) {
275
- try {
276
- fs.unlinkSync(tempPath);
277
- }
278
- catch { /* preserve primary error */ }
279
- return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
280
- }
281
- return {
282
- status: 'applied',
283
- changed: true,
284
- artifactSha256: sha256Content(content),
285
- semanticDigest: canonicalSemanticDigest(content),
286
- error: null
287
- };
288
- });
241
+ const existing = fs.lstatSync(target);
242
+ if (existing.isSymbolicLink() || !existing.isFile())
243
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
244
+ return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
245
+ }
246
+ catch (error) {
247
+ if (error.code !== 'ENOENT')
248
+ return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
249
+ }
250
+ const skeleton = renderArtifactSkeleton({
251
+ taskId: request.taskId,
252
+ family: request.family,
253
+ artifact: request.artifact,
254
+ ...(request.locale ? { locale: request.locale } : {})
255
+ });
256
+ const content = skeleton.trimEnd();
257
+ const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
258
+ try {
259
+ fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
260
+ try {
261
+ fs.lstatSync(target);
262
+ fs.unlinkSync(tempPath);
263
+ return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
264
+ }
265
+ catch (error) {
266
+ if (error.code !== 'ENOENT')
267
+ throw error;
268
+ }
269
+ fs.renameSync(tempPath, target);
289
270
  }
290
271
  catch (error) {
291
- return resultFailure('ARTIFACT_INIT_LOCK_FAILED', String(error));
272
+ try {
273
+ fs.unlinkSync(tempPath);
274
+ }
275
+ catch { /* preserve primary error */ }
276
+ return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
292
277
  }
278
+ return {
279
+ status: 'applied',
280
+ changed: true,
281
+ artifactSha256: sha256Content(content),
282
+ semanticDigest: canonicalSemanticDigest(content),
283
+ error: null
284
+ };
293
285
  }
294
286
  export { canonicalSemanticDigest, initializeArtifactSkeleton, inspectArtifactContract, inspectArtifactPatterns, inspectArtifactStructure, sha256Content };
295
287
  //# sourceMappingURL=artifact-operations.js.map
@@ -275,7 +275,7 @@ function recommendNext(facts) {
275
275
  }
276
276
  return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
277
277
  }
278
- function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false) {
278
+ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false, repositoryRoot) {
279
279
  try {
280
280
  const metadata = parseTypedTaskFrontmatter(content);
281
281
  const rework = parseReworkIntentDocument(content);
@@ -387,7 +387,7 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
387
387
  && artifactHashes[input] === receipt.inputSha256)
388
388
  reviewedInputs[family] = input;
389
389
  if (family === 'review-code') {
390
- const identity = inspectReviewIdentity(taskDir, reviewContent);
390
+ const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
391
391
  reviewCodeIdentityValid = identity.status === 'matched'
392
392
  && hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
393
393
  && hasArtifactCompletionLog(content, latest, 'review-code.completed');
@@ -4,9 +4,10 @@ import { execFileSync } from 'node:child_process';
4
4
  import { formatTable } from '../../table.js';
5
5
  import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
6
6
  import { loadShortIdByTaskId } from '../short-id.js';
7
+ import { resolveProjectTaskWorkspaceRoot } from '../workspace-root.js';
7
8
  const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
8
9
 
9
- Lists tasks under .agents/workspace/. Defaults to active tasks only.
10
+ Lists tasks under the project task workspace. Defaults to active tasks only.
10
11
  --all Include active + blocked + completed (excludes archive)
11
12
  --blocked Only blocked tasks
12
13
  --completed Only completed tasks
@@ -55,7 +56,7 @@ function parseSelection(args) {
55
56
  }
56
57
  }
57
58
  function collectTasks(repoRoot, state) {
58
- const dir = path.join(repoRoot, '.agents', 'workspace', state);
59
+ const dir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
59
60
  if (!fs.existsSync(dir))
60
61
  return [];
61
62
  const shortIdByTaskId = state === 'active' || state === 'completed'
@@ -101,7 +102,7 @@ function ls(args = []) {
101
102
  rows.push(...collectTasks(repoRoot, state));
102
103
  }
103
104
  if (rows.length === 0) {
104
- process.stdout.write(`No tasks under .agents/workspace/${selection.join('|')}\n`);
105
+ process.stdout.write(`No tasks under project task workspace state ${selection.join('|')}\n`);
105
106
  return;
106
107
  }
107
108
  const tableRows = rows.map((r, i) => [
@@ -388,7 +388,7 @@ function buildFromResolved(input) {
388
388
  const run = input.run ?? makeRunner(input.repoRoot);
389
389
  const artifacts = enumerateArtifacts(input.taskDir);
390
390
  const workflow = collectWorkflow(content, input.now);
391
- const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
391
+ const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
392
392
  const next = facts.ok ? recommendNext(facts.facts) : null;
393
393
  const recommendation = next?.action ?? DASH;
394
394
  return {