@fitlab-ai/agent-infra 0.9.7 → 0.9.9

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 (195) hide show
  1. package/README.md +1 -1
  2. package/README.zh-CN.md +1 -1
  3. package/dist/bin/internal-cli.js +11 -1
  4. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +60 -22
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +8 -1
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +175 -11
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-context.js +147 -0
  9. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +14 -0
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +86 -69
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
  12. package/dist/lib/decide.js +68 -14
  13. package/dist/lib/git/workflow.js +10 -1
  14. package/dist/lib/init.js +1 -1
  15. package/dist/lib/internal/codex-lifecycle.js +19 -5
  16. package/dist/lib/internal/codex-sandbox-controller.js +6 -13
  17. package/dist/lib/internal/git-workflow.js +5 -7
  18. package/dist/lib/internal/platform-pr.js +20 -7
  19. package/dist/lib/internal/release-workflow.js +3 -4
  20. package/dist/lib/internal/sandbox-control.js +79 -2
  21. package/dist/lib/internal/task-activity.js +57 -7
  22. package/dist/lib/internal/task-event.js +57 -4
  23. package/dist/lib/internal/task-finalization.js +54 -0
  24. package/dist/lib/internal/task-ledger.js +59 -5
  25. package/dist/lib/internal/task-lifecycle.js +59 -8
  26. package/dist/lib/internal/task-orchestration.js +161 -288
  27. package/dist/lib/internal/task-override.js +131 -0
  28. package/dist/lib/internal/task-review.js +64 -6
  29. package/dist/lib/internal/task-verify.js +65 -3
  30. package/dist/lib/internal/task-warning.js +57 -4
  31. package/dist/lib/platform/pr-summary.js +53 -13
  32. package/dist/lib/platform/pull-requests.js +205 -41
  33. package/dist/lib/runtime/agent-runtime.js +56 -0
  34. package/dist/lib/sandbox/commands/create.js +389 -311
  35. package/dist/lib/sandbox/commands/rm.js +316 -55
  36. package/dist/lib/sandbox/control/client.js +190 -6
  37. package/dist/lib/sandbox/control/container-identity.js +1 -1
  38. package/dist/lib/sandbox/control/controller-registration.js +273 -0
  39. package/dist/lib/sandbox/control/executor.js +145 -5
  40. package/dist/lib/sandbox/control/lifecycle.js +395 -73
  41. package/dist/lib/sandbox/control/protocol.js +67 -6
  42. package/dist/lib/sandbox/control/state.js +27 -11
  43. package/dist/lib/sandbox/engine.js +14 -7
  44. package/dist/lib/sandbox/index.js +5 -3
  45. package/dist/lib/sandbox/managed-fs.js +5 -0
  46. package/dist/lib/sandbox/mounts.js +20 -5
  47. package/dist/lib/sandbox/recovery.js +78 -10
  48. package/dist/lib/sandbox/task-resolver.js +50 -6
  49. package/dist/lib/sandbox/workspace-identity.js +27 -0
  50. package/dist/lib/sandbox/workspace-view.js +62 -40
  51. package/dist/lib/sandbox/worktree-safety.js +215 -2
  52. package/dist/lib/server/process-state.js +51 -1
  53. package/dist/lib/task/activity-intent.js +4 -1
  54. package/dist/lib/task/codex-orchestration.js +158 -33
  55. package/dist/lib/task/commands/status.js +59 -1
  56. package/dist/lib/task/commit-operation.js +335 -0
  57. package/dist/lib/task/commit-policy.js +18 -0
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +279 -17
  60. package/dist/lib/task/events.js +9 -5
  61. package/dist/lib/task/finalization.js +567 -0
  62. package/dist/lib/task/guard-override.js +31 -0
  63. package/dist/lib/task/human-override.js +1240 -0
  64. package/dist/lib/task/ledger-intents.js +6 -3
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/operation-outcome.js +33 -0
  67. package/dist/lib/task/orchestration.js +199 -719
  68. package/dist/lib/task/review-finalization.js +3 -2
  69. package/dist/lib/task/short-id.js +59 -1
  70. package/dist/lib/task/task-execution-lock.js +6 -1
  71. package/dist/lib/task/verification-engine.js +138 -59
  72. package/dist/lib/task/verification.js +15 -2
  73. package/dist/lib/task/workflow-warning-intents.js +54 -3
  74. package/dist/lib/task/write.js +2 -1
  75. package/dist/lifecycle-build-manifest.json +3 -3
  76. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  77. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +63 -20
  78. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +12 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +278 -13
  80. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +180 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +19 -0
  82. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +9 -1
  83. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +112 -93
  84. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
  85. package/lib/decide.ts +59 -14
  86. package/lib/git/workflow.ts +11 -2
  87. package/lib/init.ts +1 -1
  88. package/lib/internal/codex-lifecycle.ts +18 -5
  89. package/lib/internal/codex-sandbox-controller.ts +6 -12
  90. package/lib/internal/git-workflow.ts +5 -6
  91. package/lib/internal/platform-pr.ts +14 -7
  92. package/lib/internal/release-workflow.ts +3 -4
  93. package/lib/internal/sandbox-control.ts +92 -2
  94. package/lib/internal/task-activity.ts +46 -6
  95. package/lib/internal/task-event.ts +53 -4
  96. package/lib/internal/task-finalization.ts +68 -0
  97. package/lib/internal/task-ledger.ts +48 -4
  98. package/lib/internal/task-lifecycle.ts +56 -12
  99. package/lib/internal/task-orchestration.ts +32 -107
  100. package/lib/internal/task-override.ts +138 -0
  101. package/lib/internal/task-review.ts +51 -6
  102. package/lib/internal/task-verify.ts +52 -3
  103. package/lib/internal/task-warning.ts +44 -3
  104. package/lib/platform/pr-summary.ts +65 -14
  105. package/lib/platform/pull-requests.ts +211 -41
  106. package/lib/runtime/agent-runtime.ts +76 -0
  107. package/lib/sandbox/commands/create.ts +88 -4
  108. package/lib/sandbox/commands/rm.ts +413 -61
  109. package/lib/sandbox/config.ts +2 -1
  110. package/lib/sandbox/control/client.ts +255 -6
  111. package/lib/sandbox/control/container-identity.ts +3 -3
  112. package/lib/sandbox/control/controller-registration.ts +347 -0
  113. package/lib/sandbox/control/executor.ts +172 -6
  114. package/lib/sandbox/control/lifecycle.ts +465 -77
  115. package/lib/sandbox/control/protocol.ts +85 -9
  116. package/lib/sandbox/control/state.ts +35 -18
  117. package/lib/sandbox/engine.ts +20 -9
  118. package/lib/sandbox/index.ts +5 -3
  119. package/lib/sandbox/managed-fs.ts +5 -0
  120. package/lib/sandbox/mounts.ts +23 -5
  121. package/lib/sandbox/recovery.ts +90 -9
  122. package/lib/sandbox/task-resolver.ts +58 -7
  123. package/lib/sandbox/workspace-identity.ts +50 -2
  124. package/lib/sandbox/workspace-view.ts +73 -43
  125. package/lib/sandbox/worktree-safety.ts +236 -2
  126. package/lib/server/process-state.ts +48 -1
  127. package/lib/task/activity-intent.ts +4 -2
  128. package/lib/task/codex-orchestration.ts +167 -33
  129. package/lib/task/commands/status.ts +78 -1
  130. package/lib/task/commit-operation.ts +430 -0
  131. package/lib/task/commit-policy.ts +30 -0
  132. package/lib/task/decision-intents.ts +4 -2
  133. package/lib/task/delegation-receipts.ts +304 -19
  134. package/lib/task/events.ts +10 -5
  135. package/lib/task/finalization.ts +719 -0
  136. package/lib/task/guard-override.ts +50 -0
  137. package/lib/task/human-override.ts +1503 -0
  138. package/lib/task/ledger-intents.ts +6 -3
  139. package/lib/task/lifecycle.ts +71 -14
  140. package/lib/task/operation-outcome.ts +48 -0
  141. package/lib/task/orchestration.ts +239 -922
  142. package/lib/task/review-finalization.ts +6 -2
  143. package/lib/task/short-id.ts +65 -1
  144. package/lib/task/task-execution-lock.ts +10 -1
  145. package/lib/task/verification-engine.ts +145 -64
  146. package/lib/task/verification.ts +18 -3
  147. package/lib/task/workflow-warning-intents.ts +61 -4
  148. package/lib/task/write.ts +4 -1
  149. package/package.json +2 -2
  150. package/templates/.agents/QUICKSTART.en.md +2 -2
  151. package/templates/.agents/QUICKSTART.zh-CN.md +2 -2
  152. package/templates/.agents/hooks/lifecycle-delegation.js +9 -24
  153. package/templates/.agents/rules/lifecycle-orchestration.en.md +4 -3
  154. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +4 -3
  155. package/templates/.agents/rules/task-management.en.md +2 -2
  156. package/templates/.agents/rules/task-management.zh-CN.md +2 -2
  157. package/templates/.agents/skills/block-task/SKILL.en.md +2 -4
  158. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -4
  159. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  160. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  161. package/templates/.agents/skills/close-codescan/SKILL.en.md +6 -2
  162. package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +6 -2
  163. package/templates/.agents/skills/close-dependabot/SKILL.en.md +6 -2
  164. package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +6 -2
  165. package/templates/.agents/skills/commit/SKILL.en.md +41 -125
  166. package/templates/.agents/skills/commit/SKILL.zh-CN.md +52 -123
  167. package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
  168. package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
  169. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +14 -43
  170. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +14 -49
  171. package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
  172. package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
  173. package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -35
  174. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -35
  175. package/templates/.agents/skills/complete-task/SKILL.en.md +31 -38
  176. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +31 -38
  177. package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
  178. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
  179. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  180. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  181. package/templates/.agents/skills/create-pr/SKILL.en.md +6 -4
  182. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +6 -4
  183. package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +2 -2
  184. package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +2 -2
  185. package/templates/.agents/skills/run-task/SKILL.en.md +5 -5
  186. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +5 -5
  187. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
  188. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
  189. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +1 -1
  190. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +1 -1
  191. package/templates/.codex/hooks.json +4 -4
  192. package/dist/lib/task/commit-finalization.js +0 -305
  193. package/dist/lib/task/commit-intent.js +0 -165
  194. package/lib/task/commit-finalization.ts +0 -419
  195. package/lib/task/commit-intent.ts +0 -230
@@ -10,12 +10,12 @@ description: >
10
10
  The orchestrator delegates only and never executes a stage skill itself. First read `.agents/rules/no-mid-flow-questions.md`, `.agents/rules/lifecycle-orchestration.md`, and `reference/host-validation.md`.
11
11
 
12
12
  1. Resolve the canonical task ID, current Agent Client, and optional atomic policy flags, then run `task-snapshot`. If one explicit role field is present, all four are required.
13
- 2. Before `begin-or-resume`, Codex selects a host. Without `AGENT_INFRA_CONTROL_TOKEN`, use direct-host. With task-bound control authority but no `AGENT_INFRA_CODEX_CONTROLLER_CONTEXT`, only run `codex-sandbox-controller run --task-id {task-id} --task-ref {task-ref}` with the complete policy and wait; the outer loop must not create a run, baseline, or receipt. With a context, run `verify-context --task-id {task-id}` first. Stop on any bootstrap, bundle, source/profile discovery, or context failure.
14
- 3. Run `task-orchestration {task-id} begin-or-resume --client {client}` with the complete explicit policy. With no explicit policy, core reads that client's configured policy; existing v2/v3 runs use persisted policy. Stop on paused/completed results.
13
+ 2. Before `begin-or-resume`, Codex selects a host. Without `AGENT_INFRA_CONTROL_TOKEN`, use direct-host. With task-bound control authority but no `AGENT_INFRA_CODEX_CONTROLLER_CONTEXT`, only run `codex-sandbox-controller run` with the complete policy and wait; the outer loop must not create a run, baseline, or receipt. With a context, run `verify-context` first. Stop on any bootstrap, bundle, source/profile discovery, or context failure.
14
+ 3. Run `task-orchestration {task-id} begin-or-resume --client {client}` with the complete explicit policy. With no explicit policy, core reads that client's configured policy; existing runs use persisted policy. Disk state outside the complete current structure fails closed without rewriting; finish or clear active runs before upgrading. Stop on paused/completed results.
15
15
  4. Run `route`. On `completed`, run `agent-infra-internal task-verify {task-id} run-task.completed --format text` and stop. Only a `running` result with non-null `next` supplies action, role, round, artifact, requested model, and requested effort.
16
- 5. Codex runs `codex-lifecycle capability-arm --task-id {task-id}` once. The current loop's real PostToolUse must attest that ordinary tool response. Pass only its marker token to `prepare --client {client} --capability-token {token}`. Before snapshotting, core atomically consumes it, verifies task, hook source, build, contract, and controller binding, and persists the capability session, turn, and tool-use origin. Activation then verifies that spawn belongs to the same session and turn and uses a distinct tool-use. Other clients use the same prepare without a token. Failure creates no baseline, receipt, or child.
17
- 6. After prepare, run `task-orchestration <task-ref> dispatch` immediately before calling the fresh native child, then use route's exact model/effort and only the short task ref, skill, `--orchestrated`, and stage identity. The trusted hook-spawn first-observation time must fall between dispatch and the deadline. Its first provenance-sensitive command must be `await-activation --stage ... --round ... --artifact ... --role ...`; before `running`, it must not snapshot, emit stage-started, write business files, or start commit. Only explicit `recover-prepared` may clear an expired orphan when the task fingerprint is exact, no commit intent exists, and no matching unconsumed active lifecycle evidence remains.
18
- 7. Codex uses SubagentStart/Stop and App Server actual evidence to activate, consume, and seal one v3 receipt; trusted parent fallback must produce the same provenance. Timed-out waits do nothing. Advance only a sealed receipt and repeat step 4 only while running.
16
+ 5. Codex runs `codex-lifecycle capability-arm --task-id {task-id}` once. The current loop's real PostToolUse must attest that ordinary tool response. Pass its marker token, the exact routed model, and the exact routed reasoning effort to `prepare --client {client} --requested-model {requestedModel} --requested-reasoning-effort {requestedReasoningEffort} --capability-token {token}`. Core first validates the exact routed model/effort, capability provenance, and controller binding, then captures a read-only snapshot, builds an in-memory prepared receipt, atomically consumes the token, and saves the prepared state with the capability session, turn, and tool-use origin. Activation then verifies that spawn belongs to the same session and turn and uses a distinct tool-use. Other clients use the same prepare without a token. Failure creates no baseline, receipt, or child.
17
+ 6. After prepare, run `task-orchestration <task-ref> dispatch` immediately before calling the fresh native child, then use route's exact model/effort and only the short task ref, skill, `--orchestrated`, and stage identity. The trusted hook-spawn first-observation time must fall between dispatch and the deadline. Its first provenance-sensitive command must be `await-activation --stage ... --round ... --artifact ... --role ...`; before `running`, it must not snapshot, emit stage-started, write business files, or start commit. Only explicit `recover-prepared` may clear an expired orphan when the task fingerprint is exact and no matching unconsumed active lifecycle evidence remains.
18
+ 7. Codex uses SubagentStart/Stop and App Server actual evidence to activate, consume, and seal one receipt; trusted parent fallback must produce the same complete provenance. Timed-out waits do nothing. Advance only a sealed receipt and repeat step 4 only while running.
19
19
  8. Create a fresh child each round and never follow up with an old reviewer. Each requested/actual mismatch needs its own host fallback reason. Capability, hook, source, controller, build, identity, ledger, or fingerprint failure pauses and fails closed.
20
20
  9. On pause or completion, run the matching typed verification and report the structured endpoint.
21
21
 
@@ -10,12 +10,12 @@ description: >
10
10
  总控只编排,不直接执行任何阶段技能。执行前先读取 `.agents/rules/no-mid-flow-questions.md`、`.agents/rules/lifecycle-orchestration.md` 与 `reference/host-validation.md`。
11
11
 
12
12
  1. 解析规范任务 ID、当前 Agent Client,以及可选的原子策略 `--executor-model`、`--executor-reasoning-effort`、`--reviewer-model`、`--reviewer-reasoning-effort`,并执行 `agent-infra-internal task-snapshot {task-id} --format text`。任一显式策略字段出现时四个 role 字段必须完整,不得与配置拼接;两个角色可以使用同一模型。
13
- 2. Codex 在 `begin-or-resume` 前选择宿主:没有 `AGENT_INFRA_CONTROL_TOKEN` 时使用 direct-host;有 task-bound control authority 但没有 `AGENT_INFRA_CODEX_CONTROLLER_CONTEXT` 时,只调用 `agent-infra-internal codex-sandbox-controller run --task-id {task-id} --task-ref {task-ref}`(转发完整显式策略)并等待其终态,外层不得创建 run、baseline 或 receipt;已有 context 时先调用 `verify-context --task-id {task-id}`。controller 的 bootstrap、bundle、source/profile discovery 或 context 任一校验失败即停止。
14
- 3. 调用 `agent-infra-internal task-orchestration {task-id} begin-or-resume --client {client}` 并转发完整显式策略。完全没有显式策略时由核心读取当前 client 的 `agentClients[].orchestration`;已有 v2/v3 run 使用持久化策略。仅当核心返回 `ORCHESTRATION_MODEL_POLICY_REQUIRED` 时,先用 `agent-client model-selection` 展示 complete/partial/interactive-only 来源,再一次收集完整策略;未回答则不创建 run。若为 paused/completed,按结构化结果停止。
13
+ 2. Codex 在 `begin-or-resume` 前选择宿主:没有 `AGENT_INFRA_CONTROL_TOKEN` 时使用 direct-host;有 task-bound control authority 但没有 `AGENT_INFRA_CODEX_CONTROLLER_CONTEXT` 时,只调用 `agent-infra-internal codex-sandbox-controller run`(转发完整显式策略)并等待其终态,外层不得创建 run、baseline 或 receipt;已有 context 时先调用 `verify-context`。controller 的 bootstrap、bundle、source/profile discovery 或 context 任一校验失败即停止。
14
+ 3. 调用 `agent-infra-internal task-orchestration {task-id} begin-or-resume --client {client}` 并转发完整显式策略。完全没有显式策略时由核心读取当前 client 的 `agentClients[].orchestration`;existing run 使用持久化策略。磁盘状态不符合当前完整结构时核心失败关闭且不改写;升级前必须完成或清空 active run。仅当核心返回 `ORCHESTRATION_MODEL_POLICY_REQUIRED` 时,先用 `agent-client model-selection` 展示 complete/partial/interactive-only 来源,再一次收集完整策略;未回答则不创建 run。若为 paused/completed,按结构化结果停止。
15
15
  4. 调用 `route` 并读取结构化结果。若返回 `completed`,立即运行 `agent-infra-internal task-verify {task-id} run-task.completed --format text` 并停止;仅当返回 `running` 且 `next` 非空时读取唯一 action、role、round、artifact、`requestedModel` 和 `requestedReasoningEffort`,不得自行推断。
16
- 5. Codex 先调用一次 `agent-infra-internal codex-lifecycle capability-arm --task-id {task-id}`。该普通工具调用必须由当前 loop 的真实 PostToolUse 回写 attestation;仅把返回的 marker 中 token 传给 `prepare --client {client} --capability-token {token}`。核心在快照前原子消费 token,校验 task、hook source、build、contract 与 controller binding,并持久化 capability 的 session、turn、tool-use 来源。activation 再校验 spawn 与该来源属于同一 session/turn,且 spawn 使用独立的 tool-use。其他客户端直接调用同一 prepare(无 token)。任一失败不得创建 baseline、receipt 或 child。
17
- 6. prepare 成功后,在调用 fresh 原生子 Agent 的紧邻前一步执行 `task-orchestration <task-ref> dispatch`,再显式覆盖 route 返回的 model/effort;只传短任务引用、skill 名、`--orchestrated` 与 stage/round/artifact/role。可信 hook-spawn 首次观察时间必须位于 dispatch 与 deadline 之间。child 的第一条 provenance-sensitive 命令必须是 `await-activation --stage ... --round ... --artifact ... --role ...`;返回 running 前禁止 snapshot、阶段 started、业务写入或 commit-start。超时由核心暂停;崩溃遗留 prepared receipt 只能在 exact workspace fingerprint、无 commit intent、无匹配的未消费 active lifecycle evidence 且 deadline 已过时显式 `recover-prepared`。
18
- 7. Codex 用 SubagentStart/Stop 与 App Server actual evidence 激活、消费并封存唯一 receipt;受信 parent fallback 仍必须形成相同 v3 provenance。timed-out wait 无动作;只有 empty turns 或协议 `inProgress` 可等待,malformed、身份/传输错误或异常 terminal 均暂停。子 Agent 返回后只对 sealed receipt 调用 `advance`;只有 `running` 才重复步骤 4。
16
+ 5. Codex 先调用一次 `agent-infra-internal codex-lifecycle capability-arm --task-id {task-id}`。该普通工具调用必须由当前 loop 的真实 PostToolUse 回写 attestation;把 route 返回的精确 model、effort 和 marker 中 token 一并传给 `prepare --client {client} --requested-model {requestedModel} --requested-reasoning-effort {requestedReasoningEffort} --capability-token {token}`。核心先校验精确的 model/effort、capability provenance 与 controller binding,再只读捕获快照、构造内存中的 prepared receipt,最后原子消费 token 并保存 prepared 状态,同时持久化 capability 的 session、turn、tool-use 来源。activation 再校验 spawn 与该来源属于同一 session/turn,且 spawn 使用独立的 tool-use。其他客户端直接调用同一 prepare(无 token)。任一失败不得创建 baseline、receipt 或 child。
17
+ 6. prepare 成功后,在调用 fresh 原生子 Agent 的紧邻前一步执行 `task-orchestration <task-ref> dispatch`,再显式覆盖 route 返回的 model/effort;只传短任务引用、skill 名、`--orchestrated` 与 stage/round/artifact/role。可信 hook-spawn 首次观察时间必须位于 dispatch 与 deadline 之间。child 的第一条 provenance-sensitive 命令必须是 `await-activation --stage ... --round ... --artifact ... --role ...`;返回 running 前禁止 snapshot、阶段 started、业务写入或 commit。超时由核心暂停;崩溃遗留 prepared receipt 只能在 exact workspace fingerprint、无匹配的未消费 active lifecycle evidence 且 deadline 已过时显式 `recover-prepared`。
18
+ 7. Codex 用 SubagentStart/Stop 与 App Server actual evidence 激活、消费并封存唯一 receipt;受信 parent fallback 仍必须形成相同的完整 provenance。timed-out wait 无动作;只有 empty turns 或协议 `inProgress` 可等待,malformed、身份/传输错误或异常 terminal 均暂停。子 Agent 返回后只对 sealed receipt 调用 `advance`;只有 `running` 才重复步骤 4。
19
19
  8. 每轮创建新 child;禁止 follow-up 复用 reviewer。actual model/effort 与 requested 不同时必须有各自的宿主降级理由。任何 capability、hook、source、controller、build、身份、账本或 fingerprint 异常都调用 `pause` 并失败关闭。
20
20
  9. 完成或暂停后运行对应 typed verification,并把结构化 run 摘要、暂停原因、commit 终点或 clean completion evidence 告知用户。
21
21
 
@@ -6,7 +6,9 @@ Before publishing support for a client, prove the lifecycle evidence chain with
6
6
 
7
7
  - Client version, enabled multi-agent capability, launch surface, and trust boundary.
8
8
  - Raw start/stop events, or the parent PostTool spawn/completed-wait fallback when custom roles omit them, stably correlate the managed agent, parent/child identity, fresh spawn mode, actual model, and actual reasoning effort.
9
+ > Direct-host clients that structurally do not emit fork/spawn-mode event fields (currently claude-code) may derive identity correlation solely from parent/child identity uniqueness plus the receipt's one-shot state machine, without an additional fresh-spawn-mode proof; the client must still honestly record this gap and how the substitute guarantee is composed, and must never fabricate spawn-mode evidence. This exception does not apply to any other client. Concretely, claude-code's identity guarantee is provided only by non-empty and mutually distinct `parentId`/`childId`, a unique `childId` within the same run, and the receipt's one-shot state machine — it carries none of Codex's `spawn_mode` fork proof and is weaker than Codex's.
9
10
  - Explicit requested model/effort reaches each executor/reviewer. A fallback of either field reports the corresponding actual value and its own non-empty reason.
11
+ > claude-code exception: requested reasoning effort does not yet support per-role dispatch to executor/reviewer — `.claude/agents/{executor,reviewer}.md` is a repository-level shared singleton, and writing it per role would race across concurrent tasks. This client only needs to honestly record the actual model/actual reasoning effort observed in native Start/Stop host events (`delegationEvidence.actualReasoningEffort` is declared `spawn-ack`); when not observed it is recorded as missing, which is not treated as a fallback requiring its own reason and does not fail closed. This exception does not apply to any other client.
10
12
  - Candidate-checkout and packed-install behavior match, with model policy, receipts, and verification results auditable in `orchestration.json`.
11
13
  - Direct-host project/managed source and sandbox isolated-user source preserve the same build, contract, profile, and controller provenance through prepare/start/stop/consume.
12
14
 
@@ -19,4 +21,4 @@ Before publishing support for a client, prove the lifecycle evidence chain with
19
21
  5. Commit only redacted summaries and non-sensitive fixtures. Remove or replace tokens, user-specific absolute paths, transcript content, and credentials.
20
22
  6. For sandbox, run at least ten cold executor starts and ten cold reviewer starts. Record monotonic prepared, spawn-dispatch, SubagentStart, and activation-completed timestamps plus p50/p95/max. Enable only when max plus 20% fits the deadline and symlink/config/plugin/lease/context/build failure injection and terminal cleanup audits pass.
21
23
 
22
- If any required field cannot be observed reliably from the real host, keep that client's orchestration capability `unsupported` and record the gap as manual validation. Codex may advertise `experimental`, but every `prepare` must still pass static preflight and either native start/stop or the parent spawn/completed-wait fallback must produce verifiable consumed host evidence. The fallback may wait only for empty turns or protocol `inProgress`; malformed, identity/transport errors, or abnormal terminals pause deterministically.
24
+ If any required field cannot be observed reliably from the real host, keep that client's orchestration capability `unsupported` and record the gap as manual validation. Codex may advertise `experimental`, but every `prepare` must still pass static preflight and either native start/stop or the parent spawn/completed-wait fallback must produce verifiable consumed host evidence. The fallback may wait only for empty turns or protocol `inProgress`; malformed, identity/transport errors, or abnormal terminals pause deterministically. The claude-code exception above is already scoped in the second bullet and does not change this rule's fail-closed requirement for any other field or client.
@@ -6,7 +6,9 @@
6
6
 
7
7
  - 客户端版本、启用的多代理能力、启动入口与信任边界。
8
8
  - 原始 start/stop 事件,或 custom role 缺少它们时的 parent PostTool spawn/completed-wait fallback,能稳定关联 managed agent、parent/child identity、fresh spawn mode、actual model 和 actual reasoning effort。
9
+ > 结构上不提供 fork/spawn-mode 事件字段的 direct-host 客户端(当前为 claude-code),身份关联允许仅由 parent/child identity 的唯一性与一次性状态机提供,不要求额外的 fresh spawn mode 证明;该客户端仍需如实记录这一缺口与替代保证的构成,不得补造 spawn mode 证据。此例外不适用于其他客户端。具体而言,claude-code 的身份保证仅由 `parentId`/`childId` 非空且互不相等、同一 run 内 `childId` 唯一、receipt 一次性状态机三者提供,不含 codex 式的 `spawn_mode` fork 证明,强度弱于 codex。
9
10
  - 显式 requested model/effort 能传给 executor/reviewer;宿主对任一字段回退时,事件能给出对应 actual 值与独立的非空 fallback reason。
11
+ > claude-code 例外:requested reasoning effort 暂不支持按角色下发给 executor/reviewer——`.claude/agents/{executor,reviewer}.md` 是仓库级共享单例,动态写入存在跨任务竞态,解决该竞态超出本任务范围。该客户端只要求如实记录宿主原生 Start/Stop 事件中能观察到的 actual model/actual reasoning effort(`delegationEvidence.actualReasoningEffort` 声明为 `spawn-ack`);观察不到时记录为缺失,不视为需要独立 fallback reason 的回退场景,也不构成 fail-closed 阻断。此例外不适用于其他客户端。
10
12
  - candidate checkout 与打包安装后的行为一致,且模型策略、receipt 与验证结果可从 `orchestration.json` 复核。
11
13
  - direct-host 的 project/managed source 与 sandbox controller 的 isolated-user source、build/contract/profile/controller digest 必须在 prepare/start/stop/consume 全链路一致。
12
14
 
@@ -19,4 +21,4 @@
19
21
  5. 仅把去敏摘要与非敏感 fixture 纳入版本库;token、绝对用户路径、transcript 内容和凭证必须删除或替换。
20
22
  6. sandbox backend 另做至少 10 次 executor 与 10 次 reviewer 冷启动,记录 prepared、spawn dispatch、SubagentStart、activation completed 的单调时钟及 p50/p95/max;仅当 max 加 20% 余量不超过 deadline,并通过 symlink/config/plugin/lease/context/build 失败注入和终态清理审计时启用。
21
23
 
22
- 任一字段无法从真实宿主稳定观察时,该客户端的 orchestration capability 保持 `unsupported`,并把缺口记录为人工验证项。Codex 可声明为 `experimental`,但每次 `prepare` 仍必须通过静态 preflight,且原生 start/stop 或 parent spawn/completed-wait fallback 必须形成可验证的 consumed host evidence。fallback 只允许 empty turns/协议 `inProgress` 等待;malformed、身份/传输错误或异常 terminal 都稳定暂停。
24
+ 任一字段无法从真实宿主稳定观察时,该客户端的 orchestration capability 保持 `unsupported`,并把缺口记录为人工验证项。Codex 可声明为 `experimental`,但每次 `prepare` 仍必须通过静态 preflight,且原生 start/stop 或 parent spawn/completed-wait fallback 必须形成可验证的 consumed host evidence。fallback 只允许 empty turns/协议 `inProgress` 等待;malformed、身份/传输错误或异常 terminal 都稳定暂停。上述关于 claude-code 的例外已在第 2 条单独说明,不改变本条对其余字段/客户端的 fail-closed 要求。
@@ -31,7 +31,7 @@ For each failing check, decide "self-heal" vs "ask for help" in this order:
31
31
  - Before fixing, run `git status -s` to record the working tree and ensure only changes related to this failure are included.
32
32
  - Locate and make a minimal fix per the logs (touch only code / tests / config related to that failure).
33
33
  - Run the relevant tests: prefer the local command for the failing job; otherwise read the project `test` skill and select its declared core or full validation command. If neither command is known, ask for help. **Do not commit or push before tests pass.**
34
- - After tests pass, call `git-workflow commit` and `git-workflow push`, then verify the remote SHA.
34
+ - After tests pass, put the paths, message, expected HEAD, expected tree, and push policy (remote, full refs, automatic) into one intent JSON; call `git-workflow commit` once and verify the remote SHA. If the remote push fails, retry the same commit intent with empty `paths` through the push-only path.
35
35
  - Append the fix commit SHA to this run's `repairCommits`, increment the fix count, and return to SKILL step 2. When checks turn green, an empty list routes to `complete-task`; a non-empty list routes to `review-code`.
36
36
  - Never make unrelated "drive-by" optimizations; never loosen / skip the failing assertion to "make it green".
37
37
 
@@ -31,7 +31,7 @@ unknown: help
31
31
  - 自愈前先 `git status -s` 记录当前工作树,确保后续只纳入与本次失败相关的改动。
32
32
  - 在本地按日志定位并最小化修复(只动与该失败相关的代码 / 测试 / 配置)。
33
33
  - 运行对应测试:优先失败 job 对应的本地命令;否则读取项目 `test` skill,选择其中声明的 core 或 full 验证命令。两者均未知时进入求助出口。**测试通过前不得提交或推送。**
34
- - 测试通过后发布修复:把相关路径、message 与 expected HEAD 写入 intent JSON,调用 `git-workflow commit`,再调用 `git-workflow push` 并复核远端 SHA。
34
+ - 测试通过后发布修复:把相关路径、message、expected HEAD、expected tree 和 push policy(remote、完整 refs、automatic)写入同一个 intent JSON,调用一次 `git-workflow commit` 并复核远端 SHA;远端失败时使用相同 commit intent 的空 `paths` push-only 重试。
35
35
  - 将本次修复的 commit SHA 追加到当前运行的 `repairCommits`,修复计数 +1,回到 SKILL 步骤 2 重新监控;全绿时列表为空走 `complete-task`,非空走 `review-code`。
36
36
  - 绝不执行与失败无关的「顺手优化」;不放宽 / 跳过失败的断言来「修绿」。
37
37
 
@@ -18,7 +18,7 @@
18
18
  {
19
19
  "type": "command",
20
20
  "command": "node \"$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js\" --client codex --event pre-tool",
21
- "timeout": 15,
21
+ "timeout": 30,
22
22
  "statusMessage": "Recording Codex lifecycle spawn evidence"
23
23
  }
24
24
  ]
@@ -31,7 +31,7 @@
31
31
  {
32
32
  "type": "command",
33
33
  "command": "node \"$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js\" --client codex --event post-tool",
34
- "timeout": 15,
34
+ "timeout": 30,
35
35
  "statusMessage": "Recording Codex lifecycle tool completion"
36
36
  }
37
37
  ]
@@ -44,7 +44,7 @@
44
44
  {
45
45
  "type": "command",
46
46
  "command": "node \"$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js\" --client codex --event subagent-start",
47
- "timeout": 15,
47
+ "timeout": 30,
48
48
  "statusMessage": "Recording Codex child identity"
49
49
  }
50
50
  ]
@@ -57,7 +57,7 @@
57
57
  {
58
58
  "type": "command",
59
59
  "command": "node \"$(git rev-parse --show-toplevel)/.agents/hooks/lifecycle-delegation.js\" --client codex --event subagent-stop",
60
- "timeout": 15,
60
+ "timeout": 30,
61
61
  "statusMessage": "Recording Codex child stop evidence"
62
62
  }
63
63
  ]
@@ -1,305 +0,0 @@
1
- import { execFileSync, spawnSync } from 'node:child_process';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { locateActivityLog, appendActivityEntry, pairEntries, parseCommitAttemptStarted } from './activity-log.js';
5
- import { commitIntentPath, digest, readCommitIntent } from './commit-intent.js';
6
- import { parseTypedTaskFrontmatter } from './frontmatter.js';
7
- import { artifactName, maxRound, parseReviewSummary } from './review-artifacts.js';
8
- import { extractReviewBaseline, extractReviewedSnapshotTree, findAuthoritativeReviewCodeArtifact, parseReviewedGitTree } from './review-fingerprint.js';
9
- const SHA_RE = /^[a-f0-9]{40,64}$/;
10
- function git(repoRoot, args) {
11
- return execFileSync('git', args, {
12
- cwd: repoRoot,
13
- encoding: 'utf8',
14
- stdio: ['ignore', 'pipe', 'pipe']
15
- }).trim();
16
- }
17
- function commitExists(repoRoot, value) {
18
- return SHA_RE.test(value) && spawnSync('git', ['cat-file', '-e', `${value}^{commit}`], {
19
- cwd: repoRoot,
20
- stdio: 'ignore'
21
- }).status === 0;
22
- }
23
- function isAncestor(repoRoot, ancestor, descendant) {
24
- return spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], {
25
- cwd: repoRoot,
26
- stdio: 'ignore'
27
- }).status === 0;
28
- }
29
- function hasMatchingApprovedReview(taskDir, beforeRound, baseline, tree) {
30
- const entries = fs.readdirSync(taskDir);
31
- for (let round = Math.min(beforeRound - 1, maxRound(entries, 'review-code')); round >= 1; round -= 1) {
32
- const file = path.join(taskDir, artifactName('review-code', round));
33
- if (!fs.existsSync(file))
34
- continue;
35
- const content = fs.readFileSync(file, 'utf8');
36
- const summary = parseReviewSummary(content);
37
- if (summary.ok
38
- && summary.summary.verdict === 'Approved'
39
- && extractReviewBaseline(content) === baseline
40
- && extractReviewedSnapshotTree(content) === tree)
41
- return true;
42
- }
43
- return false;
44
- }
45
- function outcome(disposition, overrides = {}) {
46
- return {
47
- disposition,
48
- code: null,
49
- message: disposition,
50
- currentHead: '',
51
- committedHead: null,
52
- commitNote: null,
53
- activityNote: null,
54
- needsAnchor: false,
55
- needsLog: false,
56
- intent: null,
57
- intentDigest: null,
58
- attempt: null,
59
- ...overrides
60
- };
61
- }
62
- function invalid(message, currentHead = '', code = 'COMMIT_FINALIZATION_EVIDENCE_INVALID', overrides = {}) {
63
- return outcome('invalid', { code, message, currentHead, ...overrides });
64
- }
65
- function inspectCommitFinalization(taskDir, repoRoot, taskId) {
66
- let currentHead;
67
- let taskContent;
68
- let metadata;
69
- try {
70
- currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
71
- taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
72
- metadata = parseTypedTaskFrontmatter(taskContent);
73
- }
74
- catch (error) {
75
- return invalid(error instanceof Error ? error.message : String(error));
76
- }
77
- const activity = locateActivityLog(taskContent);
78
- if (!activity)
79
- return invalid('task activity log is missing or ambiguous', currentHead);
80
- const commitRows = pairEntries(activity.entries).filter((row) => row.step === 'Commit');
81
- const openRows = commitRows.filter((row) => row.started !== '' && row.done === '');
82
- const file = commitIntentPath(taskDir);
83
- if (!fs.existsSync(file)) {
84
- if (openRows.length === 1) {
85
- const attempt = parseCommitAttemptStarted(openRows[0].note);
86
- if (attempt && attempt.baseline === currentHead) {
87
- return outcome('retryable-start', {
88
- message: 'a structured Commit attempt can be safely reused',
89
- currentHead,
90
- attempt
91
- });
92
- }
93
- }
94
- if (openRows.length > 0) {
95
- return outcome('orphaned-start', {
96
- code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
97
- message: 'an open Commit activity entry exists without a commit intent',
98
- currentHead
99
- });
100
- }
101
- return outcome('idle', { message: 'no active commit finalization exists', currentHead });
102
- }
103
- let raw;
104
- let intent;
105
- try {
106
- raw = fs.readFileSync(file, 'utf8');
107
- intent = readCommitIntent(taskDir, taskId);
108
- }
109
- catch (error) {
110
- return invalid(error instanceof Error ? error.message : String(error), currentHead);
111
- }
112
- const intentDigest = digest(raw);
113
- const shared = { currentHead, intent, intentDigest, committedHead: intent.committedHead };
114
- if (!commitExists(repoRoot, intent.baselineHead)) {
115
- return invalid('commit intent baseline is not a valid Git commit', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
116
- }
117
- if (intent.phase === 'prepared') {
118
- if (intent.committedHead !== null || intent.pushEvidence !== null) {
119
- return invalid('prepared commit intent contains commit or push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
120
- }
121
- if (currentHead !== intent.baselineHead || openRows.length !== 1) {
122
- return outcome('conflict', {
123
- ...shared,
124
- code: 'COMMIT_FINALIZATION_CONFLICT',
125
- message: 'prepared commit intent does not match HEAD and exactly one open Commit entry'
126
- });
127
- }
128
- return outcome('prepared', { ...shared, message: 'prepared commit intent can be safely retried' });
129
- }
130
- if (!intent.committedHead || !commitExists(repoRoot, intent.committedHead)) {
131
- return invalid('committed intent head is missing or invalid', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
132
- }
133
- if (intent.phase === 'committed' && intent.pushEvidence !== null) {
134
- return invalid('committed intent contains premature push evidence', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
135
- }
136
- if (intent.phase === 'pushed'
137
- && (intent.pushEvidence === null
138
- || intent.pushEvidence.head !== intent.committedHead
139
- || !intent.pushEvidence.remote
140
- || !intent.pushEvidence.ref)) {
141
- return invalid('pushed intent evidence is missing or inconsistent', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
142
- }
143
- if (currentHead !== intent.committedHead || !isAncestor(repoRoot, intent.baselineHead, intent.committedHead)) {
144
- return outcome('conflict', {
145
- ...shared,
146
- code: 'COMMIT_FINALIZATION_CONFLICT',
147
- message: 'repository HEAD or ancestry conflicts with the commit intent'
148
- });
149
- }
150
- const commitNote = git(repoRoot, ['show', '-s', '--format=%h%x20%s', intent.committedHead]);
151
- const matchingDone = commitRows.filter((row) => row.done !== '' && row.note === commitNote);
152
- const pushOnlyRetry = intent.phase === 'pushed'
153
- && intent.baselineHead === intent.committedHead
154
- && matchingDone.length === 1
155
- && openRows.length === 1;
156
- const review = findAuthoritativeReviewCodeArtifact(taskDir);
157
- let needsAnchor = false;
158
- if (review.ok && review.path) {
159
- let reviewContent;
160
- try {
161
- reviewContent = fs.readFileSync(review.path, 'utf8');
162
- }
163
- catch (error) {
164
- return invalid(error instanceof Error ? error.message : String(error), currentHead, 'COMMIT_FINALIZATION_EVIDENCE_MISSING', shared);
165
- }
166
- const summary = parseReviewSummary(reviewContent);
167
- const reviewBaseline = extractReviewBaseline(reviewContent);
168
- const reviewTree = extractReviewedSnapshotTree(reviewContent);
169
- if (!summary.ok || summary.summary.verdict !== 'Approved' || !commitExists(repoRoot, reviewBaseline) || !parseReviewedGitTree(reviewTree)) {
170
- return invalid('authoritative review-code evidence is not a valid approved snapshot', currentHead, 'COMMIT_FINALIZATION_EVIDENCE_INVALID', shared);
171
- }
172
- const committedTree = git(repoRoot, ['rev-parse', `${intent.committedHead}^{tree}`]);
173
- const matchingCurrentReview = reviewBaseline === intent.baselineHead && reviewTree === committedTree;
174
- if (!matchingCurrentReview
175
- && !hasMatchingApprovedReview(taskDir, review.round, intent.baselineHead, committedTree)) {
176
- return outcome('conflict', {
177
- ...shared,
178
- code: 'COMMIT_FINALIZATION_CONFLICT',
179
- message: 'review baseline or snapshot tree conflicts with the committed intent'
180
- });
181
- }
182
- const anchor = String(metadata.last_reviewed_commit ?? '').trim();
183
- if (anchor && anchor !== intent.committedHead && anchor !== intent.baselineHead) {
184
- return outcome('conflict', {
185
- ...shared,
186
- code: 'COMMIT_FINALIZATION_CONFLICT',
187
- message: 'task review anchor conflicts with the committed intent'
188
- });
189
- }
190
- needsAnchor = anchor !== intent.committedHead;
191
- }
192
- const needsLog = (matchingDone.length === 0 && openRows.length === 1) || pushOnlyRetry;
193
- if (!(needsLog || (matchingDone.length === 1 && openRows.length === 0))) {
194
- return outcome('conflict', {
195
- ...shared,
196
- code: 'COMMIT_FINALIZATION_CONFLICT',
197
- message: 'Commit activity identity is missing, duplicated, or ambiguous',
198
- commitNote
199
- });
200
- }
201
- return outcome('recoverable', {
202
- ...shared,
203
- message: 'commit finalization can be recovered',
204
- commitNote,
205
- activityNote: pushOnlyRetry ? `Pushed ${commitNote}` : commitNote,
206
- needsAnchor,
207
- needsLog
208
- });
209
- }
210
- function planCommitTaskFinalization(taskDir, inspection, agent, timestamp) {
211
- if (inspection.disposition !== 'recoverable'
212
- || !inspection.committedHead
213
- || !inspection.commitNote
214
- || !inspection.activityNote) {
215
- throw new Error('commit finalization task mutations require a recoverable inspection');
216
- }
217
- const taskContent = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
218
- const frontmatter = { assigned_to: agent };
219
- if (inspection.needsAnchor)
220
- frontmatter.last_reviewed_commit = inspection.committedHead;
221
- const mutations = [{
222
- kind: 'frontmatter',
223
- set: frontmatter
224
- }];
225
- if (inspection.needsLog) {
226
- const activity = locateActivityLog(taskContent);
227
- if (!activity)
228
- throw new Error('task activity log is missing or ambiguous');
229
- mutations.push({
230
- kind: 'section',
231
- aliases: ['活动日志', 'Activity Log'],
232
- heading: activity.heading,
233
- body: appendActivityEntry(activity, {
234
- time: timestamp,
235
- step: 'Commit',
236
- agent,
237
- note: inspection.activityNote
238
- })
239
- });
240
- }
241
- return { mutations, commitNote: inspection.commitNote };
242
- }
243
- function inspectCreatePrCommitGate(taskDir, repoRoot, taskId) {
244
- const lifecycle = inspectCommitFinalization(taskDir, repoRoot, taskId);
245
- if (lifecycle.disposition !== 'idle') {
246
- const pending = lifecycle.disposition === 'prepared' || lifecycle.disposition === 'recoverable';
247
- return {
248
- allowed: false,
249
- code: pending ? 'COMMIT_FINALIZATION_PENDING' : lifecycle.code ?? 'COMMIT_FINALIZATION_CONFLICT',
250
- message: pending ? 'commit finalization must complete before creating a pull request' : lifecycle.message,
251
- disposition: lifecycle.disposition,
252
- action: 'rerun-commit'
253
- };
254
- }
255
- const review = findAuthoritativeReviewCodeArtifact(taskDir);
256
- if (!review.ok || !review.path) {
257
- return { allowed: true, code: null, message: 'no review-code history requires an anchor', disposition: 'idle', action: 'continue' };
258
- }
259
- try {
260
- const content = fs.readFileSync(review.path, 'utf8');
261
- const summary = parseReviewSummary(content);
262
- if (!summary.ok || summary.summary.verdict !== 'Approved') {
263
- return {
264
- allowed: false,
265
- code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
266
- message: 'latest authoritative review-code is not approved',
267
- disposition: 'idle',
268
- action: 'rerun-review-code'
269
- };
270
- }
271
- const task = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
272
- const currentHead = git(repoRoot, ['rev-parse', 'HEAD']);
273
- const anchor = String(task.last_reviewed_commit ?? '').trim();
274
- if (!anchor || !commitExists(repoRoot, anchor)) {
275
- return {
276
- allowed: false,
277
- code: 'COMMIT_FINALIZATION_EVIDENCE_MISSING',
278
- message: 'approved review snapshot is not anchored to a Git commit',
279
- disposition: 'idle',
280
- action: 'rerun-commit'
281
- };
282
- }
283
- if (anchor !== currentHead || extractReviewedSnapshotTree(content) !== git(repoRoot, ['rev-parse', 'HEAD^{tree}'])) {
284
- return {
285
- allowed: false,
286
- code: 'COMMIT_FINALIZATION_CONFLICT',
287
- message: 'approved review anchor or tree does not match repository HEAD',
288
- disposition: 'idle',
289
- action: 'rerun-review-code'
290
- };
291
- }
292
- return { allowed: true, code: null, message: 'commit finalization evidence is complete', disposition: 'idle', action: 'continue' };
293
- }
294
- catch (error) {
295
- return {
296
- allowed: false,
297
- code: 'COMMIT_FINALIZATION_EVIDENCE_INVALID',
298
- message: error instanceof Error ? error.message : String(error),
299
- disposition: 'idle',
300
- action: 'rerun-review-code'
301
- };
302
- }
303
- }
304
- export { inspectCommitFinalization, inspectCreatePrCommitGate, planCommitTaskFinalization };
305
- //# sourceMappingURL=commit-finalization.js.map
@@ -1,165 +0,0 @@
1
- import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- class CommitIntentError extends Error {
5
- code;
6
- constructor(code, message) {
7
- super(message);
8
- this.name = 'CommitIntentError';
9
- this.code = code;
10
- }
11
- }
12
- function commitIntentPath(taskDir) {
13
- return path.join(taskDir, 'commit-intent.json');
14
- }
15
- function digest(value) {
16
- return createHash('sha256').update(value).digest('hex');
17
- }
18
- function serialize(value) {
19
- return `${JSON.stringify(value, null, 2)}\n`;
20
- }
21
- function atomicWrite(file, value) {
22
- const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
23
- try {
24
- fs.writeFileSync(temp, serialize(value), { encoding: 'utf8', flag: 'wx', mode: 0o600 });
25
- const handle = fs.openSync(temp, 'r+');
26
- try {
27
- fs.fsyncSync(handle);
28
- }
29
- finally {
30
- fs.closeSync(handle);
31
- }
32
- fs.renameSync(temp, file);
33
- fs.chmodSync(file, 0o600);
34
- try {
35
- const directory = fs.openSync(path.dirname(file), 'r');
36
- try {
37
- fs.fsyncSync(directory);
38
- }
39
- finally {
40
- fs.closeSync(directory);
41
- }
42
- }
43
- catch (error) {
44
- const code = error.code;
45
- if (!['EACCES', 'EISDIR', 'EINVAL', 'EPERM'].includes(code ?? ''))
46
- throw error;
47
- }
48
- }
49
- finally {
50
- if (fs.existsSync(temp))
51
- fs.unlinkSync(temp);
52
- }
53
- }
54
- function isRecord(value) {
55
- return typeof value === 'object' && value !== null && !Array.isArray(value);
56
- }
57
- function keysEqual(value, expected) {
58
- return Object.keys(value).sort().join('\0') === [...expected].sort().join('\0');
59
- }
60
- function validPushEvidence(value) {
61
- return value === null || (isRecord(value)
62
- && keysEqual(value, ['remote', 'ref', 'head'])
63
- && ['remote', 'ref', 'head'].every((key) => typeof value[key] === 'string' && value[key] !== ''));
64
- }
65
- function validOrchestration(value) {
66
- if (value === null)
67
- return true;
68
- if (!isRecord(value) || !keysEqual(value, [
69
- 'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest',
70
- 'completionUpdatedAt', 'plannedReceipt'
71
- ]))
72
- return false;
73
- return [
74
- 'runId', 'receiptId', 'authorizationIssuedAt', 'sourceRunDigest', 'plannedRunDigest', 'completionUpdatedAt'
75
- ].every((key) => typeof value[key] === 'string' && value[key] !== '') && isRecord(value.plannedReceipt);
76
- }
77
- function parseIntent(raw, taskId) {
78
- let value;
79
- try {
80
- value = JSON.parse(raw);
81
- }
82
- catch {
83
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent is not valid JSON');
84
- }
85
- if (!isRecord(value) || !keysEqual(value, [
86
- 'schemaVersion', 'taskId', 'mode', 'phase', 'tokenDigest', 'baselineHead', 'committedHead',
87
- 'pushEvidence', 'orchestration', 'createdAt', 'updatedAt'
88
- ])) {
89
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has an invalid schema');
90
- }
91
- if (value.schemaVersion !== 1
92
- || value.taskId !== taskId
93
- || !['standalone', 'orchestrated'].includes(String(value.mode))
94
- || !['prepared', 'committed', 'pushed'].includes(String(value.phase))
95
- || typeof value.tokenDigest !== 'string'
96
- || !/^[a-f0-9]{64}$/.test(value.tokenDigest)
97
- || typeof value.baselineHead !== 'string'
98
- || (value.committedHead !== null && typeof value.committedHead !== 'string')
99
- || !validPushEvidence(value.pushEvidence)
100
- || !validOrchestration(value.orchestration)
101
- || typeof value.createdAt !== 'string'
102
- || typeof value.updatedAt !== 'string'
103
- || (value.mode === 'standalone') !== (value.orchestration === null)) {
104
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_INVALID', 'commit intent has invalid field values');
105
- }
106
- return value;
107
- }
108
- function tokenMatches(actualDigest, token) {
109
- const actual = Buffer.from(actualDigest, 'hex');
110
- const supplied = Buffer.from(digest(token), 'hex');
111
- return actual.length === supplied.length && timingSafeEqual(actual, supplied);
112
- }
113
- function readCommitIntent(taskDir, taskId, token) {
114
- const file = commitIntentPath(taskDir);
115
- const intent = parseIntent(fs.readFileSync(file, 'utf8'), taskId);
116
- if (token !== undefined && !tokenMatches(intent.tokenDigest, token)) {
117
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_TOKEN_MISMATCH', 'commit intent token does not match');
118
- }
119
- return intent;
120
- }
121
- function createCommitIntent(taskDir, input, options = {}) {
122
- const file = commitIntentPath(taskDir);
123
- if (fs.existsSync(file)) {
124
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_BUSY', 'an active commit intent already exists');
125
- }
126
- const token = (options.token ?? (() => randomBytes(32).toString('base64url')))();
127
- const intent = Object.freeze({ schemaVersion: 1, ...input, tokenDigest: digest(token) });
128
- atomicWrite(file, intent);
129
- return { intent, token };
130
- }
131
- function updateCommitIntent(taskDir, taskId, token, updates) {
132
- const current = readCommitIntent(taskDir, taskId, token);
133
- const nextPhase = updates.phase ?? current.phase;
134
- const transitions = {
135
- prepared: ['prepared', 'committed', 'pushed'],
136
- committed: ['committed', 'pushed'],
137
- pushed: ['pushed']
138
- };
139
- if (!transitions[current.phase].includes(nextPhase)) {
140
- throw new CommitIntentError('ORCHESTRATION_COMMIT_INTENT_STATE_INVALID', `cannot move commit intent from ${current.phase} to ${nextPhase}`);
141
- }
142
- const updated = Object.freeze({ ...current, ...updates, phase: nextPhase });
143
- atomicWrite(commitIntentPath(taskDir), updated);
144
- return updated;
145
- }
146
- function removeCommitIntent(taskDir, taskId, token) {
147
- readCommitIntent(taskDir, taskId, token);
148
- fs.unlinkSync(commitIntentPath(taskDir));
149
- }
150
- function removeCommitIntentByDigest(taskDir, taskId, expectedDigest) {
151
- const file = commitIntentPath(taskDir);
152
- const raw = fs.readFileSync(file);
153
- parseIntent(raw.toString('utf8'), taskId);
154
- const actual = digest(raw);
155
- const expected = Buffer.from(expectedDigest, 'hex');
156
- const observed = Buffer.from(actual, 'hex');
157
- if (!/^[a-f0-9]{64}$/.test(expectedDigest)
158
- || expected.length !== observed.length
159
- || !timingSafeEqual(expected, observed)) {
160
- throw new CommitIntentError('ORCHESTRATION_COMMIT_RECOVERY_REQUIRED', 'commit intent changed after recovery inspection');
161
- }
162
- fs.unlinkSync(file);
163
- }
164
- export { CommitIntentError, commitIntentPath, createCommitIntent, digest, readCommitIntent, removeCommitIntent, removeCommitIntentByDigest, serialize, updateCommitIntent };
165
- //# sourceMappingURL=commit-intent.js.map