@astrosheep/keiyaku 2.9.3 → 2.9.5

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 (282) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +14 -94
  3. package/build/agents/harness/activity-values.js +16 -0
  4. package/build/agents/harness/event-persistence.js +218 -0
  5. package/build/agents/harness/execution-handle.js +26 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +68 -5
  8. package/build/agents/harness/projection.js +179 -124
  9. package/build/agents/harness/provider-adapter.js +58 -0
  10. package/build/agents/harness/router.js +11 -187
  11. package/build/agents/harness/runtime.js +17 -4
  12. package/build/agents/harness/session.js +15 -0
  13. package/build/agents/launch-snapshot/model.js +15 -236
  14. package/build/agents/launch-snapshot/parse-helpers.js +114 -0
  15. package/build/agents/launch-snapshot/resolve.js +17 -43
  16. package/build/agents/opencode-sdk.js +45 -12
  17. package/build/agents/providers/claude-agent-sdk/adapter.js +6 -17
  18. package/build/agents/providers/claude-agent-sdk/registration.js +214 -0
  19. package/build/agents/providers/claude-agent-sdk/session-schema.js +2 -0
  20. package/build/agents/providers/codex-app-server/adapter.js +82 -53
  21. package/build/agents/providers/codex-app-server/events.js +90 -11
  22. package/build/agents/providers/codex-app-server/identity.js +1 -0
  23. package/build/agents/providers/codex-app-server/policy.js +29 -0
  24. package/build/agents/providers/codex-app-server/registration.js +248 -0
  25. package/build/agents/providers/codex-app-server/session-schema.js +6 -0
  26. package/build/agents/providers/codex-app-server/session.js +23 -17
  27. package/build/agents/providers/codex-app-server/terms.js +1 -0
  28. package/build/agents/providers/opencode-sdk/adapter.js +94 -50
  29. package/build/agents/providers/opencode-sdk/events.js +8 -4
  30. package/build/agents/providers/opencode-sdk/registration.js +135 -0
  31. package/build/agents/providers/opencode-sdk/session-schema.js +2 -0
  32. package/build/agents/providers/opencode-sdk/session.js +21 -14
  33. package/build/agents/providers/opencode-sdk/terms.js +1 -0
  34. package/build/agents/providers/pi/adapter.js +36 -30
  35. package/build/agents/providers/pi/events.js +4 -3
  36. package/build/agents/providers/pi/registration.js +145 -0
  37. package/build/agents/providers/pi/session-schema.js +6 -0
  38. package/build/agents/providers/pi/terms.js +1 -0
  39. package/build/agents/providers/registration.js +7 -0
  40. package/build/agents/providers/registry.js +76 -0
  41. package/build/agents/selector.js +2 -29
  42. package/build/agents/types.js +10 -0
  43. package/build/artifact-directories.js +22 -0
  44. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  45. package/build/cli/commands/akuma/akuma/meta.js +1 -1
  46. package/build/cli/commands/akuma/list/meta-ls.js +1 -1
  47. package/build/cli/commands/akuma/view/handler.js +5 -4
  48. package/build/cli/commands/akuma/view/meta.js +1 -1
  49. package/build/cli/commands/akuma.js +6 -0
  50. package/build/cli/commands/contract/amend/handler.js +8 -2
  51. package/build/cli/commands/contract/amend/meta.js +4 -4
  52. package/build/cli/commands/contract/arc/handler.js +8 -2
  53. package/build/cli/commands/contract/arc/meta.js +4 -4
  54. package/build/cli/commands/contract/audit/handler.js +16 -0
  55. package/build/cli/commands/contract/audit/meta.js +16 -0
  56. package/build/cli/commands/contract/bind/handler.js +4 -3
  57. package/build/cli/commands/contract/bind/meta.js +44 -8
  58. package/build/cli/commands/contract/catalog.js +2 -0
  59. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  60. package/build/cli/commands/contract/forfeit/meta.js +1 -1
  61. package/build/cli/commands/contract/log/handler.js +3 -3
  62. package/build/cli/commands/contract/log/meta.js +1 -1
  63. package/build/cli/commands/contract/petition/handler.js +5 -8
  64. package/build/cli/commands/contract/petition/meta.js +6 -6
  65. package/build/cli/commands/contract/renew/handler.js +20 -6
  66. package/build/cli/commands/contract/renew/meta.js +6 -5
  67. package/build/cli/commands/metadata.js +59 -0
  68. package/build/cli/commands/projection/call/handler.js +6 -5
  69. package/build/cli/commands/projection/call/meta.js +4 -1
  70. package/build/cli/commands/projection/kill/handler.js +16 -32
  71. package/build/cli/commands/projection/kill/meta.js +1 -1
  72. package/build/cli/commands/projection/revive/meta.js +4 -1
  73. package/build/cli/commands/projection/shared.js +28 -0
  74. package/build/cli/commands/projection/status/handler.js +1 -1
  75. package/build/cli/commands/projection/status/meta.js +1 -1
  76. package/build/cli/commands/projection/tell/handler.js +14 -30
  77. package/build/cli/commands/projection/tell/meta.js +4 -1
  78. package/build/cli/commands/projection/wait/handler.js +45 -22
  79. package/build/cli/commands/projection/wait/meta.js +8 -3
  80. package/build/cli/commands/registry.js +2 -54
  81. package/build/cli/commands/shared.js +10 -7
  82. package/build/cli/commands/skills/install/handler.js +2 -5
  83. package/build/cli/commands/skills/install/meta.js +1 -1
  84. package/build/cli/commands/skills/skills/handler.js +1 -1
  85. package/build/cli/commands/skills/skills/meta.js +1 -1
  86. package/build/cli/commands/system/completion/meta.js +1 -1
  87. package/build/cli/commands/system/dump-env/meta.js +1 -1
  88. package/build/cli/commands/system/guide/meta.js +1 -1
  89. package/build/cli/commands/task/add/handler.js +7 -15
  90. package/build/cli/commands/task/add/meta.js +4 -1
  91. package/build/cli/commands/task/catalog.js +2 -2
  92. package/build/cli/commands/task/doctor/meta.js +1 -1
  93. package/build/cli/commands/task/done/handler.js +4 -8
  94. package/build/cli/commands/task/done/meta.js +1 -1
  95. package/build/cli/commands/task/drop/handler.js +3 -6
  96. package/build/cli/commands/task/drop/meta.js +1 -1
  97. package/build/cli/commands/task/ls/handler.js +12 -8
  98. package/build/cli/commands/task/ls/meta.js +3 -3
  99. package/build/cli/commands/task/shared.js +14 -4
  100. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  101. package/build/cli/commands/task/show/meta.js +8 -0
  102. package/build/cli/commands/task/start/handler.js +3 -6
  103. package/build/cli/commands/task/start/meta.js +1 -1
  104. package/build/cli/commands/task/stop/handler.js +4 -8
  105. package/build/cli/commands/task/stop/meta.js +1 -1
  106. package/build/cli/commands/task/task/handler.js +1 -1
  107. package/build/cli/commands/task/task/meta.js +1 -1
  108. package/build/cli/commands/task/update/handler.js +6 -11
  109. package/build/cli/commands/task/update/meta.js +4 -1
  110. package/build/cli/completion.js +1 -1
  111. package/build/cli/flags.js +37 -6
  112. package/build/cli/help.js +7 -14
  113. package/build/cli/index.js +112 -154
  114. package/build/cli/missing-subcommand.js +7 -0
  115. package/build/cli/output-stream-error-policy.js +19 -5
  116. package/build/cli/parse-flags.js +33 -7
  117. package/build/cli/parse-metadata.js +3 -9
  118. package/build/cli/parse-selectors.js +2 -1
  119. package/build/cli/parse.js +35 -5
  120. package/build/cli/projection-address.js +12 -12
  121. package/build/cli/render/address.js +41 -37
  122. package/build/cli/render/arc.js +24 -12
  123. package/build/cli/render/audit.js +121 -0
  124. package/build/cli/render/call.js +11 -5
  125. package/build/cli/render/errors.js +36 -1
  126. package/build/cli/render/format.js +4 -0
  127. package/build/cli/render/kanshi.js +6 -12
  128. package/build/cli/render/kill.js +9 -0
  129. package/build/cli/render/misc.js +58 -13
  130. package/build/cli/render/petition.js +20 -15
  131. package/build/cli/render/projection-activity.js +15 -6
  132. package/build/cli/render/shared.js +60 -21
  133. package/build/cli/render/skills.js +10 -0
  134. package/build/cli/render/status.js +56 -23
  135. package/build/cli/render/success-response.js +78 -0
  136. package/build/cli/render/task.js +66 -57
  137. package/build/cli/render/tell.js +5 -0
  138. package/build/cli/render/terminal-failure.js +63 -0
  139. package/build/cli/render/types.js +1 -0
  140. package/build/cli/render/wait.js +68 -38
  141. package/build/cli/types.js +1 -1
  142. package/build/config/akuma-loader.js +0 -5
  143. package/build/config/env-keys.js +3 -1
  144. package/build/config/env.js +3 -0
  145. package/build/config/provider-policy-ownership.js +3 -32
  146. package/build/config/settings/disease.js +8 -1
  147. package/build/config/settings/loader.js +11 -3
  148. package/build/config/settings/provider-instance-fields.js +51 -0
  149. package/build/config/settings/schema.js +6 -176
  150. package/build/core/addressing.js +66 -16
  151. package/build/core/amend.js +61 -18
  152. package/build/core/arc.js +33 -73
  153. package/build/core/audit.js +650 -0
  154. package/build/core/bind.js +82 -35
  155. package/build/core/call/call.js +15 -9
  156. package/build/core/call/context.js +6 -51
  157. package/build/core/call/execution.js +22 -22
  158. package/build/core/call/prompt.js +14 -1
  159. package/build/core/command-markdown.js +45 -0
  160. package/build/core/context.js +13 -7
  161. package/build/core/contract-view.js +22 -6
  162. package/build/core/draft.js +139 -19
  163. package/build/core/entry.js +3 -4
  164. package/build/core/forfeit.js +5 -2
  165. package/build/core/hints.js +1 -75
  166. package/build/core/identifier-slug.js +66 -0
  167. package/build/core/ids.js +25 -11
  168. package/build/core/ledger.js +20 -68
  169. package/build/core/markdown/lex.js +2 -1
  170. package/build/core/markdown/parser.js +0 -68
  171. package/build/core/markdown/render.js +68 -0
  172. package/build/core/markdown/sections.js +2 -1
  173. package/build/core/outcome-base.js +4 -0
  174. package/build/core/projection/generation/database.js +41 -52
  175. package/build/core/projection/generation/model.js +63 -2
  176. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  177. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +36 -53
  178. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  179. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +9 -7
  180. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  181. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +19 -3
  182. package/build/core/projection/generation/store.js +3 -3
  183. package/build/core/projection/generation/transitions.js +19 -3
  184. package/build/core/projection/index.js +27 -0
  185. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  186. package/build/core/{projection-alias.js → projection/projection-alias.js} +13 -13
  187. package/build/core/projection/projection-coordinate.js +61 -0
  188. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  189. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +37 -78
  190. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  191. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  192. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +16 -2
  193. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  194. package/build/core/{projection-status.js → projection/projection-status.js} +163 -45
  195. package/build/core/projection/projection-terminal-failure.js +106 -0
  196. package/build/core/projection/projection-wait.js +275 -0
  197. package/build/core/projection/projection-wake.js +227 -0
  198. package/build/core/provider-loader-ports.js +4 -0
  199. package/build/core/ref-log.js +65 -40
  200. package/build/core/registry.js +63 -10
  201. package/build/core/renew-session.js +128 -0
  202. package/build/core/renew.js +448 -102
  203. package/build/core/scope.js +272 -68
  204. package/build/core/seal.js +165 -40
  205. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +8 -8
  206. package/build/core/{claim.js → settlement/claim.js} +32 -8
  207. package/build/core/settlement/index.js +19 -0
  208. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +24 -49
  209. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  210. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  211. package/build/core/settlement/petition-io.js +1 -0
  212. package/build/core/{petition-preview.js → settlement/petition-preview.js} +2 -2
  213. package/build/core/{petition.js → settlement/petition.js} +21 -12
  214. package/build/core/settlement/queue-read-model.js +143 -0
  215. package/build/core/settlement/queue-seat-allocation.js +156 -0
  216. package/build/core/settlement/queue.js +6 -0
  217. package/build/core/settlement/settlement.js +288 -0
  218. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  219. package/build/core/settlement/verification.js +59 -0
  220. package/build/core/status/board.js +62 -41
  221. package/build/core/status/ledger-batch.js +158 -0
  222. package/build/core/status/reconciliation.js +10 -4
  223. package/build/core/stored-agent-event.js +6 -2
  224. package/build/core/task/board.js +41 -4
  225. package/build/core/task/commands.js +45 -28
  226. package/build/core/task/document.js +5 -0
  227. package/build/core/task/index.js +12 -0
  228. package/build/core/task/model.js +8 -1
  229. package/build/core/task/query.js +172 -0
  230. package/build/core/task/settlement-git.js +265 -74
  231. package/build/core/task/settlement-policy.js +67 -29
  232. package/build/core/task/source-board.js +1 -0
  233. package/build/core/task/task-contract.js +444 -0
  234. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  235. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  236. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  237. package/build/core/{task-git-store.js → task/task-git-store.js} +90 -19
  238. package/build/core/task/task-repository.js +1 -0
  239. package/build/core/{task-store-repository.js → task/task-store-repository.js} +6 -4
  240. package/build/core/task/task-store.js +1 -0
  241. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  242. package/build/core/task/task.js +7 -0
  243. package/build/core/task/tree.js +37 -0
  244. package/build/core/task/validation.js +5 -2
  245. package/build/core/transcripts.js +15 -3
  246. package/build/core/worktree-path.js +43 -28
  247. package/build/flow-error.js +83 -39
  248. package/build/fs/path-coordinate.js +16 -0
  249. package/build/generated/version.js +3 -2
  250. package/build/git/branches.js +2 -1
  251. package/build/git/core.js +106 -2
  252. package/build/git/diff/parsers.js +23 -13
  253. package/build/git/diff/structured.js +127 -0
  254. package/build/git/path-coordinate.js +17 -0
  255. package/build/git/refs.js +57 -0
  256. package/build/git/streaming-batch.js +301 -0
  257. package/build/git/worktree.js +65 -0
  258. package/build/index.js +9 -9
  259. package/build/keiyaku.js +2 -36
  260. package/build/telemetry/logger.js +4 -2
  261. package/package.json +4 -10
  262. package/skills/keiyaku/SKILL.md +5 -4
  263. package/skills/keiyaku-akuma/SKILL.md +25 -11
  264. package/skills/keiyaku-task/SKILL.md +17 -8
  265. package/skills/keiyaku-workflow/SKILL.md +58 -9
  266. package/build/agents/providers/codex-sdk.js +0 -329
  267. package/build/cli/commands/task/view/meta.js +0 -8
  268. package/build/core/command-io.js +0 -174
  269. package/build/core/path-coordinate.js +0 -27
  270. package/build/core/petition-claim.js +0 -117
  271. package/build/core/petition-run.js +0 -83
  272. package/build/core/projection-coordinate.js +0 -63
  273. package/build/core/projection-wait.js +0 -165
  274. package/build/core/projection-wake.js +0 -136
  275. package/build/core/queue.js +0 -343
  276. package/build/core/task-contract.js +0 -237
  277. package/build/core/task.js +0 -4
  278. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  279. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  280. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  281. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  282. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -1,16 +1,21 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { spawnSync } from "node:child_process";
3
2
  import * as fs from "node:fs";
4
3
  import * as path from "node:path";
5
4
  import { DatabaseSync } from "node:sqlite";
6
- import { FlowError } from "../flow-error.js";
7
- import { syncDirectorySync } from "../fs/durability.js";
8
- import { isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS } from "../git/core.js";
9
- import { gitCommonRootPath, normalizeGitPath } from "./path-coordinate.js";
10
- const TASK_ID = /^k-[0-9a-f]{12}$/;
5
+ import { setTimeout as delay } from "node:timers/promises";
6
+ import { FlowError } from "../../flow-error.js";
7
+ import { syncDirectorySync } from "../../fs/durability.js";
8
+ import { normalizeFilesystemCoordinate } from "../../fs/path-coordinate.js";
9
+ import { isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS, runGitProcess } from "../../git/core.js";
10
+ import { gitCommonRootPath, normalizeGitPath } from "../../git/path-coordinate.js";
11
+ import { inspectCurrentBranch, publishTaskMutationCommit } from "./settlement-git.js";
12
+ import { isTaskId } from "./validation.js";
11
13
  const SQLITE_BUSY = 5;
12
14
  const LOCK_FILE = "task-lock.sqlite";
13
15
  const GIT_LOCK_FILE = "keiyaku-task-lock.sqlite";
16
+ export const TASK_LOCK_ADMISSION_TIMEOUT_MS = 4_000;
17
+ export const TASK_LOCK_ADMISSION_INITIAL_DELAY_MS = 10;
18
+ export const TASK_LOCK_ADMISSION_MAX_DELAY_MS = 100;
14
19
  export class TaskLockHeldError extends Error {
15
20
  path;
16
21
  code = "TASK_LOCK_HELD";
@@ -20,6 +25,16 @@ export class TaskLockHeldError extends Error {
20
25
  this.name = "TaskLockHeldError";
21
26
  }
22
27
  }
28
+ export class TaskLockAdmissionTimeoutError extends FlowError {
29
+ path;
30
+ timeoutMs;
31
+ constructor(path, timeoutMs, options) {
32
+ super("TASK_LOCK_ADMISSION_TIMEOUT", `task lock admission timed out after ${timeoutMs}ms: ${path}`, { cause: options?.cause });
33
+ this.path = path;
34
+ this.timeoutMs = timeoutMs;
35
+ this.name = "TaskLockAdmissionTimeoutError";
36
+ }
37
+ }
23
38
  function errorText(error) {
24
39
  return error instanceof Error ? error.message : String(error);
25
40
  }
@@ -29,17 +44,11 @@ function isSqliteBusy(error) {
29
44
  && "errcode" in error
30
45
  && error.errcode === SQLITE_BUSY;
31
46
  }
32
- function defaultRunGit(cwd, args, input) {
33
- return spawnSync("git", [...args], {
34
- cwd,
35
- input,
36
- encoding: "utf8",
37
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" },
38
- stdio: ["pipe", "pipe", "pipe"],
39
- });
47
+ async function defaultRunGit(cwd, args, input) {
48
+ return await runGitProcess(cwd, args, { input });
40
49
  }
41
50
  function nearestDirectoryBoard(cwd) {
42
- const original = path.resolve(cwd);
51
+ const original = path.resolve(normalizeFilesystemCoordinate(cwd));
43
52
  let candidate = original;
44
53
  while (true) {
45
54
  if (lstatIfPresent(path.join(candidate, ".keiyaku")))
@@ -54,7 +63,8 @@ export function taskBoardRootFromGitDir(rawGitDir, platform = process.platform)
54
63
  return gitCommonRootPath(rawGitDir, platform);
55
64
  }
56
65
  export async function locateTaskBoard(cwd, runGitProbe = defaultRunGit) {
57
- const probe = runGitProbe(cwd, ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
66
+ cwd = normalizeFilesystemCoordinate(cwd);
67
+ const probe = await runGitProbe(cwd, ["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
58
68
  if (probe.error && !isGitExecutableUnavailable(probe.error)) {
59
69
  throw new FlowError("INTERNAL_STATE", `cannot inspect task repository: ${probe.error.message}`, { cause: probe.error });
60
70
  }
@@ -72,7 +82,7 @@ export async function locateTaskBoard(cwd, runGitProbe = defaultRunGit) {
72
82
  if (bare === "true" || insideWorktree !== "true") {
73
83
  throw new FlowError("NOT_GIT_REPO", "bare repositories are not supported for task storage");
74
84
  }
75
- const facts = runGitProbe(cwd, ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"]);
85
+ const facts = await runGitProbe(cwd, ["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"]);
76
86
  if (facts.error) {
77
87
  throw new FlowError("INTERNAL_STATE", `git rev-parse failed: ${facts.error.message}`, { cause: facts.error });
78
88
  }
@@ -139,6 +149,31 @@ function acquireTaskLock(lockPath) {
139
149
  throw new FlowError("INTERNAL_STATE", `cannot acquire task lock: ${errorText(error)}`, { cause: error });
140
150
  }
141
151
  }
152
+ async function acquireTaskLockWithAdmission(lockPath, deps) {
153
+ const now = deps.now ?? Date.now;
154
+ const random = deps.random ?? Math.random;
155
+ const sleep = deps.sleep ?? ((milliseconds, signal) => (delay(milliseconds, undefined, signal ? { signal } : undefined)));
156
+ const deadline = now() + TASK_LOCK_ADMISSION_TIMEOUT_MS;
157
+ let delayCapMs = TASK_LOCK_ADMISSION_INITIAL_DELAY_MS;
158
+ for (;;) {
159
+ deps.signal?.throwIfAborted();
160
+ try {
161
+ return acquireTaskLock(lockPath);
162
+ }
163
+ catch (error) {
164
+ if (!(error instanceof TaskLockHeldError))
165
+ throw error;
166
+ const observedAt = now();
167
+ if (observedAt >= deadline) {
168
+ throw new TaskLockAdmissionTimeoutError(lockPath, TASK_LOCK_ADMISSION_TIMEOUT_MS, { cause: error });
169
+ }
170
+ const unit = Math.max(0, Math.min(1, random()));
171
+ const jitteredDelayMs = Math.ceil((delayCapMs / 2) + ((delayCapMs / 2) * unit));
172
+ await sleep(Math.min(jitteredDelayMs, deadline - observedAt), deps.signal);
173
+ delayCapMs = Math.min(TASK_LOCK_ADMISSION_MAX_DELAY_MS, delayCapMs * 2);
174
+ }
175
+ }
176
+ }
142
177
  export async function inspectTaskLock(cwd) {
143
178
  const lockPath = taskLockPath(await locateTaskBoard(cwd));
144
179
  if (!fs.existsSync(lockPath))
@@ -236,7 +271,7 @@ function validateWrites(writes) {
236
271
  const targets = new Set();
237
272
  for (const write of writes) {
238
273
  const target = `${write.id}.md`;
239
- if (!TASK_ID.test(write.id)) {
274
+ if (!isTaskId(write.id)) {
240
275
  throw new FlowError("INTERNAL_STATE", `invalid task mutation id: ${write.id}`);
241
276
  }
242
277
  if (targets.has(target))
@@ -321,9 +356,32 @@ export async function openTaskStore(cwd, deps = {}) {
321
356
  },
322
357
  async mutate(apply) {
323
358
  return withLockHeldTaskBoard(cwd, async (transaction) => {
359
+ const previousSources = transaction.sources;
324
360
  const plan = await apply(transaction.sources);
325
361
  validatePlan(plan);
362
+ let subject;
363
+ if (transaction.location.mode === "git") {
364
+ subject = plan.subject?.trim();
365
+ if (!subject) {
366
+ throw new FlowError("INTERNAL_STATE", "git task mutation requires a micro-commit subject");
367
+ }
368
+ await inspectCurrentBranch(transaction.location.root);
369
+ }
326
370
  await transaction.replace(plan.writes);
371
+ if (transaction.location.mode === "git") {
372
+ try {
373
+ await (deps.publishMutation ?? publishTaskMutationCommit)({
374
+ cwd: transaction.location.root,
375
+ gitDir: transaction.location.gitDir,
376
+ writes: plan.writes,
377
+ subject: subject,
378
+ });
379
+ }
380
+ catch (error) {
381
+ await transaction.restore(plan.writes, previousSources);
382
+ throw error;
383
+ }
384
+ }
327
385
  return plan.value;
328
386
  }, deps, board);
329
387
  },
@@ -333,7 +391,7 @@ export async function withLockHeldTaskBoard(cwd, apply, deps = {}, locatedBoard)
333
391
  const board = locatedBoard ?? await locateTaskBoard(cwd, deps.runGit);
334
392
  const directory = taskDirectory(board);
335
393
  assertKeiyakuDirectory(board);
336
- const database = acquireTaskLock(taskLockPath(board));
394
+ const database = await acquireTaskLockWithAdmission(taskLockPath(board), deps);
337
395
  try {
338
396
  let sources = await readTaskSources(directory);
339
397
  const transaction = {
@@ -368,6 +426,19 @@ export async function withLockHeldTaskBoard(cwd, apply, deps = {}, locatedBoard)
368
426
  }
369
427
  sources = await readTaskSources(directory);
370
428
  },
429
+ async restore(writes, previousSources) {
430
+ validateWrites(writes);
431
+ const currentSources = await readTaskSources(directory);
432
+ if (!sameSources(sources, currentSources)) {
433
+ throw new FlowError("CONCURRENT_MODIFICATION", "task sources changed while rolling back a failed publication");
434
+ }
435
+ const previousById = new Map(previousSources.map((source) => [path.basename(source.path, ".md"), source.text]));
436
+ restoreFiles(writes.map((write) => ({
437
+ path: path.join(directory, mutationTarget(write)),
438
+ text: previousById.get(write.id),
439
+ })), deps);
440
+ sources = await readTaskSources(directory);
441
+ },
371
442
  };
372
443
  return await apply(transaction);
373
444
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,7 +1,7 @@
1
- import { requireHealthy } from "./task/board.js";
2
- import { parseTaskDocument, serializeTaskDocument } from "./task/document.js";
3
- import { buildTaskSourceBoard } from "./task/source-board.js";
4
- function taskBoardFromSourceBoard(sourceBoard) {
1
+ import { requireHealthy } from "./board.js";
2
+ import { parseTaskDocument, serializeTaskDocument } from "./document.js";
3
+ import { buildTaskSourceBoard } from "./source-board.js";
4
+ export function taskBoardFromSourceBoard(sourceBoard) {
5
5
  const tasks = sourceBoard.tasks.map((task) => ({
6
6
  id: task.id,
7
7
  title: task.title,
@@ -11,6 +11,7 @@ function taskBoardFromSourceBoard(sourceBoard) {
11
11
  parent: task.parent,
12
12
  from: [...task.from],
13
13
  createdAt: task.createdAt,
14
+ ...(task.updatedAt === undefined ? {} : { updatedAt: task.updatedAt }),
14
15
  creator: task.creator,
15
16
  ...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
16
17
  ...(task.contractId === undefined ? {} : { contractId: task.contractId }),
@@ -66,6 +67,7 @@ function mutationPlan(sources, mutation) {
66
67
  return {
67
68
  writes: prepared.writes,
68
69
  value: mutation.select(prepared.board),
70
+ ...(mutation.subject === undefined ? {} : { subject: mutation.subject }),
69
71
  };
70
72
  }
71
73
  export function taskRepositoryFromStore(store) {
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { FlowError } from "../flow-error.js";
1
+ import { FlowError } from "../../flow-error.js";
2
2
  function documentFacts(task) {
3
3
  return JSON.stringify({
4
4
  id: task.id,
@@ -41,6 +41,7 @@ export function withTaskWorktreePolicy(repository, contractId) {
41
41
  const mutation = await apply(before);
42
42
  return {
43
43
  replacements: mutation.replacements,
44
+ ...(mutation.subject === undefined ? {} : { subject: mutation.subject }),
44
45
  select(after) {
45
46
  validateTaskWorktreeMutation(before, mutation.replacements, after, contractId);
46
47
  return mutation.select(after);
@@ -0,0 +1,7 @@
1
+ export { deriveTaskDisplay, parseTaskFilter, parseTaskSort, selectTaskList } from "./query.js";
2
+ export { advancedTaskUpdatedAt, taskUpdatedAt } from "./model.js";
3
+ export { parseTaskDocument, serializeTaskDocument, } from "./document.js";
4
+ export { resolveTaskReadId, } from "./board.js";
5
+ export { buildTaskBoard } from "./source-board.js";
6
+ export { buildTaskTree } from "./tree.js";
7
+ export { readTaskBoard, createTask, updateTask, startTask, stopTask, doneTask, dropTask, selectSoleInProgress, rollbackOrphanTaskPointers, } from "./commands.js";
@@ -0,0 +1,37 @@
1
+ function compareText(left, right) {
2
+ return left < right ? -1 : left > right ? 1 : 0;
3
+ }
4
+ /** Board task order is the deterministic order for every parent child list. */
5
+ function compareBoardOrder(left, right) {
6
+ const leftTerminal = left.state === "done" || left.state === "drop" ? 1 : 0;
7
+ const rightTerminal = right.state === "done" || right.state === "drop" ? 1 : 0;
8
+ return leftTerminal - rightTerminal
9
+ || left.pri - right.pri
10
+ || compareText(left.createdAt, right.createdAt)
11
+ || compareText(left.id, right.id);
12
+ }
13
+ function diagnosticOrder(left, right) {
14
+ return compareText(left.code, right.code)
15
+ || compareText(left.id ?? "", right.id ?? "")
16
+ || compareText(left.message, right.message);
17
+ }
18
+ export function buildTaskTree(board, rootId) {
19
+ const childrenByParent = new Map();
20
+ for (const task of board.tasks) {
21
+ if (task.parent === null)
22
+ continue;
23
+ childrenByParent.set(task.parent, [...(childrenByParent.get(task.parent) ?? []), task]);
24
+ }
25
+ for (const children of childrenByParent.values())
26
+ children.sort(compareBoardOrder);
27
+ const visit = (parentId, ancestors) => (childrenByParent.get(parentId) ?? [])
28
+ .filter((child) => !ancestors.has(child.id))
29
+ .map((child) => ({
30
+ task: child,
31
+ children: visit(child.id, new Set([...ancestors, child.id])),
32
+ }));
33
+ return {
34
+ descendants: visit(rootId, new Set([rootId])),
35
+ diagnostics: [...board.diseases].sort(diagnosticOrder),
36
+ };
37
+ }
@@ -1,5 +1,5 @@
1
1
  import { FlowError } from "../../flow-error.js";
2
- export const TASK_ID = /^k-[0-9a-f]{12}$/;
2
+ import { isNormalizedIdentifierSlug } from "../identifier-slug.js";
3
3
  export const TASK_STATES = new Set(["open", "in_progress", "done", "drop"]);
4
4
  const CANONICAL_ISO_UTC_MS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
5
5
  export function invalid(message) {
@@ -13,10 +13,13 @@ export function nonblank(field, value) {
13
13
  }
14
14
  export function fullId(field, value) {
15
15
  const id = nonblank(field, value);
16
- if (!TASK_ID.test(id))
16
+ if (!isTaskId(id))
17
17
  invalid(`task field '${field}' must be a canonical full task id`);
18
18
  return id;
19
19
  }
20
+ export function isTaskId(value) {
21
+ return isNormalizedIdentifierSlug(value);
22
+ }
20
23
  export function canonicalTimestamp(field, value) {
21
24
  const timestamp = nonblank(field, value);
22
25
  if (!CANONICAL_ISO_UTC_MS.test(timestamp)) {
@@ -21,6 +21,15 @@ const RESPONSE_HISTORY_SESSION_KEY = "session";
21
21
  const RESPONSE_HISTORY_LAUNCH_SNAPSHOT_KEY = "launch_snapshot";
22
22
  const RESPONSE_HISTORY_PROVENANCE_KEY = "provenance";
23
23
  const REVIEW_HISTORY_FILE_RE = /^\d{8}-\d{9}_REVIEW_(\d+)\.md$/;
24
+ /** Artifact-owner rendering of seal receipt/diffstat facts into the durable Diff section. */
25
+ export function renderSealDiffSection(sealed, emptyLabel) {
26
+ if (!sealed)
27
+ return emptyLabel;
28
+ if (sealed.receipts.length === 0)
29
+ return "nothing sealed";
30
+ const count = sealed.receipts.length;
31
+ return `${count} receipt${count === 1 ? "" : "s"} · +${sealed.diffstat.insertions} −${sealed.diffstat.deletions}`;
32
+ }
24
33
  export const RESPONSE_ARTIFACT_HEADINGS = {
25
34
  call: "# Call",
26
35
  bind: "# Bind",
@@ -100,9 +109,12 @@ function buildMarkdown(input, iso, provenance) {
100
109
  return [...frontmatter, "# Call", "", "## Task", section(input.task), "", "## Context", section(input.context), "", "## Response", section(input.response), ""].join("\n");
101
110
  if (input.tool === "review")
102
111
  return [...frontmatter, "# Review", "", "## Prompt", section(input.prompt), "", "## Response", section(input.response), ""].join("\n");
103
- if (input.tool === "arc")
104
- return [...frontmatter, "# Arc", "", "## Objective", section(input.objective), "", "## Brief", section(input.brief), "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(input.diff), ""].join("\n");
105
- return [...frontmatter, "# Renew", "", "## Base", `${input.oldBase} -> ${input.newBase}`, "", "## Head", `${input.oldHead} -> ${input.newHead}`, "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(input.diff), ""].join("\n");
112
+ if (input.tool === "arc") {
113
+ const diff = renderSealDiffSection(input.sealed, input.emptyDiffLabel);
114
+ return [...frontmatter, "# Arc", "", "## Objective", section(input.objective), "", "## Brief", section(input.brief), "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(diff), ""].join("\n");
115
+ }
116
+ const diff = renderSealDiffSection(input.sealed, input.emptyDiffLabel);
117
+ return [...frontmatter, "# Renew", "", "## Base", `${input.oldBase} -> ${input.newBase}`, "", "## Head", `${input.oldHead} -> ${input.newHead}`, "", "## Ledger Commit", section(input.ledgerCommit), "", "## Summary", section(input.summary), "", "## Diff", section(diff), ""].join("\n");
106
118
  }
107
119
  /** The sole response artifact writer. */
108
120
  export async function persistResponseHistory(input) {
@@ -3,9 +3,9 @@ import * as fs from "node:fs/promises";
3
3
  import { FlowError } from "../flow-error.js";
4
4
  import { createGit, wrapGitError } from "../git/core.js";
5
5
  import { readLedger } from "./ledger.js";
6
- import { listContractIds } from "./ledger.js";
7
6
  import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
8
- import { gitCommonRootPath, resolveGitCommonRoot } from "./path-coordinate.js";
7
+ import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
8
+ import { gitCommonRootPath, resolveGitCommonRoot } from "../git/path-coordinate.js";
9
9
  export function findBindEntry(entries) {
10
10
  return entries.find((entry) => entry.kind === "bind");
11
11
  }
@@ -36,8 +36,10 @@ async function listWorktrees(cwd) {
36
36
  throw wrapGitError("worktree list --porcelain -z", error, cwd);
37
37
  }
38
38
  }
39
- function pathContains(root, candidate) {
40
- const relative = path.relative(root, candidate);
39
+ export function worktreeContainsCoordinate(root, candidate) {
40
+ const normalizedRoot = normalizeFilesystemCoordinate(root);
41
+ const normalizedCandidate = normalizeFilesystemCoordinate(candidate);
42
+ const relative = path.relative(normalizedRoot, normalizedCandidate);
41
43
  return relative === "" || (!path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`));
42
44
  }
43
45
  export async function stableRepoRoot(cwd) {
@@ -100,6 +102,37 @@ async function findContractWorktree(cwd, contractId) {
100
102
  export async function findContractWorktreePath(cwd, contractId) {
101
103
  return (await findContractWorktree(cwd, contractId)).path;
102
104
  }
105
+ export async function findRegisteredContractWorktreePath(cwd, contractId) {
106
+ const stableRoot = await stableRepoRoot(cwd);
107
+ const ledger = await readLedger(stableRoot, contractId);
108
+ const bind = ledger ? findBindEntry(ledger.entries) : undefined;
109
+ if (!bind || bind.data.workspace !== "worktree")
110
+ return undefined;
111
+ let expected;
112
+ try {
113
+ expected = await fs.realpath(await contractWorktreePathFromBind(stableRoot, contractId, bind));
114
+ }
115
+ catch {
116
+ return undefined;
117
+ }
118
+ for (const worktree of await listWorktrees(stableRoot)) {
119
+ try {
120
+ if (await fs.realpath(worktree.path) === expected)
121
+ return worktree.path;
122
+ }
123
+ catch {
124
+ // A stale Git registration is not an engine-owned worktree fact.
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ /**
130
+ * Delivery coordinate for dirty admission and KEIYAKU.md render: the linked
131
+ * contract worktree when Git registers one, otherwise cwd (workspace here).
132
+ */
133
+ export async function resolveContractDeliveryCwd(cwd, contractId) {
134
+ return (await findContractWorktreePath(cwd, contractId)) ?? cwd;
135
+ }
103
136
  /**
104
137
  * Resolve the contract id for an engine-owned bound worktree containing cwd.
105
138
  * Matches the canonical bind place path and worktree workspace only; ledger
@@ -132,27 +165,23 @@ async function contractIdForEngineOwnedWorktree(cwd, options) {
132
165
  }
133
166
  if (canonicalWorktree !== expectedWorktree)
134
167
  continue;
135
- if (pathContains(canonicalWorktree, canonicalCwd))
168
+ if (worktreeContainsCoordinate(canonicalWorktree, canonicalCwd))
136
169
  return contractId;
137
170
  }
138
171
  return undefined;
139
172
  }
140
- /**
141
- * Resolve the active commission presented by an engine-owned contract
142
- * worktree containing cwd. Ordinary repository directories and arbitrary
143
- * worktrees never present a commission address.
144
- */
145
- export async function contractPresentedByWorktree(cwd) {
146
- return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
147
- }
148
173
  /**
149
174
  * Resolve the contract bound by an engine-owned worktree containing cwd,
150
175
  * including when the ledger is already terminal. Task CLI contract context
151
- * uses this residual binding; call/addressing keep active-only presentation.
176
+ * uses this residual binding; call attachment never infers from cwd.
152
177
  */
153
178
  export async function contractBoundByEngineWorktree(cwd) {
154
179
  return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
155
180
  }
181
+ /** Existing-contract verbs may infer only a verified, active engine binding. */
182
+ export async function activeContractBoundByEngineWorktree(cwd) {
183
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
184
+ }
156
185
  export async function cleanupContractWorkspace(cwd, contractId) {
157
186
  const stableRoot = await stableRepoRoot(cwd);
158
187
  const git = createGit(stableRoot);
@@ -197,17 +226,3 @@ export async function cleanupContractWorkspace(cwd, contractId) {
197
226
  }
198
227
  return facts;
199
228
  }
200
- export async function activeContractIdForWorktreeDir(cwd, dirname) {
201
- const ids = await listContractIds(cwd);
202
- for (const id of ids) {
203
- const ledger = await readLedger(cwd, id);
204
- if (!ledger || isTerminalState(deriveContractState(ledger.entries)))
205
- continue;
206
- if (id === dirname)
207
- return id;
208
- const place = ledger ? findBindEntry(ledger.entries)?.data.place : undefined;
209
- if (place === dirname)
210
- return id;
211
- }
212
- return undefined;
213
- }
@@ -1,4 +1,5 @@
1
1
  import { ACTOR_IDENTITY, SETTINGS_PATH_SEGMENTS } from "./keiyaku-constants.js";
2
+ import { formatArtifactDirectories, KEIYAKU_ARTIFACT_DIRS } from "./artifact-directories.js";
2
3
  const SETTINGS_DISPLAY_PATH = SETTINGS_PATH_SEGMENTS.join("/");
3
4
  const DEFAULT_HINT_CONTEXT = {
4
5
  bindToolName: "bind",
@@ -17,69 +18,115 @@ export function renderTemplate(template, values) {
17
18
  return Object.prototype.hasOwnProperty.call(values, key) ? values[key] : match;
18
19
  });
19
20
  }
20
- function resolveFlowErrorHint(code, context) {
21
+ function formatInvalidSettingsDiseaseHints(diseases) {
22
+ return diseases.map((disease) => {
23
+ const where = disease.knob ? `${disease.source}:${disease.knob}` : `${disease.source}:(root)`;
24
+ return `${where}: ${disease.reason}`;
25
+ });
26
+ }
27
+ /** CLI-owned typed hint table. Domain code supplies code + facts only. */
28
+ export function resolveFlowErrorHints(code, context, facts) {
29
+ if (facts?.kind === "invalid_settings") {
30
+ return formatInvalidSettingsDiseaseHints(facts.diseases);
31
+ }
32
+ if (facts?.kind === "verification_failure") {
33
+ return [
34
+ `Fix failures from '${facts.label}', then retry CLAIM.`,
35
+ `stdout:\n${facts.stdout}`,
36
+ `stderr:\n${facts.stderr}`,
37
+ ];
38
+ }
39
+ if (facts?.kind === "dirty_worktree") {
40
+ return [
41
+ `Files under \`.keiyaku/\` are blocking the operation. Add ${formatArtifactDirectories(KEIYAKU_ARTIFACT_DIRS)} to \`.gitignore\` if you do not want those artifacts tracked.`,
42
+ ];
43
+ }
44
+ if (facts?.kind === "loose_commits") {
45
+ return [
46
+ `keiyaku @${facts.contractId} arc — opens a new arc to receipt them, then ${facts.nextVerb}`,
47
+ ];
48
+ }
49
+ if (facts?.kind === "needs_renew") {
50
+ return [`keiyaku @${facts.contractId} renew — renews delivery before petition`];
51
+ }
52
+ if (facts?.kind === "projection_in_flight") {
53
+ return ["keiyaku status — watch for the akuma to land, then renew"];
54
+ }
55
+ if (facts?.kind === "projection_wake_failure") {
56
+ return [];
57
+ }
21
58
  switch (code) {
22
59
  case "NOT_GIT_REPO":
23
- return "The provided `cwd` is not a git repository.";
60
+ return ["The provided `cwd` is not a git repository."];
24
61
  case "GIT_UNAVAILABLE":
25
- return "Install Git, or use an explicitly bare command that does not require repository authority.";
62
+ return ["Install Git, or use an explicitly bare command that does not require repository authority."];
26
63
  case "ACTIVE_KEIYAKU_EXISTS":
27
- return `Next: open an arc with \`${context.arcToolName}\`, then use \`${context.renewToolName}\` when delivery must follow the current base, or \`${context.closeToolName}\` to settle it.`;
64
+ return [`Next: open an arc with \`${context.arcToolName}\`, then use \`${context.renewToolName}\` when delivery must follow the current base, or \`${context.closeToolName}\` to settle it.`];
28
65
  case "CONTRACT_AMBIGUOUS":
29
- return "Address a specific contract workspace, or use `call --bare` to run without contract attachment.";
66
+ return ["Address a specific contract workspace, or use `call --bare` to run without contract attachment."];
30
67
  case "UNKNOWN_COMMAND":
31
- return "Run `keiyaku --help` to inspect available commands.";
68
+ return ["Run `keiyaku --help` to inspect available commands."];
32
69
  case "EMPTY_PARAM":
33
- return "One or more required parameters are empty.";
70
+ return ["One or more required parameters are empty."];
34
71
  case "DIRTY_WORKTREE":
35
- return "The working tree has uncommitted changes.";
72
+ return ["The working tree has uncommitted changes."];
36
73
  case "NOT_ACTIVE_KEIYAKU_BRANCH":
37
- return "Checkout an active Keiyaku branch to continue.";
74
+ return ["keiyaku bind — opens a new contract"];
38
75
  case "MISSING_KEIYAKU_BASE":
39
- return "Current branch is not associated with an active contract ledger.";
76
+ return ["Current branch is not associated with an active contract ledger."];
40
77
  case "MISSING_PROTOCOL_FILES":
41
- return "Required protocol file is missing (`KEIYAKU.md`).";
78
+ return ["Required protocol file is missing (`KEIYAKU.md`)."];
42
79
  case "CLOSE_MERGE_CONFLICT":
43
- return [
44
- "Renew conflict detected; the workspace was restored without ledger writes.",
45
- `Address the conflicting delivery and retry \`${context.renewToolName}\`.`,
46
- `For CLAIM conflicts: resolve and commit before \`${context.closeToolName}\` can be attempted again.`,
47
- ].join(" ");
80
+ return [`Open a Keiyaku recovery session with \`keiyaku ${context.renewToolName} --resolve\`.`];
81
+ case "RENEW_COORDINATE_DRIFT":
82
+ return [`Restore the delivery to its ledger-recorded history with Git reflog, then retry \`keiyaku ${context.renewToolName}\`.`];
83
+ case "RENEW_SESSION_OPEN":
84
+ return [`Continue the owned rewrite with \`keiyaku ${context.renewToolName} --continue\`, or restore it with \`keiyaku ${context.renewToolName} --abort\`.`];
85
+ case "RENEW_SESSION_NOT_FOUND":
86
+ return [`No recovery session is open; run \`keiyaku ${context.renewToolName}\` normally.`];
48
87
  case "CLOSE_QUALITY_GATE_FAILED":
49
- return `CLAIM denied: a configured verification command failed. Use \`${context.arcToolName}\` after addressing the failure before petitioning again.`;
88
+ return [`CLAIM denied: a configured verification command failed. Use \`${context.arcToolName}\` after addressing the failure before petitioning again.`];
50
89
  case "INVALID_BRANCH_TITLE":
51
- return "Title cannot be converted to a valid branch token for `keiyaku/<title>`.";
90
+ return ["Title cannot be converted to a valid branch token for `keiyaku/<title>`."];
52
91
  case "UNKNOWN_SUBAGENT": {
53
92
  const names = context.availableActorNames.join(", ");
54
- return `Unknown ${context.actorIdentity.toLowerCase()} name. Valid options: ${names}.`;
93
+ return [`Unknown ${context.actorIdentity.toLowerCase()} name. Valid options: ${names}.`];
55
94
  }
56
95
  case "FROM_DRAFT_NOT_FOUND":
57
- return "The path in `from_draft` does not exist. `from_draft` must reference an existing file path (absolute or relative to `cwd`).";
96
+ return ["The path in `from_draft` does not exist. `from_draft` must reference an existing file path (absolute or relative to `cwd`)."];
58
97
  case "INVALID_KEIYAKU_DRAFT":
59
- return "Invalid draft/input. Use a `# <title>` heading with non-empty `## Objective` and `## Scope` sections (or the equivalent shape in `from_draft`).";
98
+ return ["Invalid draft/input. Use a `# <title>` heading with non-empty `## Objective` and `## Scope` sections (or the equivalent shape in `from_draft`)."];
99
+ case "BIND_TASK_INCOMPLETE":
100
+ return ["Task supplies name/objective defaults; pipe only the missing sections as partial contract Markdown on stdin."];
60
101
  case "KEIYAKU_FILE_EXISTS":
61
- return `\`${context.bindToolName}\` requires a clean slate. \`KEIYAKU.md\` must be removed before starting.`;
102
+ return [`\`${context.bindToolName}\` requires a clean slate. \`KEIYAKU.md\` must be removed before starting.`];
62
103
  case "DETACHED_HEAD":
63
- return "Repository is in detached HEAD state. Address an active contract workspace before retrying.";
104
+ return ["Repository is in detached HEAD state. Address an active contract workspace before retrying."];
64
105
  case "INVALID_RESPONSE_PATH":
65
- return "`keiyaku revive ARTIFACT_ID` needs a valid previous Keiyaku response artifact that includes complete provenance plus `session` and `subagent_name`.";
106
+ return ["`keiyaku revive ARTIFACT_ID` needs a valid previous Keiyaku response artifact that includes complete provenance plus `session` and `subagent_name`."];
66
107
  case "INVALID_CLOSE_INTENT":
67
- return "Invalid close intent. Valid values are `CLAIM` and `FORFEIT`.";
108
+ return ["Invalid close intent. Valid values are `CLAIM` and `FORFEIT`."];
68
109
  case "INVALID_SETTINGS":
69
- return `Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry.`;
110
+ return [`Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry.`];
70
111
  case "INVALID_EFFORT_OVERRIDE":
71
- return "Choose an effort value declared by the selected Akuma's effortOptions.";
112
+ return ["Choose an effort value declared by the selected Akuma's effortOptions."];
72
113
  case "CONCURRENT_MODIFICATION":
73
- return `Concurrent repository changes were detected during \`${context.arcToolName}\` or \`${context.renewToolName}\`. Retry on the latest branch state.`;
114
+ return [`Concurrent repository changes were detected during \`${context.arcToolName}\` or \`${context.renewToolName}\`. Retry on the latest branch state.`];
115
+ case "TASK_LOCK_ADMISSION_TIMEOUT":
116
+ return ["Task coordination did not become available before the bounded deadline. Retry the command; repeated timeouts indicate a stuck or damaged repository lock."];
74
117
  case "INTERNAL_STATE":
75
- return "An internal state error occurred. Please verify your repository status.";
118
+ return ["An internal state error occurred. Please verify your repository status."];
119
+ case "SCOPE_VIOLATION":
120
+ return ["Files outside the contract's declared scope were modified. Append ordered scope terms with `--append-scope`, amend the contract, or move the changes back."];
121
+ case "NO_OPEN_ARC":
122
+ return ["Open an arc to receipt delivery commits after the fence, then retry."];
76
123
  case "REVIEW_REJECTED":
77
- return `CLAIM denied by the reviewer. Address the findings and retry \`${context.arcToolName}\` before petitioning again.`;
124
+ return [`CLAIM denied by the reviewer. Address the findings and retry \`${context.arcToolName}\` before petitioning again.`];
78
125
  case "VERDICT_DENIED":
79
126
  case "OATH_MISSING":
80
- return DEFAULT_ERROR_HINT;
127
+ return [DEFAULT_ERROR_HINT];
81
128
  default:
82
- return DEFAULT_ERROR_HINT;
129
+ return [DEFAULT_ERROR_HINT];
83
130
  }
84
131
  }
85
132
  function getDefaultErrorHint() {
@@ -87,14 +134,14 @@ function getDefaultErrorHint() {
87
134
  }
88
135
  export class FlowError extends Error {
89
136
  code;
90
- hints;
91
137
  review;
138
+ facts;
92
139
  constructor(code, message, options) {
93
140
  super(message, options?.cause === undefined ? undefined : { cause: options.cause });
94
141
  this.name = "FlowError";
95
142
  this.code = code;
96
- this.hints = options?.hints;
97
143
  this.review = options?.review;
144
+ this.facts = options?.facts;
98
145
  }
99
146
  }
100
147
  export function isFlowError(err) {
@@ -117,18 +164,15 @@ export function wrapFlowError(action, err) {
117
164
  if (isFlowError(err)) {
118
165
  return new FlowError(err.code, `${action} failed: ${err.message}`, {
119
166
  cause: err,
120
- hints: err.hints,
121
167
  review: err.review,
168
+ facts: err.facts,
122
169
  });
123
170
  }
124
171
  return new Error(`${action} failed: ${asMessage(err)}`);
125
172
  }
126
173
  export function pickHintsFromError(err, hintContext = DEFAULT_HINT_CONTEXT) {
127
174
  if (isFlowError(err)) {
128
- if (err.hints && err.hints.length > 0) {
129
- return err.hints;
130
- }
131
- return [resolveFlowErrorHint(err.code, hintContext)];
175
+ return resolveFlowErrorHints(err.code, hintContext, err.facts);
132
176
  }
133
177
  return [getDefaultErrorHint()];
134
178
  }