@astrosheep/keiyaku 2.9.9 → 2.9.11

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 (230) hide show
  1. package/README.md +93 -174
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/harness/control-types.js +18 -1
  4. package/build/agents/harness/index.js +2 -0
  5. package/build/agents/harness/outcome-error.js +81 -0
  6. package/build/agents/harness/projection.js +2 -1
  7. package/build/agents/harness/router.js +5 -1
  8. package/build/agents/providers/claude-agent-sdk/adapter.js +18 -4
  9. package/build/agents/providers/claude-agent-sdk/registration.js +11 -1
  10. package/build/agents/providers/claude-agent-sdk/session.js +19 -5
  11. package/build/agents/providers/codex-app-server/adapter.js +11 -6
  12. package/build/agents/providers/codex-app-server/events.js +67 -6
  13. package/build/agents/providers/codex-app-server/registration.js +11 -1
  14. package/build/agents/providers/opencode-sdk/adapter.js +8 -1
  15. package/build/agents/providers/opencode-sdk/events.js +24 -2
  16. package/build/agents/providers/opencode-sdk/registration.js +11 -1
  17. package/build/agents/providers/pi/checkpoint.js +9 -1
  18. package/build/agents/providers/pi/events.js +2 -0
  19. package/build/agents/providers/pi/registration.js +14 -1
  20. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  21. package/build/cli/commands/akuma/akuma/meta.js +3 -3
  22. package/build/cli/commands/akuma/catalog.js +2 -2
  23. package/build/cli/commands/akuma/{view → show}/handler.js +5 -5
  24. package/build/cli/commands/akuma/{view → show}/meta.js +3 -3
  25. package/build/cli/commands/akuma.js +1 -1
  26. package/build/cli/commands/contract/amend/handler.js +5 -4
  27. package/build/cli/commands/contract/arc/handler.js +5 -4
  28. package/build/cli/commands/contract/audit/handler.js +2 -2
  29. package/build/cli/commands/contract/bind/handler.js +12 -3
  30. package/build/cli/commands/contract/bind/meta.js +45 -15
  31. package/build/cli/commands/contract/forfeit/handler.js +9 -5
  32. package/build/cli/commands/contract/log/handler.js +2 -2
  33. package/build/cli/commands/contract/petition/handler.js +34 -7
  34. package/build/cli/commands/contract/renew/handler.js +7 -7
  35. package/build/cli/commands/contract/renew/meta.js +3 -3
  36. package/build/cli/commands/metadata.js +10 -6
  37. package/build/cli/commands/projection/call/meta.js +1 -1
  38. package/build/cli/commands/projection/catalog.js +2 -0
  39. package/build/cli/commands/projection/history/handler.js +21 -0
  40. package/build/cli/commands/projection/history/meta.js +9 -0
  41. package/build/cli/commands/projection/shared.js +3 -4
  42. package/build/cli/commands/projection/status/handler.js +3 -3
  43. package/build/cli/commands/projection/wait/handler.js +20 -11
  44. package/build/cli/commands/task/add/handler.js +14 -5
  45. package/build/cli/commands/task/add/meta.js +2 -2
  46. package/build/cli/commands/task/catalog.js +4 -0
  47. package/build/cli/commands/task/done/handler.js +1 -4
  48. package/build/cli/commands/task/done/meta.js +1 -1
  49. package/build/cli/commands/task/drop/meta.js +1 -1
  50. package/build/cli/commands/task/hold/handler.js +3 -0
  51. package/build/cli/commands/task/hold/meta.js +8 -0
  52. package/build/cli/commands/task/log/handler.js +1 -1
  53. package/build/cli/commands/task/log/meta.js +2 -2
  54. package/build/cli/commands/task/resume/handler.js +3 -0
  55. package/build/cli/commands/task/resume/meta.js +8 -0
  56. package/build/cli/commands/task/shared.js +39 -14
  57. package/build/cli/commands/task/show/handler.js +1 -1
  58. package/build/cli/commands/task/show/meta.js +1 -1
  59. package/build/cli/commands/task/start/meta.js +1 -1
  60. package/build/cli/commands/task/stop/handler.js +1 -4
  61. package/build/cli/commands/task/stop/meta.js +1 -1
  62. package/build/cli/commands/task/update/handler.js +11 -3
  63. package/build/cli/commands/task/update/meta.js +3 -3
  64. package/build/cli/completion.js +3 -2
  65. package/build/cli/flags.js +51 -14
  66. package/build/cli/help.js +4 -1
  67. package/build/cli/index.js +40 -25
  68. package/build/cli/parse-flags.js +40 -7
  69. package/build/cli/parse-metadata.js +5 -2
  70. package/build/cli/parse-selectors.js +2 -3
  71. package/build/cli/parse.js +40 -6
  72. package/build/cli/projection-address.js +12 -10
  73. package/build/cli/render/amendment-warning.js +8 -0
  74. package/build/cli/render/arc.js +6 -2
  75. package/build/cli/render/audit.js +400 -81
  76. package/build/cli/render/kanshi.js +6 -1
  77. package/build/cli/render/lifecycle.js +56 -0
  78. package/build/cli/render/misc.js +24 -28
  79. package/build/cli/render/petition.js +5 -0
  80. package/build/cli/render/projection-activity.js +3 -2
  81. package/build/cli/render/projection-history.js +64 -0
  82. package/build/cli/render/shared.js +23 -9
  83. package/build/cli/render/status.js +9 -16
  84. package/build/cli/render/task.js +51 -26
  85. package/build/cli/render/terminal-failure.js +3 -1
  86. package/build/cli/render/wait.js +3 -5
  87. package/build/cli/subagent-guard.js +1 -1
  88. package/build/cli/types.js +1 -1
  89. package/build/config/env-keys.js +2 -0
  90. package/build/config/env.js +23 -0
  91. package/build/config/settings/schema.js +3 -1
  92. package/build/core/address-carrier.js +89 -0
  93. package/build/core/addressing.js +255 -196
  94. package/build/core/amend.js +115 -38
  95. package/build/core/amendment-criteria.js +17 -0
  96. package/build/core/amendment-warning.js +10 -0
  97. package/build/core/arc.js +184 -83
  98. package/build/core/audit/candidate.js +110 -9
  99. package/build/core/audit/coordinates.js +120 -65
  100. package/build/core/audit/evidence.js +19 -23
  101. package/build/core/audit/facade.js +34 -11
  102. package/build/core/audit/report.js +5 -5
  103. package/build/core/bind-reconciliation.js +390 -0
  104. package/build/core/bind-workspace.js +12 -20
  105. package/build/core/bind.js +242 -273
  106. package/build/core/call/call.js +1 -0
  107. package/build/core/call/context.js +46 -71
  108. package/build/core/call/prompt.js +6 -3
  109. package/build/core/context.js +49 -35
  110. package/build/core/contract-carrier-runtime.js +408 -0
  111. package/build/core/contract-carrier.js +270 -0
  112. package/build/core/contract-view.js +33 -27
  113. package/build/core/contract.js +5 -1
  114. package/build/core/derived-replay.js +207 -0
  115. package/build/core/draft.js +125 -57
  116. package/build/core/forfeit.js +113 -56
  117. package/build/core/identifier-slug.js +63 -13
  118. package/build/core/ids.js +4 -4
  119. package/build/core/lifecycle-history.js +35 -0
  120. package/build/core/lifecycle-recovery.js +117 -0
  121. package/build/core/lifecycle-runner.js +377 -0
  122. package/build/core/log.js +37 -11
  123. package/build/core/outcome-base.js +181 -0
  124. package/build/core/process-group.js +139 -0
  125. package/build/core/projection/generation/database.js +82 -7
  126. package/build/core/projection/generation/model.js +9 -36
  127. package/build/core/projection/generation/projection-generation-continuation.js +10 -22
  128. package/build/core/projection/generation/projection-generation-execution.js +6 -5
  129. package/build/core/projection/index.js +5 -4
  130. package/build/core/projection/projection-activity.js +27 -8
  131. package/build/core/projection/projection-alias.js +23 -10
  132. package/build/core/projection/projection-core.js +21 -7
  133. package/build/core/projection/projection-execution-observer.js +91 -39
  134. package/build/core/projection/projection-history.js +474 -0
  135. package/build/core/projection/projection-runner-lock.js +12 -0
  136. package/build/core/projection/projection-status-observation.js +365 -0
  137. package/build/core/projection/projection-status.js +21 -358
  138. package/build/core/projection/projection-terminal-failure.js +13 -8
  139. package/build/core/projection/projection-wait.js +7 -2
  140. package/build/core/projection/tell/launch-store.js +244 -0
  141. package/build/core/projection/tell/model.js +3 -3
  142. package/build/core/projection/tell/store.js +70 -118
  143. package/build/core/registry.js +22 -87
  144. package/build/core/renew-build.js +424 -0
  145. package/build/core/renew-plan.js +239 -0
  146. package/build/core/renew-rewrite.js +77 -0
  147. package/build/core/renew.js +295 -520
  148. package/build/core/repository-ledger/accepted-fold-read.js +19 -0
  149. package/build/core/repository-ledger/accepted-tail.js +116 -0
  150. package/build/core/repository-ledger/admission.js +38 -0
  151. package/build/core/repository-ledger/authoritative-transaction.js +1 -0
  152. package/build/core/repository-ledger/claim-evidence.js +147 -0
  153. package/build/core/repository-ledger/codec.js +297 -0
  154. package/build/core/repository-ledger/current-state-store.js +853 -0
  155. package/build/core/repository-ledger/fold-repository.js +375 -0
  156. package/build/core/repository-ledger/fold.js +248 -0
  157. package/build/core/repository-ledger/git-object-store.js +243 -0
  158. package/build/core/repository-ledger/history-read.js +15 -0
  159. package/build/core/repository-ledger/incremental-read.js +153 -0
  160. package/build/core/repository-ledger/index.js +15 -0
  161. package/build/core/repository-ledger/inventory-plan.js +75 -0
  162. package/build/core/repository-ledger/inventory.js +310 -0
  163. package/build/core/repository-ledger/publication-admission.js +125 -0
  164. package/build/core/repository-ledger/publication.js +353 -0
  165. package/build/core/repository-ledger/read-model.js +469 -0
  166. package/build/core/repository-ledger/transaction-membership.js +78 -0
  167. package/build/core/repository-ledger/traversal.js +404 -0
  168. package/build/core/repository-ledger/write-transaction.js +339 -0
  169. package/build/core/seal.js +168 -161
  170. package/build/core/settlement/claim-delivery.js +42 -215
  171. package/build/core/settlement/claim.js +235 -153
  172. package/build/core/settlement/index.js +4 -4
  173. package/build/core/settlement/petition-claim-gates.js +52 -33
  174. package/build/core/settlement/petition-forfeit.js +12 -5
  175. package/build/core/settlement/petition-head-guard.js +8 -5
  176. package/build/core/settlement/petition-preview.js +68 -56
  177. package/build/core/settlement/petition-window.js +50 -0
  178. package/build/core/settlement/petition.js +252 -131
  179. package/build/core/settlement/queue-read-model.js +72 -95
  180. package/build/core/settlement/queue-seat-allocation.js +35 -126
  181. package/build/core/settlement/queue.js +1 -1
  182. package/build/core/settlement/settlement.js +172 -80
  183. package/build/core/settlement/verdict.js +112 -66
  184. package/build/core/settlement/verification-coordination.js +305 -0
  185. package/build/core/settlement/verification-supervisor.js +275 -0
  186. package/build/core/settlement/verification.js +398 -31
  187. package/build/core/status/board.js +114 -119
  188. package/build/core/status/lifecycle.js +7 -80
  189. package/build/core/structured-query.js +23 -0
  190. package/build/core/target-ref.js +1 -1
  191. package/build/core/task/board.js +59 -24
  192. package/build/core/task/commands.js +181 -33
  193. package/build/core/task/coordinate.js +56 -0
  194. package/build/core/task/document.js +25 -9
  195. package/build/core/task/index.js +7 -4
  196. package/build/core/task/query.js +17 -27
  197. package/build/core/task/settlement-git.js +164 -179
  198. package/build/core/task/settlement-policy.js +15 -11
  199. package/build/core/task/source-board.js +31 -13
  200. package/build/core/task/task-bind-preparation.js +193 -0
  201. package/build/core/task/task-contract.js +102 -193
  202. package/build/core/task/task-file-transaction.js +171 -0
  203. package/build/core/task/task-git-runtime.js +76 -14
  204. package/build/core/task/task-git-store.js +68 -169
  205. package/build/core/task/task-store-repository.js +13 -6
  206. package/build/core/task/task-worktree.js +6 -33
  207. package/build/core/task/task.js +1 -1
  208. package/build/core/task/tree.js +3 -3
  209. package/build/core/task/validation.js +4 -3
  210. package/build/core/transcripts.js +93 -1
  211. package/build/core/worktree-bootstrap.js +44 -36
  212. package/build/core/worktree-path.js +210 -77
  213. package/build/flow-error.js +7 -7
  214. package/build/generated/version.js +2 -2
  215. package/build/git/core.js +76 -13
  216. package/build/git/refs.js +168 -46
  217. package/build/git/streaming-batch.js +73 -13
  218. package/package.json +1 -1
  219. package/skills/keiyaku/SKILL.md +1 -1
  220. package/skills/keiyaku-akuma/SKILL.md +3 -3
  221. package/skills/keiyaku-task/SKILL.md +4 -2
  222. package/skills/keiyaku-workflow/SKILL.md +88 -203
  223. package/build/core/entry.js +0 -305
  224. package/build/core/ledger-batch.js +0 -194
  225. package/build/core/ledger.js +0 -84
  226. package/build/core/projection/leash.js +0 -87
  227. package/build/core/ref-log.js +0 -119
  228. package/build/core/renew-session.js +0 -128
  229. package/build/core/status/ledger-batch.js +0 -1
  230. package/build/core/status/reconciliation.js +0 -137
@@ -3,6 +3,7 @@ import { RESPONSE_MARKERS } from "./response-style.js";
3
3
  import { formatReview, HINTS_LIST_MAX_ITEMS, REVIEW_SECTION_TITLE, toReviewPayload, } from "./shared.js";
4
4
  import { prependAddressReceipt, renderAddressCarrier } from "./address.js";
5
5
  import { commissionSlug } from "../../core/ids.js";
6
+ import { renderAmendmentWarning } from "./amendment-warning.js";
6
7
  export function buildErrorResponse(input) {
7
8
  const shouldRaiseProtocolError = input.errorType === "runtime_error";
8
9
  const text = input.errorCode === "PROJECTION_INCOMPATIBLE"
@@ -28,14 +29,17 @@ export function buildErrorResponse(input) {
28
29
  }
29
30
  function buildErrorText(input) {
30
31
  const errorSection = buildSection("Error", input.message);
31
- const recoverySection = input.facts?.kind === "renew_conflict"
32
- ? buildSection("Recovery", [
32
+ const conflictSection = input.facts?.kind === "renew_conflict_scratch"
33
+ ? buildSection("Conflict scratch", [
34
+ input.facts.scratchPath,
35
+ "",
33
36
  "Conflicting paths:",
34
37
  ...input.facts.conflictPaths.map((filePath) => ` ${filePath}`),
35
- ...(input.facts.conflictPathCapture.status === "failed"
36
- ? [`Conflict-path capture failed: ${input.facts.conflictPathCapture.diagnostic}`]
37
- : []),
38
- "Delivery branch and worktree were restored; no ledger writes were appended.",
38
+ "",
39
+ `Ledger: ${input.facts.observedLedgerHead}`,
40
+ `Delivery: ${input.facts.deliveryRef} at ${input.facts.oldHead}`,
41
+ `Target: ${input.facts.targetRef} at ${input.facts.newBase}`,
42
+ "Authoritative ledger and delivery refs were unchanged.",
39
43
  ].join("\n"))
40
44
  : null;
41
45
  const reviewSection = input.review ? buildSection(REVIEW_SECTION_TITLE, formatReview(input.review)) : null;
@@ -43,32 +47,22 @@ function buildErrorText(input) {
43
47
  && input.addressCarrier.binding.kind === "commission"
44
48
  ? input.addressCarrier
45
49
  : undefined;
46
- const recoveryAddress = commissionedCarrier
50
+ const renewAddress = commissionedCarrier
47
51
  ? `@${commissionedCarrier.place.display ?? commissionSlug(commissionedCarrier.binding.commissionId)}`
48
- : input.facts?.kind === "renew_session"
49
- ? `@${commissionSlug(input.facts.commission)}`
50
- : undefined;
51
- const nextActionSection = input.facts?.kind === "renew_session" && recoveryAddress
52
- ? buildSection("Next action", input.facts.nextAction === "resolve-conflicts"
53
- ? [
54
- "Resolve and stage the conflicting delivery with ordinary Git, then:",
55
- `» keiyaku ${recoveryAddress} renew --continue`,
56
- "Or restore the pre-renew delivery:",
57
- `» keiyaku ${recoveryAddress} renew --abort`,
58
- ]
59
- : input.facts.nextAction === "continue-or-abort" ? [
60
- `» keiyaku ${recoveryAddress} renew --continue`,
61
- `» keiyaku ${recoveryAddress} renew --abort`,
62
- ] : [`» keiyaku ${recoveryAddress} renew --abort`])
52
+ : undefined;
53
+ const nextActionSection = input.facts?.kind === "renew_conflict_scratch" && renewAddress
54
+ ? buildSection("Next action", [
55
+ "Resolve files in the conflict scratch, then:",
56
+ `» keiyaku ${renewAddress} renew --continue`,
57
+ "Or discard the conflict scratch:",
58
+ `» keiyaku ${renewAddress} renew --abort`,
59
+ ])
63
60
  : null;
64
- const hints = input.facts?.kind === "renew_session"
61
+ const hints = input.facts?.kind === "renew_conflict_scratch"
65
62
  ? []
66
63
  : formatHints(input.hints, { maxItems: HINTS_LIST_MAX_ITEMS, maxItemChars: FORMAT_LIST_MAX_ITEM_CHARS });
67
- if (input.facts?.kind === "renew_conflict") {
68
- hints.push("your old head survives:", `» git log ${input.facts.recoveryRef}`);
69
- }
70
64
  const hintsSection = buildSection("Hints", hints);
71
- const body = assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, recoverySection, nextActionSection, reviewSection, hintsSection].filter((section) => section !== null));
65
+ const body = assembleResponse(colorizeErrorStatus(`${RESPONSE_MARKERS.failure} Failed [${input.errorCode}]`), "Request could not be completed.", [errorSection, conflictSection, nextActionSection, reviewSection, hintsSection].filter((section) => section !== null));
72
66
  return input.addressCarrier ? `${renderAddressCarrier(input.addressCarrier)}\n${body}` : body;
73
67
  }
74
68
  export function buildHelpResponse(input) {
@@ -82,7 +76,9 @@ export function buildLogResponse(result) {
82
76
  const { address: _address, ...resultData } = result;
83
77
  const payload = resultData;
84
78
  return prependAddressReceipt({
85
- text: result.markdown,
79
+ text: result.amendmentWarning
80
+ ? `${result.markdown.trimEnd()}\n\nWarning\n${renderAmendmentWarning(result.amendmentWarning)}\n`
81
+ : result.markdown,
86
82
  payload: buildPayload(payload),
87
83
  }, result.address);
88
84
  }
@@ -8,6 +8,11 @@ import { prependAddressReceipt } from "./address.js";
8
8
  export function renderPetitionRenewedConsequence(input) {
9
9
  return `renewed onto ${input.newBase.slice(0, 8)} · ${input.sealed?.receipts.length ?? 0} receipts replayed`;
10
10
  }
11
+ export function renderPetitionRetryConsequence(reason) {
12
+ return reason === "target-drift"
13
+ ? "rejected petition after target drift · contract active"
14
+ : "rejected petition after delivery drift · contract active";
15
+ }
11
16
  function formatPetitionSealSource(source) {
12
17
  return source.arc === null
13
18
  ? `no open arc · fence ${source.fence}`
@@ -210,6 +210,7 @@ export function renderProjectionActivity(activity, input) {
210
210
  let priorCompletedRan;
211
211
  let hasEndAnchor = false;
212
212
  let lastRunningTool;
213
+ const interrupted = input.state === "dead" || input.state === "lost" || input.state === "dismissed";
213
214
  const maxColumns = input.maxColumns ?? resolveLineColumns();
214
215
  const entries = timelineEntries(activity);
215
216
  const gapBeforeOrder = input.leadingOmittedCount ? activity.window[0]?.order : undefined;
@@ -244,14 +245,14 @@ export function renderProjectionActivity(activity, input) {
244
245
  priorMinute = minute;
245
246
  const spine = row.kind === "turn"
246
247
  ? "├"
247
- : input.state === "dead" || input.state === "lost"
248
+ : interrupted
248
249
  ? "┆"
249
250
  : "│";
250
251
  if (row.kind === "ran") {
251
252
  const duration = formatActionDuration(row.durationMs);
252
253
  const activityState = row.state === "completed"
253
254
  ? "completed"
254
- : input.state === "dead" || input.state === "lost"
255
+ : interrupted
255
256
  ? "interrupted"
256
257
  : input.state === "stalled" || input.state === "unknown"
257
258
  ? "unresolved"
@@ -0,0 +1,64 @@
1
+ import { renderProjectionActivity } from "./projection-activity.js";
2
+ function fact(value) {
3
+ return value ?? "unavailable";
4
+ }
5
+ function consequenceLines(run) {
6
+ const consequences = run.consequences;
7
+ if (consequences === null)
8
+ return [];
9
+ if (consequences.retention === "unavailable") {
10
+ return [`consequences: unavailable · ${consequences.reason}`];
11
+ }
12
+ const retained = (name, value, detail) => value.retention === "retained" ? `${name}: retained · ${detail}` : `${name}: unavailable · ${value.reason}`;
13
+ return [
14
+ `review: unavailable · ${consequences.review.reason}`,
15
+ retained("petition", consequences.petition, consequences.petition.retention === "retained" ? `${consequences.petition.intent} · ${consequences.petition.objectId}` : ""),
16
+ retained("verdict", consequences.verdict, consequences.verdict.retention === "retained" ? `${consequences.verdict.result} · ${consequences.verdict.objectId}` : ""),
17
+ retained("claim", consequences.claim, consequences.claim.retention === "retained" ? `${consequences.claim.objectId} · ${consequences.claim.mergeCommit}` : ""),
18
+ retained("landed commit", consequences.landedCommit, consequences.landedCommit.retention === "retained" ? consequences.landedCommit.commit : ""),
19
+ ];
20
+ }
21
+ function runLines(run) {
22
+ return [
23
+ `${run.id}${run.alias ? ` (${run.alias})` : ""} · ${run.outcome}`,
24
+ `akuma: ${fact(run.akuma)} · provider: ${fact(run.provider)}${run.pid === null ? "" : ` · pid: ${run.pid}`} · role: ${run.role}`,
25
+ `contract: ${fact(run.contractId)}`,
26
+ `created: ${fact(run.mintedAt)}${run.terminalAt ? ` · terminal: ${run.terminalAt}` : ""}`,
27
+ ...(run.generation ? [`generation: ${run.generation.executionId} · created: ${run.generation.createdAt}${run.generation.adoptedAt ? ` · adopted: ${run.generation.adoptedAt}` : ""}`] : []),
28
+ `activity: ${run.activityRetention} · ${run.activityCount} retained${run.activityRange ? ` · ${run.activityRange.first}-${run.activityRange.last}` : ""}`,
29
+ ...(run.telemetry ? [`telemetry: ${run.telemetry.inputTokens} in · ${run.telemetry.outputTokens} out${run.telemetry.cachedInputTokens !== undefined ? ` · ${run.telemetry.cachedInputTokens} cached` : ""}${run.telemetry.costUsd !== undefined ? ` · $${run.telemetry.costUsd.toFixed(2)}` : ""}`] : []),
30
+ ...(run.artifact ? [`artifact: ${fact(run.artifact.id)} · ${run.artifact.retention}${run.artifact.path ? ` · path: ${run.artifact.path}` : ""}`] : []),
31
+ ...consequenceLines(run),
32
+ ...run.diagnostics.map((diagnostic) => `diagnostic: ${diagnostic}`),
33
+ ];
34
+ }
35
+ export function renderProjectionHistoryList(runs) {
36
+ return runs.flatMap((run, index) => index === 0 ? runLines(run) : ["", ...runLines(run)]).join("\n");
37
+ }
38
+ export function renderProjectionHistoryDetail(detail) {
39
+ const lines = runLines(detail);
40
+ if (!detail.activity || detail.activity.retainedTimeline.length === 0)
41
+ return lines.join("\n");
42
+ const state = detail.outcome === "done" ? "returned"
43
+ : detail.outcome === "out" ? "running"
44
+ : detail.outcome === "failed" || detail.outcome === "dead" ? "dead"
45
+ : detail.outcome === "lost" ? "lost"
46
+ : detail.outcome === "killed" || detail.outcome === "dismissed" ? "dismissed"
47
+ : detail.outcome === "startup-timeout" ? "stalled"
48
+ : "unknown";
49
+ const activity = renderProjectionActivity({
50
+ ...detail.activity,
51
+ timeline: detail.activity.retainedTimeline,
52
+ omittedSettledCount: 0,
53
+ }, {
54
+ anchorMs: Date.now(),
55
+ state,
56
+ showPlan: false,
57
+ showSilence: false,
58
+ showTerminalFiles: false,
59
+ });
60
+ return [...lines, "", ...activity.lines].join("\n");
61
+ }
62
+ export function projectionHistoryJson(value) {
63
+ return `${JSON.stringify(value)}\n`;
64
+ }
@@ -1,4 +1,5 @@
1
1
  import { KEIYAKU_FILE } from "../../keiyaku.js";
2
+ import { isCompleteLifecycleOutcome } from "../../core/outcome-base.js";
2
3
  import { commissionSlug, formatCommissionCoordinate } from "../../core/ids.js";
3
4
  import { assembleResponse, buildSection, DISPLAY_TEXT_MAX_CHARS, FORMAT_LIST_MAX_ITEM_CHARS, FORMAT_LIST_MAX_ITEMS, formatMaybe, formatWarnings, shellQuote, truncateForDisplay, } from "./format.js";
4
5
  import { WARNING_SECTION_TITLE } from "./response-style.js";
@@ -63,6 +64,26 @@ export function buildBindResponse(result, cwd) {
63
64
  ...(overlapWarnings.length > 0 ? ["Warnings:", ...overlapWarnings] : []),
64
65
  ].join("\n"), payload);
65
66
  }
67
+ export function buildContainedBindResponse(result) {
68
+ const payload = {
69
+ family: result.family,
70
+ completion: result.completion,
71
+ committed: result.committed,
72
+ heads: result.heads,
73
+ reconciliationKey: result.reconciliationKey,
74
+ prerequisites: result.prerequisites,
75
+ downstream: result.downstream,
76
+ replay: result.replay,
77
+ carrier: result.carrier,
78
+ cancellationObserved: result.cancellationObserved,
79
+ };
80
+ return coreTextResponse(`Bind committed with incomplete replay · head ${result.committed.head} · reconcile ${result.reconciliationKey}`, payload);
81
+ }
82
+ export function buildBindOutcomeResponse(result, cwd) {
83
+ return isCompleteLifecycleOutcome(result)
84
+ ? buildBindResponse(result, cwd)
85
+ : buildContainedBindResponse(result);
86
+ }
66
87
  function renderScopeOverlapWarnings(overlaps) {
67
88
  const overlapsByCounterparty = new Map();
68
89
  for (const overlap of overlaps) {
@@ -105,10 +126,8 @@ export function buildForfeitResponse(result) {
105
126
  `Could not remove contract worktree ${result.cleanup.worktreeRemoveFailure.path}: ${result.cleanup.worktreeRemoveFailure.reason}`,
106
127
  ]
107
128
  : []),
108
- ...(result.cleanup.branchDeleteFailure
109
- ? [
110
- `Could not delete contract branch: ${redactBranchReference(result.cleanup.branchDeleteFailure.reason, result.cleanup.branchDeleteFailure.name)}`,
111
- ]
129
+ ...(result.cleanup.deliveryRef.status === "mismatch"
130
+ ? [`Delivery ref ${result.cleanup.deliveryRef.mismatch.ref} remains present at ${result.cleanup.deliveryRef.mismatch.observed.head}; terminal replay preserved it as a projected-surface mismatch.`]
112
131
  : []),
113
132
  ];
114
133
  if (warnings.length === 0) {
@@ -117,11 +136,6 @@ export function buildForfeitResponse(result) {
117
136
  const warningSection = buildSection(WARNING_SECTION_TITLE, formatWarnings(warnings));
118
137
  return prependAddressReceipt(coreTextResponse(assembleResponse(base, "", [warningSection].filter((section) => section !== null)), payload), result.address);
119
138
  }
120
- function redactBranchReference(message, branch) {
121
- return message
122
- .replaceAll(`refs/heads/${branch}`, "contract branch")
123
- .replaceAll(branch, "contract branch");
124
- }
125
139
  export const RULES_SECTION_MAX_CHARS = 5000;
126
140
  export const INFO_SECTION_TITLE = "Info";
127
141
  export const HINTS_SECTION_TITLE = "Hints";
@@ -7,7 +7,6 @@ import { renderTerminalFailureFace, terminalFailureCodeSuffix } from "./terminal
7
7
  import { formatTellCommand } from "./shared.js";
8
8
  export const STATUS_DISEASES_SECTION_TITLE = "Diseases";
9
9
  export const STATUS_PROJECTION_SECTION_TITLE = "akuma";
10
- export const STATUS_CLAIM_RECEIPTS_SECTION_TITLE = "claim receipts";
11
10
  function projectionGlyph(state) {
12
11
  switch (state) {
13
12
  case "out":
@@ -37,15 +36,19 @@ export function renderSettingsDiseasesSection(diseases, taskDiseases = []) {
37
36
  return `${STATUS_DISEASES_SECTION_TITLE}\n${lines.join("\n")}\n`;
38
37
  }
39
38
  function renderTaskStatusSection(tasks) {
40
- if (!tasks || tasks.inProgress + tasks.ready + tasks.blocked === 0)
39
+ if (!tasks || tasks.inProgress + tasks.onHold + tasks.ready + tasks.blocked === 0)
41
40
  return "";
42
41
  const segments = [
43
42
  tasks.inProgress > 0 ? `${tasks.inProgress} in_progress` : undefined,
43
+ tasks.onHold > 0 ? `${tasks.onHold} on hold` : undefined,
44
44
  tasks.ready > 0 ? `${tasks.ready} ready` : undefined,
45
45
  tasks.blocked > 0 ? `${tasks.blocked} blocked` : undefined,
46
46
  ].filter((segment) => segment !== undefined);
47
47
  const lines = [`tasks · ${segments.join(" / ")}`];
48
- lines.push(...tasks.topTasks.map((task) => ` ${task.id} · P${task.pri} · ${task.state === "in_progress" ? "in_progress" : "ready"} · age ${formatAgeMs(task.ageMs)} · active ${formatAgeMs(task.activityAgeMs)} · ${task.title}`));
48
+ lines.push(...tasks.topTasks.map((task) => {
49
+ const onHold = task.state === "on_hold";
50
+ return ` ${task.id} · P${task.pri} · ${onHold ? "on hold" : "in_progress"} · age ${formatAgeMs(task.ageMs)} · ${onHold ? "updated" : "active"} ${formatAgeMs(task.activityAgeMs)} · ${task.title}`;
51
+ }));
49
52
  if (tasks.omitted > 0) {
50
53
  lines.push(` · +${tasks.omitted} more — keiyaku task ls`);
51
54
  }
@@ -99,10 +102,9 @@ function renderProjectionAge(row) {
99
102
  return facts;
100
103
  }
101
104
  function renderProjectionTellCounts(row) {
102
- const { inbox, inflight, submitted, consumed } = row.tellCounts;
103
- if (inbox === 0 && inflight === 0 && submitted === 0 && consumed === 0)
104
- return "";
105
- return ` tell ${inbox}▸${inflight}▸${submitted}▸${consumed}`;
105
+ const { inbox, inflight, submitted, seen, consumed } = row.tellCounts;
106
+ const total = inbox + inflight + submitted + seen + consumed;
107
+ return total === 0 ? "" : ` tell ${total} told`;
106
108
  }
107
109
  function projectionActivityState(row) {
108
110
  switch (row.state) {
@@ -194,15 +196,6 @@ export function renderProjectionStatusSection(board) {
194
196
  lines.push(omission);
195
197
  return `${STATUS_PROJECTION_SECTION_TITLE}\n${lines.join("\n")}\n`;
196
198
  }
197
- function renderClaimReconciliationSection(recon) {
198
- if (!recon)
199
- return "";
200
- const lines = recon.unclaimedSegments.map((seg) => `${recon.defaultBranch} ${shortCommitHash(seg.oldHead)}▸${shortCommitHash(seg.newHead)} · ${seg.commitCount} commits · 无 claim 收据`);
201
- lines.push(...recon.receiptIssues.map((issue) => `receipt line ${issue.line} · ${issue.reason}`));
202
- if (lines.length === 0)
203
- return "";
204
- return `${STATUS_CLAIM_RECEIPTS_SECTION_TITLE}\n${lines.join("\n")}\n`;
205
- }
206
199
  /**
207
200
  * Status mouth: kanshi board plus optional diseases section.
208
201
  * Healthy boards (no diseases) are byte-identical to renderKanshiBoard alone.
@@ -8,13 +8,29 @@ export function deriveTaskDisplay(task) {
8
8
  return display === "bound" ? `bound(${shortContractId(task.contractId)})` : display;
9
9
  }
10
10
  function renderTaskRowWithId(task, id) {
11
- return `${id} · P${task.pri} · ${deriveTaskDisplay(task)} · ${task.title}`;
11
+ return `${id} · ${task.namespace ?? "hub"} · P${task.pri} · ${deriveTaskDisplay(task)} · ${task.title}`;
12
12
  }
13
13
  export function renderTaskListRow(task) {
14
- return renderTaskRowWithId(task, task.id);
14
+ return renderTaskRowWithId(task, task.coordinate);
15
15
  }
16
16
  export function renderTaskMutationRow(task) {
17
- return renderTaskRowWithId(task, task.id);
17
+ return renderTaskRowWithId(task, task.coordinate);
18
+ }
19
+ export function renderTaskUpdate(result) {
20
+ const lines = [renderTaskMutationRow(result.task)];
21
+ if (result.bodyChange) {
22
+ lines.push(`body · +${result.bodyChange.diffstat.additions} −${result.bodyChange.diffstat.deletions} lines`);
23
+ for (const section of result.bodyChange.removedSections) {
24
+ lines.push(`▲ section removed: ${section}`);
25
+ }
26
+ }
27
+ return lines.join("\n");
28
+ }
29
+ export function renderTaskUpdateJson(result) {
30
+ return JSON.stringify({
31
+ ...taskJson(result.task),
32
+ ...(result.bodyChange === undefined ? {} : { bodyChange: result.bodyChange }),
33
+ });
18
34
  }
19
35
  export function renderTaskRow(task, completeId = false) {
20
36
  return completeId ? renderTaskMutationRow(task) : renderTaskListRow(task);
@@ -28,24 +44,20 @@ export function renderTaskList(tasks, options = {}) {
28
44
  export function renderSelectedTaskList(tasks) {
29
45
  return tasks.map(renderTaskListRow).join("\n");
30
46
  }
31
- function renderIds(ids) {
32
- return ids.length === 0 ? "(none)" : ids.join(", ");
33
- }
34
47
  function renderTaskDetails(task) {
35
48
  const released = new Set(task.releasedNeeds);
36
- const needs = task.needs.length === 0
37
- ? "(none)"
38
- : task.needs
39
- .map((id) => `${id}${released.has(id) ? " (dropped)" : ""}`)
40
- .join(", ");
41
49
  return [
42
- `${task.id} · ${task.title}`,
50
+ `${task.coordinate} · ${task.title}`,
51
+ `namespace: ${task.namespace ?? "(hub)"}`,
43
52
  `state: ${deriveTaskDisplay(task)}`,
44
53
  `priority: ${task.pri}`,
45
- `needs: ${needs}`,
46
- `blocks: ${renderIds(task.blocks)}`,
47
- `parent: ${task.parent === null ? "(none)" : task.parent}`,
48
- `from: ${renderIds(task.from)}`,
54
+ ...(task.needs.length === 0 ? [] : [`needs: ${task.needs.map((id) => `${id}${released.has(id) ? " (dropped)" : ""}`).join(", ")}`]),
55
+ ...(task.blocks.length === 0 ? [] : [`blocks: ${task.blocks.join(", ")}`]),
56
+ ...(task.parent === null ? [] : [`parent: ${task.parent}`]),
57
+ ...(task.from.length === 0 ? [] : [`from: ${task.from.join(", ")}`]),
58
+ ...(task.supersedes.length === 0 ? [] : [`supersedes: ${task.supersedes.join(", ")}`]),
59
+ ...(task.supersededBy.length === 0 ? [] : [`superseded by: ${task.supersededBy.join(", ")}`]),
60
+ ...(task.relatedTasks.length === 0 ? [] : [`relates to: ${task.relatedTasks.join(", ")}`]),
49
61
  `created: ${task.createdAt} by ${task.creator}`,
50
62
  "",
51
63
  task.body,
@@ -55,16 +67,24 @@ function renderTreeNode(node, prefix, last) {
55
67
  const branch = last ? "`- " : "|- ";
56
68
  const nestedPrefix = `${prefix}${last ? " " : "| "}`;
57
69
  const task = node.task;
58
- const released = new Set(task.releasedNeeds);
59
- const needs = task.needs.length === 0
60
- ? "(none)"
61
- : task.needs.map((id) => `${id}${released.has(id) ? " (dropped)" : ""}`).join(", ");
62
70
  const lines = [
63
- `${prefix}${branch}${task.id} · ${deriveTaskDisplay(task)} · P${task.pri} · ${task.title}`,
64
- `${nestedPrefix}needs: ${needs}`,
65
- `${nestedPrefix}blocks: ${renderIds(task.blocks)}`,
66
- `${nestedPrefix}from: ${renderIds(task.from)}`,
71
+ `${prefix}${branch}${task.coordinate} · ${deriveTaskDisplay(task)} · P${task.pri} · ${task.title}`,
67
72
  ];
73
+ const released = new Set(task.releasedNeeds);
74
+ if (task.needs.length > 0)
75
+ lines.push(`${nestedPrefix}needs: ${task.needs.map((id) => `${id}${released.has(id) ? " (dropped)" : ""}`).join(", ")}`);
76
+ if (task.blocks.length > 0)
77
+ lines.push(`${nestedPrefix}blocks: ${task.blocks.join(", ")}`);
78
+ if (task.parent !== null)
79
+ lines.push(`${nestedPrefix}parent: ${task.parent}`);
80
+ if (task.from.length > 0)
81
+ lines.push(`${nestedPrefix}from: ${task.from.join(", ")}`);
82
+ if (task.supersedes.length > 0)
83
+ lines.push(`${nestedPrefix}supersedes: ${task.supersedes.join(", ")}`);
84
+ if (task.supersededBy.length > 0)
85
+ lines.push(`${nestedPrefix}superseded by: ${task.supersededBy.join(", ")}`);
86
+ if (task.relatedTasks.length > 0)
87
+ lines.push(`${nestedPrefix}relates to: ${task.relatedTasks.join(", ")}`);
68
88
  node.children.forEach((child, index) => lines.push(...renderTreeNode(child, nestedPrefix, index === node.children.length - 1)));
69
89
  return lines;
70
90
  }
@@ -74,15 +94,17 @@ function treeDiagnostic(diagnostic) {
74
94
  }
75
95
  export function renderTaskShow(task, tree) {
76
96
  const treeLines = tree.descendants.length === 0
77
- ? ["children tree: (empty)"]
97
+ ? []
78
98
  : ["children tree:", ...tree.descendants.flatMap((node, index) => renderTreeNode(node, "", index === tree.descendants.length - 1))];
79
99
  if (tree.diagnostics.length > 0)
80
100
  treeLines.push("tree diagnostics:", ...tree.diagnostics.map(treeDiagnostic));
81
- return [...renderTaskDetails(task), "", ...treeLines].join("\n");
101
+ return [...renderTaskDetails(task), ...(treeLines.length === 0 ? [] : ["", ...treeLines])].join("\n");
82
102
  }
83
103
  export function taskJson(task) {
84
104
  return {
85
105
  id: task.id,
106
+ namespace: task.namespace,
107
+ coordinate: task.coordinate,
86
108
  title: task.title,
87
109
  state: task.state,
88
110
  display: deriveTaskDisplay(task),
@@ -91,6 +113,9 @@ export function taskJson(task) {
91
113
  blocks: [...task.blocks],
92
114
  parent: task.parent,
93
115
  from: [...task.from],
116
+ supersedes: [...task.supersedes],
117
+ supersededBy: [...task.supersededBy],
118
+ relatesTo: [...task.relatedTasks],
94
119
  contractId: task.contractId ?? null,
95
120
  createdAt: task.createdAt,
96
121
  updatedAt: task.updatedAt ?? task.createdAt,
@@ -3,7 +3,7 @@ export function terminalFailureCodeSuffix(code) {
3
3
  return code.startsWith("SUBAGENT_") ? code.slice("SUBAGENT_".length) : code;
4
4
  }
5
5
  export function terminalFailureColdStatement(failure) {
6
- if (failure.publicCause?.kind === "rate_limit_exhausted") {
6
+ if (failure.publicCause?.code === "rate_limit_exhausted") {
7
7
  return "the provider rate limit is exhausted";
8
8
  }
9
9
  if (failure.class === "runner") {
@@ -33,6 +33,8 @@ function terminalFailureEvidence(failure, statement) {
33
33
  && failure.code === "SUBAGENT_NON_ZERO_EXIT"
34
34
  && failure.exitCode !== undefined;
35
35
  const parts = [];
36
+ if (failure.message)
37
+ parts.push(failure.message);
36
38
  if (failure.timeoutMs !== undefined) {
37
39
  parts.push(failure.code === "SUBAGENT_POLICY_BUDGET" ? `budget ${failure.timeoutMs}ms` : `idle ${failure.timeoutMs}ms`);
38
40
  }
@@ -1,8 +1,7 @@
1
1
  import { formatTimelineDuration, renderPendingProjectionTell, renderProjectionActivity, } from "./projection-activity.js";
2
2
  import { displayColumns, resolveLineColumns, truncateColumns } from "./line-width.js";
3
- import { formatTellCommand, projectionRejoinHintLines, textResponse } from "./shared.js";
3
+ import { coreTextResponse, formatTellCommand, projectionRejoinHintLines, textResponse } from "./shared.js";
4
4
  import { renderTerminalFailureFace } from "./terminal-failure.js";
5
- const WAIT_HEADER_MAX_COLUMNS = 60;
6
5
  function shortAddress(projectionId) {
7
6
  return /\/([0-9a-f]{8})$/.exec(projectionId)?.[1] ?? projectionId;
8
7
  }
@@ -35,8 +34,7 @@ function projectionIdentityParts(projectionId) {
35
34
  const match = /^(.*)\/([0-9a-f]{8})$/.exec(projectionId);
36
35
  return match ? { slug: match[1], shortId: match[2] } : { slug: "", shortId: projectionId };
37
36
  }
38
- function waitHeader(projectionId, akuma, snapshot, outputColumns) {
39
- const maxColumns = Math.min(WAIT_HEADER_MAX_COLUMNS, outputColumns);
37
+ function waitHeader(projectionId, akuma, snapshot, maxColumns) {
40
38
  const state = waitStateText(snapshot);
41
39
  const identity = projectionIdentityParts(projectionId);
42
40
  // Slug often mirrors the Akuma name; print once when they are the same fact.
@@ -237,7 +235,7 @@ export function buildWaitResponse(projectionId, result, options = {}) {
237
235
  if (nextActions.length > 0)
238
236
  lines.push("");
239
237
  lines.push(...nextActions);
240
- const response = textResponse(lines.join("\n"));
238
+ const response = coreTextResponse(lines.join("\n"), result);
241
239
  if (snapshot.phase === "dismissed")
242
240
  return { ...response, exitCode: 130 };
243
241
  if (["failed", "lost", "startup-timeout", "unknown"].includes(snapshot.phase)) {
@@ -1,4 +1,4 @@
1
- const SUBAGENT_SAFE_COMMANDS = new Set(["status", "log", "guide", "dump-env", "wait"]);
1
+ const SUBAGENT_SAFE_COMMANDS = new Set(["status", "history", "log", "guide", "dump-env", "wait"]);
2
2
  const SUBAGENT_SAFE_FLAGS = new Set(["--help", "-h", "--version"]);
3
3
  function isSubagentSafeCommand(args) {
4
4
  if (args.length === 0)
@@ -1 +1 @@
1
- export const TASK_SUBCOMMANDS = ["add", "show", "ls", "log", "start", "stop", "done", "drop", "update", "doctor"];
1
+ export const TASK_SUBCOMMANDS = ["add", "show", "ls", "log", "start", "stop", "hold", "resume", "done", "drop", "update", "doctor"];
@@ -22,6 +22,7 @@ export const ENV_KEYS = {
22
22
  REVIEW_ENABLED: "KEIYAKU_REVIEW_ENABLED",
23
23
  REVIEW_MIN_LINES: "KEIYAKU_REVIEW_MIN_LINES",
24
24
  REVIEW_RESUME_SESSION: "KEIYAKU_REVIEW_RESUME_SESSION",
25
+ AMENDMENT_WARNING_THRESHOLD: "KEIYAKU_AMENDMENT_WARNING_THRESHOLD",
25
26
  };
26
27
  const ENV_KEY_ENTRIES = [
27
28
  { key: ENV_KEYS.AGENT_OVERRIDE, defaultValue: "", description: "Override default agent name", category: "subagent" },
@@ -34,6 +35,7 @@ const ENV_KEY_ENTRIES = [
34
35
  { key: ENV_KEYS.REVIEW_ENABLED, defaultValue: "1", description: "Run the reviewer gate when set to 1", category: "workflow" },
35
36
  { key: ENV_KEYS.REVIEW_MIN_LINES, defaultValue: "200", description: "Minimum changed lines required before running the reviewer gate", category: "workflow" },
36
37
  { key: ENV_KEYS.REVIEW_RESUME_SESSION, defaultValue: "1", description: "Resume reviewer sessions from the prior persisted response path when set to 1", category: "workflow" },
38
+ { key: ENV_KEYS.AMENDMENT_WARNING_THRESHOLD, defaultValue: "5", description: "Advisory amendment-count threshold for open contracts", category: "workflow" },
37
39
  { key: ENV_KEYS.DEBUG_LOG_PATH, defaultValue: "", description: "Optional debug log output file path", category: "debug" },
38
40
  { key: ENV_KEYS.TEST_SILENT, defaultValue: "0", description: "Suppress progress logs during tests when set to 1", category: "debug" },
39
41
  { key: ENV_KEYS.GIT_TIMEOUT_MS, defaultValue: "15000", description: "Git command timeout in milliseconds", category: "git" },
@@ -95,6 +95,22 @@ function positiveIntEnv(key) {
95
95
  }
96
96
  });
97
97
  }
98
+ function strictPositiveIntEnv(key) {
99
+ return z
100
+ .string()
101
+ .default(getEnvDefaultValue(key))
102
+ .transform((value, ctx) => {
103
+ const trimmed = value.trim();
104
+ const parsed = Number(trimmed);
105
+ if (/^\d+$/.test(trimmed) && Number.isSafeInteger(parsed) && parsed > 0)
106
+ return parsed;
107
+ ctx.addIssue({
108
+ code: z.ZodIssueCode.custom,
109
+ message: `${key} must be a positive integer, received '${value}'.`,
110
+ });
111
+ return z.NEVER;
112
+ });
113
+ }
98
114
  function parseNonNegativeInt(value, key) {
99
115
  const trimmed = value.trim();
100
116
  const parsed = Number.parseInt(trimmed, 10);
@@ -166,6 +182,9 @@ const reviewSchema = z.object({
166
182
  minLines: nonNegativeIntEnv(ENV_KEYS.REVIEW_MIN_LINES),
167
183
  resumeSession: booleanFlagEnv(ENV_KEYS.REVIEW_RESUME_SESSION),
168
184
  });
185
+ const amendmentWarningSchema = z.object({
186
+ threshold: strictPositiveIntEnv(ENV_KEYS.AMENDMENT_WARNING_THRESHOLD),
187
+ });
169
188
  const terminalSchema = z.object({
170
189
  noColor: z.string().optional(),
171
190
  forceColor: z.string().optional(),
@@ -188,6 +207,7 @@ const envSchema = z
188
207
  [ENV_KEYS.REVIEW_ENABLED]: z.string().optional(),
189
208
  [ENV_KEYS.REVIEW_MIN_LINES]: z.string().optional(),
190
209
  [ENV_KEYS.REVIEW_RESUME_SESSION]: z.string().optional(),
210
+ [ENV_KEYS.AMENDMENT_WARNING_THRESHOLD]: z.string().optional(),
191
211
  NO_COLOR: z.string().optional(),
192
212
  FORCE_COLOR: z.string().optional(),
193
213
  TERM: z.string().optional(),
@@ -222,6 +242,9 @@ const envSchema = z
222
242
  minLines: rawEnv[ENV_KEYS.REVIEW_MIN_LINES],
223
243
  resumeSession: rawEnv[ENV_KEYS.REVIEW_RESUME_SESSION],
224
244
  }),
245
+ amendmentWarning: amendmentWarningSchema.parse({
246
+ threshold: rawEnv[ENV_KEYS.AMENDMENT_WARNING_THRESHOLD],
247
+ }),
225
248
  terminal: terminalSchema.parse({
226
249
  noColor: rawEnv.NO_COLOR,
227
250
  forceColor: rawEnv.FORCE_COLOR,
@@ -55,8 +55,10 @@ const gateCommandSchema = z
55
55
  const gateSchema = z
56
56
  .object({
57
57
  commands: z.array(gateCommandSchema).optional(),
58
+ timeoutMs: z.number().int("timeoutMs must be an integer.").min(1).max(3_600_000).optional(),
58
59
  })
59
- .strict();
60
+ .strict()
61
+ .transform((value) => ({ ...value, timeoutMs: value.timeoutMs ?? 300_000 }));
60
62
  const WORKTREE_BOOTSTRAP_DEFAULT_TIMEOUT_MS = 300_000;
61
63
  const WORKTREE_BOOTSTRAP_MIN_TIMEOUT_MS = 1;
62
64
  const WORKTREE_BOOTSTRAP_MAX_TIMEOUT_MS = 3_600_000;