@astrosheep/keiyaku 2.9.2 → 2.9.4

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 (238) hide show
  1. package/build/.tsbuildinfo +1 -1
  2. package/build/agents/call-terms.js +13 -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 +25 -222
  6. package/build/agents/harness/index.js +3 -1
  7. package/build/agents/harness/outcome.js +47 -4
  8. package/build/agents/harness/projection.js +42 -59
  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 +50 -53
  21. package/build/agents/providers/codex-app-server/events.js +67 -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/registration.js +145 -0
  36. package/build/agents/providers/pi/session-schema.js +6 -0
  37. package/build/agents/providers/pi/terms.js +1 -0
  38. package/build/agents/providers/registration.js +7 -0
  39. package/build/agents/providers/registry.js +76 -0
  40. package/build/agents/selector.js +2 -29
  41. package/build/agents/types.js +8 -0
  42. package/build/artifact-directories.js +22 -0
  43. package/build/cli/commands/akuma/akuma/handler.js +1 -1
  44. package/build/cli/commands/akuma/view/handler.js +5 -4
  45. package/build/cli/commands/akuma.js +6 -0
  46. package/build/cli/commands/contract/amend/handler.js +8 -2
  47. package/build/cli/commands/contract/arc/handler.js +7 -1
  48. package/build/cli/commands/contract/bind/handler.js +4 -3
  49. package/build/cli/commands/contract/bind/meta.js +34 -7
  50. package/build/cli/commands/contract/forfeit/handler.js +4 -4
  51. package/build/cli/commands/contract/log/handler.js +3 -3
  52. package/build/cli/commands/contract/petition/handler.js +4 -6
  53. package/build/cli/commands/contract/renew/handler.js +20 -6
  54. package/build/cli/commands/contract/renew/meta.js +5 -4
  55. package/build/cli/commands/metadata.js +58 -0
  56. package/build/cli/commands/projection/call/handler.js +6 -5
  57. package/build/cli/commands/projection/kill/handler.js +16 -32
  58. package/build/cli/commands/projection/shared.js +28 -0
  59. package/build/cli/commands/projection/status/handler.js +1 -1
  60. package/build/cli/commands/projection/tell/handler.js +14 -30
  61. package/build/cli/commands/projection/wait/handler.js +45 -22
  62. package/build/cli/commands/projection/wait/meta.js +7 -2
  63. package/build/cli/commands/registry.js +2 -54
  64. package/build/cli/commands/shared.js +10 -7
  65. package/build/cli/commands/skills/install/handler.js +2 -5
  66. package/build/cli/commands/skills/skills/handler.js +1 -1
  67. package/build/cli/commands/task/add/handler.js +5 -13
  68. package/build/cli/commands/task/catalog.js +2 -2
  69. package/build/cli/commands/task/done/handler.js +4 -8
  70. package/build/cli/commands/task/drop/handler.js +3 -6
  71. package/build/cli/commands/task/ls/handler.js +3 -5
  72. package/build/cli/commands/task/shared.js +14 -4
  73. package/build/cli/commands/task/{view → show}/handler.js +7 -8
  74. package/build/cli/commands/task/show/meta.js +8 -0
  75. package/build/cli/commands/task/start/handler.js +3 -6
  76. package/build/cli/commands/task/stop/handler.js +4 -8
  77. package/build/cli/commands/task/task/handler.js +1 -1
  78. package/build/cli/commands/task/update/handler.js +4 -8
  79. package/build/cli/completion.js +1 -1
  80. package/build/cli/flags.js +19 -3
  81. package/build/cli/help.js +4 -11
  82. package/build/cli/index.js +57 -83
  83. package/build/cli/missing-subcommand.js +7 -0
  84. package/build/cli/parse-flags.js +14 -5
  85. package/build/cli/parse-metadata.js +3 -3
  86. package/build/cli/parse-selectors.js +1 -1
  87. package/build/cli/parse.js +21 -0
  88. package/build/cli/projection-address.js +7 -12
  89. package/build/cli/render/address.js +41 -37
  90. package/build/cli/render/arc.js +24 -12
  91. package/build/cli/render/call.js +11 -5
  92. package/build/cli/render/errors.js +36 -1
  93. package/build/cli/render/format.js +4 -0
  94. package/build/cli/render/kanshi.js +6 -12
  95. package/build/cli/render/kill.js +9 -0
  96. package/build/cli/render/misc.js +55 -12
  97. package/build/cli/render/petition.js +20 -15
  98. package/build/cli/render/projection-activity.js +3 -0
  99. package/build/cli/render/shared.js +60 -21
  100. package/build/cli/render/skills.js +10 -0
  101. package/build/cli/render/status.js +37 -14
  102. package/build/cli/render/success-response.js +77 -0
  103. package/build/cli/render/task.js +54 -14
  104. package/build/cli/render/tell.js +5 -0
  105. package/build/cli/render/terminal-failure.js +58 -0
  106. package/build/cli/render/types.js +1 -0
  107. package/build/cli/render/wait.js +38 -36
  108. package/build/cli/types.js +1 -1
  109. package/build/config/akuma-loader.js +0 -5
  110. package/build/config/env-keys.js +1 -1
  111. package/build/config/provider-policy-ownership.js +3 -32
  112. package/build/config/settings/disease.js +8 -1
  113. package/build/config/settings/loader.js +11 -3
  114. package/build/config/settings/provider-instance-fields.js +51 -0
  115. package/build/config/settings/schema.js +6 -176
  116. package/build/core/addressing.js +66 -16
  117. package/build/core/amend.js +135 -18
  118. package/build/core/arc.js +19 -67
  119. package/build/core/bind.js +82 -35
  120. package/build/core/call/call.js +15 -9
  121. package/build/core/call/context.js +6 -51
  122. package/build/core/call/execution.js +21 -22
  123. package/build/core/call/prompt.js +14 -1
  124. package/build/core/command-markdown.js +45 -0
  125. package/build/core/context.js +13 -7
  126. package/build/core/contract-view.js +20 -3
  127. package/build/core/draft.js +123 -16
  128. package/build/core/forfeit.js +5 -2
  129. package/build/core/hints.js +1 -75
  130. package/build/core/identifier-slug.js +66 -0
  131. package/build/core/ids.js +25 -11
  132. package/build/core/ledger.js +20 -68
  133. package/build/core/markdown/parser.js +0 -68
  134. package/build/core/markdown/render.js +68 -0
  135. package/build/core/markdown/sections.js +2 -1
  136. package/build/core/outcome-base.js +4 -0
  137. package/build/core/projection/generation/database.js +2 -38
  138. package/build/core/projection/generation/model.js +42 -1
  139. package/build/core/{projection-generation-continuation.js → projection/generation/projection-generation-continuation.js} +3 -3
  140. package/build/core/{projection-generation-execution.js → projection/generation/projection-generation-execution.js} +19 -49
  141. package/build/core/{projection-generation-identity.js → projection/generation/projection-generation-identity.js} +2 -2
  142. package/build/core/{projection-generation-launcher.js → projection/generation/projection-generation-launcher.js} +5 -3
  143. package/build/core/{projection-generation-process.js → projection/generation/projection-generation-process.js} +2 -1
  144. package/build/core/{projection-generation-runner.js → projection/generation/projection-generation-runner.js} +18 -3
  145. package/build/core/projection/generation/store.js +2 -2
  146. package/build/core/projection/generation/transitions.js +1 -1
  147. package/build/core/projection/index.js +27 -0
  148. package/build/core/{projection-activity.js → projection/projection-activity.js} +12 -0
  149. package/build/core/{projection-alias.js → projection/projection-alias.js} +14 -19
  150. package/build/core/projection/projection-coordinate.js +61 -0
  151. package/build/core/{projection-core.js → projection/projection-core.js} +9 -9
  152. package/build/core/{projection-execution-observer.js → projection/projection-execution-observer.js} +2 -57
  153. package/build/core/{projection-identity.js → projection/projection-identity.js} +1 -1
  154. package/build/core/{projection-kill.js → projection/projection-kill.js} +1 -1
  155. package/build/core/{projection-life-observer.js → projection/projection-life-observer.js} +1 -1
  156. package/build/core/{projection-mint.js → projection/projection-mint.js} +10 -10
  157. package/build/core/{projection-status.js → projection/projection-status.js} +98 -25
  158. package/build/core/projection/projection-terminal-failure.js +104 -0
  159. package/build/core/projection/projection-wait.js +278 -0
  160. package/build/core/projection/projection-wake.js +203 -0
  161. package/build/core/provider-loader-ports.js +4 -0
  162. package/build/core/ref-log.js +65 -40
  163. package/build/core/registry.js +63 -10
  164. package/build/core/renew-session.js +128 -0
  165. package/build/core/renew.js +426 -99
  166. package/build/core/scope.js +143 -66
  167. package/build/core/seal.js +139 -35
  168. package/build/core/{claim-delivery.js → settlement/claim-delivery.js} +9 -14
  169. package/build/core/{claim.js → settlement/claim.js} +32 -8
  170. package/build/core/settlement/index.js +19 -0
  171. package/build/core/{petition-claim-gates.js → settlement/petition-claim-gates.js} +22 -18
  172. package/build/core/{petition-forfeit.js → settlement/petition-forfeit.js} +2 -2
  173. package/build/core/{petition-head-guard.js → settlement/petition-head-guard.js} +2 -2
  174. package/build/core/settlement/petition-io.js +1 -0
  175. package/build/core/{petition-preview.js → settlement/petition-preview.js} +1 -1
  176. package/build/core/{petition.js → settlement/petition.js} +12 -10
  177. package/build/core/settlement/queue-read-model.js +143 -0
  178. package/build/core/settlement/queue-seat-allocation.js +156 -0
  179. package/build/core/settlement/queue.js +6 -0
  180. package/build/core/settlement/settlement.js +295 -0
  181. package/build/core/{verdict.js → settlement/verdict.js} +9 -37
  182. package/build/core/status/board.js +24 -23
  183. package/build/core/status/ledger-batch.js +158 -0
  184. package/build/core/status/reconciliation.js +10 -4
  185. package/build/core/stored-agent-event.js +3 -0
  186. package/build/core/task/board.js +40 -4
  187. package/build/core/task/commands.js +30 -21
  188. package/build/core/task/index.js +12 -0
  189. package/build/core/task/settlement-git.js +157 -65
  190. package/build/core/task/settlement-policy.js +54 -24
  191. package/build/core/task/task-contract.js +370 -0
  192. package/build/core/{task-doctor.js → task/task-doctor.js} +1 -1
  193. package/build/core/{task-git-actor.js → task/task-git-actor.js} +5 -10
  194. package/build/core/{task-git-runtime.js → task/task-git-runtime.js} +3 -3
  195. package/build/core/{task-git-store.js → task/task-git-store.js} +92 -29
  196. package/build/core/task/task-repository.js +1 -0
  197. package/build/core/{task-store-repository.js → task/task-store-repository.js} +5 -4
  198. package/build/core/task/task-store.js +1 -0
  199. package/build/core/{task-worktree.js → task/task-worktree.js} +2 -1
  200. package/build/core/task/task.js +5 -0
  201. package/build/core/task/tree.js +37 -0
  202. package/build/core/task/validation.js +5 -2
  203. package/build/core/transcripts.js +15 -3
  204. package/build/core/worktree-path.js +43 -28
  205. package/build/flow-error.js +85 -39
  206. package/build/fs/durability.js +27 -0
  207. package/build/fs/path-coordinate.js +16 -0
  208. package/build/generated/version.js +3 -2
  209. package/build/git/branches.js +2 -1
  210. package/build/git/core.js +106 -2
  211. package/build/git/path-coordinate.js +17 -0
  212. package/build/git/refs.js +57 -0
  213. package/build/git/streaming-batch.js +301 -0
  214. package/build/git/worktree.js +65 -0
  215. package/build/index.js +3 -3
  216. package/build/keiyaku.js +2 -36
  217. package/package.json +4 -10
  218. package/skills/keiyaku/SKILL.md +3 -2
  219. package/skills/keiyaku-akuma/SKILL.md +10 -6
  220. package/skills/keiyaku-task/SKILL.md +1 -1
  221. package/skills/keiyaku-workflow/SKILL.md +25 -4
  222. package/build/agents/providers/codex-sdk.js +0 -329
  223. package/build/cli/commands/task/view/meta.js +0 -8
  224. package/build/core/command-io.js +0 -174
  225. package/build/core/path-coordinate.js +0 -27
  226. package/build/core/petition-claim.js +0 -117
  227. package/build/core/petition-run.js +0 -83
  228. package/build/core/projection-coordinate.js +0 -63
  229. package/build/core/projection-wait.js +0 -165
  230. package/build/core/projection-wake.js +0 -136
  231. package/build/core/queue.js +0 -343
  232. package/build/core/task-contract.js +0 -237
  233. package/build/core/task.js +0 -4
  234. /package/build/{core/task-repository.js → agents/launch-snapshot/types.js} +0 -0
  235. /package/build/{core/task-store.js → agents/providers/claude-agent-sdk/terms.js} +0 -0
  236. /package/build/core/{projection-generation-runtime.js → projection/generation/projection-generation-runtime.js} +0 -0
  237. /package/build/core/{projection-life-protocol.js → projection/projection-life-protocol.js} +0 -0
  238. /package/build/core/{projection-runner-lock.js → projection/projection-runner-lock.js} +0 -0
@@ -1,19 +1,13 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import * as fs from "node:fs";
3
2
  import * as path from "node:path";
4
3
  import { FlowError } from "../../flow-error.js";
5
- import { parseTaskDocument } from "../task.js";
6
- function gitResult(cwd, args, input, env) {
7
- return spawnSync("git", [...args], {
8
- cwd,
9
- input,
10
- encoding: "utf8",
11
- env: { ...process.env, ...env, GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" },
12
- stdio: ["pipe", "pipe", "pipe"],
13
- });
4
+ import { runGitProcess } from "../../git/core.js";
5
+ import { parseTaskDocument } from "./task.js";
6
+ async function gitResult(cwd, args, input, env) {
7
+ return await runGitProcess(cwd, args, { input, env });
14
8
  }
15
- function git(cwd, args, input, env) {
16
- const result = gitResult(cwd, args, input, env);
9
+ async function git(cwd, args, input, env) {
10
+ const result = await gitResult(cwd, args, input, env);
17
11
  if (result.error) {
18
12
  throw new FlowError("INTERNAL_STATE", `git ${args[0]} failed: ${result.error.message}`, { cause: result.error });
19
13
  }
@@ -29,36 +23,118 @@ function boardPath(root, source) {
29
23
  }
30
24
  return relative;
31
25
  }
32
- export function inspectCurrentBranch(cwd) {
33
- const branch = gitResult(cwd, ["symbolic-ref", "--quiet", "HEAD"]);
26
+ export async function inspectCurrentBranch(cwd) {
27
+ const branch = await gitResult(cwd, ["symbolic-ref", "--quiet", "HEAD"]);
34
28
  if (branch.error) {
35
29
  throw new FlowError("INTERNAL_STATE", `cannot inspect HEAD: ${branch.error.message}`, { cause: branch.error });
36
30
  }
37
31
  if (branch.status !== 0 || branch.stdout.trim() === "") {
38
32
  throw new FlowError("DETACHED_HEAD", "cannot publish task board from detached HEAD");
39
33
  }
40
- return { ref: branch.stdout.trim(), head: git(cwd, ["rev-parse", "HEAD"]).trim() };
34
+ return { ref: branch.stdout.trim(), head: (await git(cwd, ["rev-parse", "HEAD"])).trim() };
41
35
  }
42
- export function createTaskBoardCommit(input) {
36
+ export async function createTaskBoardCommit(input) {
43
37
  const temporaryDirectory = fs.mkdtempSync(path.join(input.gitDir, "keiyaku-task-index-"));
44
38
  const indexPath = path.join(temporaryDirectory, "index");
45
39
  const env = { GIT_INDEX_FILE: indexPath };
46
40
  try {
47
- git(input.cwd, ["read-tree", input.parent], undefined, env);
48
- git(input.cwd, ["rm", "-r", "--cached", "-f", "--ignore-unmatch", "--", ".keiyaku/tasks"], undefined, env);
41
+ await git(input.cwd, ["read-tree", input.parent], undefined, env);
49
42
  for (const source of input.sources) {
50
- const oid = git(input.cwd, ["hash-object", "-w", "--stdin"], source.text).trim();
51
- git(input.cwd, ["update-index", "--add", "--cacheinfo", `100644,${oid},${boardPath(input.root, source)}`], undefined, env);
43
+ const oid = (await git(input.cwd, ["hash-object", "-w", "--stdin"], source.text)).trim();
44
+ await git(input.cwd, ["update-index", "--add", "--cacheinfo", `100644,${oid},${boardPath(input.root, source)}`], undefined, env);
45
+ }
46
+ const tree = (await git(input.cwd, ["write-tree"], undefined, env)).trim();
47
+ return (await git(input.cwd, ["commit-tree", tree, "-p", input.parent, "-m", input.subject])).trim();
48
+ }
49
+ finally {
50
+ fs.rmSync(temporaryDirectory, { recursive: true, force: true });
51
+ }
52
+ }
53
+ function taskMutationPath(write) {
54
+ return `.keiyaku/tasks/${write.id}.md`;
55
+ }
56
+ /** Build a commit that updates only the listed task paths on top of parent. */
57
+ export async function createTaskMutationCommit(input) {
58
+ if (input.writes.length === 0) {
59
+ throw new FlowError("INTERNAL_STATE", "task mutation commit requires at least one write");
60
+ }
61
+ const temporaryDirectory = fs.mkdtempSync(path.join(input.gitDir, "keiyaku-task-index-"));
62
+ const env = { GIT_INDEX_FILE: path.join(temporaryDirectory, "index") };
63
+ try {
64
+ await git(input.cwd, ["read-tree", input.parent], undefined, env);
65
+ for (const write of input.writes) {
66
+ const oid = (await git(input.cwd, ["hash-object", "-w", "--stdin"], write.text)).trim();
67
+ await git(input.cwd, [
68
+ "update-index",
69
+ "--add",
70
+ "--cacheinfo",
71
+ `100644,${oid},${taskMutationPath(write)}`,
72
+ ], undefined, env);
52
73
  }
53
- const tree = git(input.cwd, ["write-tree"], undefined, env).trim();
54
- return git(input.cwd, ["commit-tree", tree, "-p", input.parent, "-m", input.subject]).trim();
74
+ const tree = (await git(input.cwd, ["write-tree"], undefined, env)).trim();
75
+ const parentTree = (await git(input.cwd, ["rev-parse", `${input.parent}^{tree}`])).trim();
76
+ if (tree === parentTree)
77
+ return input.parent;
78
+ return (await git(input.cwd, ["commit-tree", tree, "-p", input.parent, "-m", input.subject])).trim();
55
79
  }
56
80
  finally {
57
81
  fs.rmSync(temporaryDirectory, { recursive: true, force: true });
58
82
  }
59
83
  }
60
- function taskTreeEntries(cwd, commit) {
61
- return git(cwd, ["ls-tree", "-r", "-z", commit, "--", ".keiyaku/tasks"])
84
+ /** Stage only the mutated task paths into the real index; leave unrelated entries alone. */
85
+ export async function reconcileTaskMutationIndex(cwd, writes) {
86
+ for (const write of writes) {
87
+ const oid = (await git(cwd, ["hash-object", "-w", "--stdin"], write.text)).trim();
88
+ await git(cwd, [
89
+ "update-index",
90
+ "--add",
91
+ "--cacheinfo",
92
+ `100644,${oid},${taskMutationPath(write)}`,
93
+ ]);
94
+ }
95
+ }
96
+ /**
97
+ * Atomically publish a pathspec-scoped task micro-commit under the hub branch.
98
+ * Unrelated staged and unstaged non-task bytes are never read into the commit.
99
+ */
100
+ export async function publishTaskMutationCommit(input, deps = {}) {
101
+ let lastError;
102
+ for (let attempt = 0; attempt < 4; attempt += 1) {
103
+ const branch = await inspectCurrentBranch(input.cwd);
104
+ const commit = await createTaskMutationCommit({
105
+ cwd: input.cwd,
106
+ gitDir: input.gitDir,
107
+ parent: branch.head,
108
+ writes: input.writes,
109
+ subject: input.subject,
110
+ });
111
+ if (commit === branch.head) {
112
+ await reconcileTaskMutationIndex(input.cwd, input.writes);
113
+ return commit;
114
+ }
115
+ try {
116
+ await updateBranch({
117
+ cwd: input.cwd,
118
+ ref: branch.ref,
119
+ next: commit,
120
+ expected: branch.head,
121
+ }, deps);
122
+ try {
123
+ await reconcileTaskMutationIndex(input.cwd, input.writes);
124
+ }
125
+ catch {
126
+ // Branch fact remains authoritative; index may be repaired by a later mutation.
127
+ }
128
+ return commit;
129
+ }
130
+ catch (error) {
131
+ lastError = error;
132
+ }
133
+ }
134
+ throw lastError;
135
+ }
136
+ async function taskTreeEntries(cwd, commit) {
137
+ return (await git(cwd, ["ls-tree", "-r", "-z", commit, "--", ".keiyaku/tasks"]))
62
138
  .split("\0")
63
139
  .filter(Boolean)
64
140
  .map((record) => {
@@ -70,28 +146,35 @@ function taskTreeEntries(cwd, commit) {
70
146
  return { mode, oid, path: record.slice(tab + 1) };
71
147
  });
72
148
  }
73
- export function reconcileTaskIndex(cwd, commit) {
74
- const existing = git(cwd, ["ls-files", "-z", "--", ".keiyaku/tasks"])
75
- .split("\0")
76
- .filter(Boolean);
77
- for (const file of existing)
78
- git(cwd, ["update-index", "--force-remove", "--", file]);
79
- for (const entry of taskTreeEntries(cwd, commit)) {
80
- git(cwd, ["update-index", "--add", "--cacheinfo", `${entry.mode},${entry.oid},${entry.path}`]);
149
+ /** Reconcile only settlement-owned task paths; never rewrite an unrelated index entry. */
150
+ export async function reconcileTaskPathsetIndex(cwd, commit, paths) {
151
+ const entries = new Map((await taskTreeEntries(cwd, commit)).map((entry) => [entry.path, entry]));
152
+ for (const taskPath of paths) {
153
+ const entry = entries.get(taskPath);
154
+ if (entry) {
155
+ await git(cwd, ["update-index", "--add", "--cacheinfo", `${entry.mode},${entry.oid},${entry.path}`]);
156
+ }
157
+ else {
158
+ await git(cwd, ["update-index", "--force-remove", "--", taskPath]);
159
+ }
81
160
  }
82
161
  }
83
- export function refHead(cwd, ref) {
84
- return git(cwd, ["rev-parse", ref]).trim();
162
+ /** Task paths tracked by one authoritative commit, independent of the worktree index. */
163
+ export async function trackedTaskPaths(cwd, commit) {
164
+ return (await taskTreeEntries(cwd, commit)).map((entry) => entry.path).sort();
165
+ }
166
+ export async function refHead(cwd, ref) {
167
+ return (await git(cwd, ["rev-parse", ref])).trim();
85
168
  }
86
- export function readableRefHead(cwd, ref) {
87
- const result = gitResult(cwd, ["rev-parse", ref]);
169
+ export async function readableRefHead(cwd, ref) {
170
+ const result = await gitResult(cwd, ["rev-parse", ref]);
88
171
  if (result.status !== 0)
89
172
  return undefined;
90
173
  return result.stdout.trim();
91
174
  }
92
- export function taskContractAtCommit(cwd, commit, id) {
175
+ export async function taskContractAtCommit(cwd, commit, id) {
93
176
  const sourcePath = `.keiyaku/tasks/${id}.md`;
94
- const result = gitResult(cwd, ["show", `${commit}:${sourcePath}`]);
177
+ const result = await gitResult(cwd, ["show", `${commit}:${sourcePath}`]);
95
178
  if (result.status !== 0)
96
179
  return undefined;
97
180
  return parseTaskDocument({ path: sourcePath, text: result.stdout }).contractId;
@@ -100,17 +183,17 @@ export async function updateBranch(input, deps) {
100
183
  if (deps.updateBranch)
101
184
  await deps.updateBranch(input);
102
185
  else
103
- git(input.cwd, ["update-ref", input.ref, input.next, input.expected]);
186
+ await git(input.cwd, ["update-ref", input.ref, input.next, input.expected]);
104
187
  }
105
188
  export async function publishForwardTaskBoardCommit(input, deps) {
106
189
  let lastError;
107
190
  for (let attempt = 0; attempt < 4; attempt += 1) {
108
- const parent = refHead(input.cwd, input.ref);
109
- const commit = createTaskBoardCommit({ ...input, parent });
191
+ const parent = await refHead(input.cwd, input.ref);
192
+ const commit = await createTaskBoardCommit({ ...input, parent });
110
193
  try {
111
194
  await updateBranch({ cwd: input.cwd, ref: input.ref, next: commit, expected: parent }, deps);
112
195
  try {
113
- reconcileTaskIndex(input.cwd, commit);
196
+ await reconcileTaskPathsetIndex(input.cwd, commit, input.paths);
114
197
  }
115
198
  catch { /* the forward branch fact remains authoritative */ }
116
199
  return commit;
@@ -121,32 +204,32 @@ export async function publishForwardTaskBoardCommit(input, deps) {
121
204
  }
122
205
  throw lastError;
123
206
  }
124
- function treeWithoutTasks(cwd, commit, gitDir) {
207
+ async function treeWithoutTasks(cwd, commit, gitDir) {
125
208
  const temporaryDirectory = fs.mkdtempSync(path.join(gitDir, "keiyaku-landing-index-"));
126
209
  const env = { GIT_INDEX_FILE: path.join(temporaryDirectory, "index") };
127
210
  try {
128
- git(cwd, ["read-tree", commit], undefined, env);
129
- git(cwd, ["rm", "-r", "--cached", "-f", "--ignore-unmatch", "--", ".keiyaku/tasks"], undefined, env);
130
- return git(cwd, ["write-tree"], undefined, env).trim();
211
+ await git(cwd, ["read-tree", commit], undefined, env);
212
+ await git(cwd, ["rm", "-r", "--cached", "-f", "--ignore-unmatch", "--", ".keiyaku/tasks"], undefined, env);
213
+ return (await git(cwd, ["write-tree"], undefined, env)).trim();
131
214
  }
132
215
  finally {
133
216
  fs.rmSync(temporaryDirectory, { recursive: true, force: true });
134
217
  }
135
218
  }
136
- function composeLandingTree(input) {
137
- const baseTree = treeWithoutTasks(input.cwd, input.base, input.gitDir);
138
- const targetTree = treeWithoutTasks(input.cwd, input.target, input.gitDir);
139
- const deliveryTree = treeWithoutTasks(input.cwd, input.delivery, input.gitDir);
219
+ async function composeLandingTree(input) {
220
+ const baseTree = await treeWithoutTasks(input.cwd, input.base, input.gitDir);
221
+ const targetTree = await treeWithoutTasks(input.cwd, input.target, input.gitDir);
222
+ const deliveryTree = await treeWithoutTasks(input.cwd, input.delivery, input.gitDir);
140
223
  let mergedTree;
141
224
  try {
142
- mergedTree = git(input.cwd, [
225
+ mergedTree = (await git(input.cwd, [
143
226
  "merge-tree",
144
227
  "--write-tree",
145
228
  "--merge-base",
146
229
  baseTree,
147
230
  targetTree,
148
231
  deliveryTree,
149
- ]).trim();
232
+ ])).trim();
150
233
  }
151
234
  catch (error) {
152
235
  throw new FlowError("CLOSE_MERGE_CONFLICT", "claim merge conflict outside task board", { cause: error });
@@ -154,26 +237,35 @@ function composeLandingTree(input) {
154
237
  const temporaryDirectory = fs.mkdtempSync(path.join(input.gitDir, "keiyaku-landing-index-"));
155
238
  const env = { GIT_INDEX_FILE: path.join(temporaryDirectory, "index") };
156
239
  try {
157
- git(input.cwd, ["read-tree", mergedTree], undefined, env);
158
- git(input.cwd, ["rm", "-r", "--cached", "-f", "--ignore-unmatch", "--", ".keiyaku/tasks"], undefined, env);
240
+ await git(input.cwd, ["read-tree", mergedTree], undefined, env);
241
+ // merge-tree deliberately excluded task paths. Restore the target board as
242
+ // the inherited baseline, then overlay only settlement-authorized writes.
243
+ for (const entry of await taskTreeEntries(input.cwd, input.target)) {
244
+ await git(input.cwd, [
245
+ "update-index",
246
+ "--add",
247
+ "--cacheinfo",
248
+ `${entry.mode},${entry.oid},${entry.path}`,
249
+ ], undefined, env);
250
+ }
159
251
  for (const source of input.sources) {
160
- const oid = git(input.cwd, ["hash-object", "-w", "--stdin"], source.text).trim();
161
- git(input.cwd, [
252
+ const oid = (await git(input.cwd, ["hash-object", "-w", "--stdin"], source.text)).trim();
253
+ await git(input.cwd, [
162
254
  "update-index",
163
255
  "--add",
164
256
  "--cacheinfo",
165
257
  `100644,${oid},${boardPath(input.root, source)}`,
166
258
  ], undefined, env);
167
259
  }
168
- return git(input.cwd, ["write-tree"], undefined, env).trim();
260
+ return (await git(input.cwd, ["write-tree"], undefined, env)).trim();
169
261
  }
170
262
  finally {
171
263
  fs.rmSync(temporaryDirectory, { recursive: true, force: true });
172
264
  }
173
265
  }
174
- export function createLandingCommit(input) {
175
- const tree = composeLandingTree(input);
176
- return git(input.cwd, [
266
+ export async function createLandingCommit(input) {
267
+ const tree = await composeLandingTree(input);
268
+ return (await git(input.cwd, [
177
269
  "commit-tree",
178
270
  tree,
179
271
  "-p",
@@ -184,13 +276,13 @@ export function createLandingCommit(input) {
184
276
  `keiyaku: claim ${input.contractId}`,
185
277
  "-m",
186
278
  `Merge contract ${input.contractId} into ${input.targetBranch}.`,
187
- ]).trim();
279
+ ])).trim();
188
280
  }
189
- export function containsCommit(cwd, ancestor, descendant) {
190
- return gitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).status === 0;
281
+ export async function containsCommit(cwd, ancestor, descendant) {
282
+ return (await gitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant])).status === 0;
191
283
  }
192
- export function updateTargetRef(cwd, ref, next, expected) {
193
- git(cwd, ["update-ref", ref, next, expected]);
284
+ export async function updateTargetRef(cwd, ref, next, expected) {
285
+ await git(cwd, ["update-ref", ref, next, expected]);
194
286
  }
195
287
  export function sameTaskSourceBytes(root, left, right) {
196
288
  const snapshot = (sources) => new Map(sources.map((source) => [boardPath(root, source), source.text]));
@@ -31,15 +31,12 @@ export function reopenedTaskDocument(task) {
31
31
  delete document.startedAt;
32
32
  return document;
33
33
  }
34
- export function selectReadyTaskForBind(board, taskId) {
35
- requireHealthySettlementBoard(board);
36
- const id = resolveTaskReadId(board, taskId);
37
- const task = board.byId.get(id);
34
+ function validateReadyForBind(board, task) {
38
35
  if (task.state === "in_progress") {
39
- throw new FlowError("INVALID_TARGET", `task ${id} is in_progress; stop it before bind`);
36
+ throw new FlowError("INVALID_TARGET", `task ${task.id} is in_progress; stop it before bind`);
40
37
  }
41
38
  if (task.state !== "open") {
42
- throw new FlowError("INVALID_TARGET", `task ${id} is ${task.state}, not ready for bind`);
39
+ throw new FlowError("INVALID_TARGET", `task ${task.id} is ${task.state}, not ready for bind`);
43
40
  }
44
41
  const unsatisfied = task.needs
45
42
  .filter((need) => {
@@ -48,22 +45,64 @@ export function selectReadyTaskForBind(board, taskId) {
48
45
  })
49
46
  .sort();
50
47
  if (unsatisfied.length > 0) {
51
- throw new FlowError("INVALID_TARGET", `task ${id} is blocked by: ${unsatisfied.join(", ")}`);
48
+ throw new FlowError("INVALID_TARGET", `task ${task.id} is blocked by: ${unsatisfied.join(", ")}`);
52
49
  }
53
50
  if (task.contractId !== undefined) {
54
- throw new FlowError("INVALID_TARGET", `task ${id} is already bound to contract ${task.contractId}`);
51
+ throw new FlowError("INVALID_TARGET", `task ${task.id} is already bound to contract ${task.contractId}`);
55
52
  }
56
- return task;
57
53
  }
58
- export function taskForContract(board, contractId) {
54
+ export function selectReadyTasksForBind(board, taskIds) {
59
55
  requireHealthySettlementBoard(board);
60
- const matches = board.tasks
56
+ const rootIds = taskIds.map((taskId) => resolveTaskReadId(board, taskId));
57
+ const duplicates = rootIds.filter((id, index) => rootIds.indexOf(id) !== index);
58
+ if (duplicates.length > 0) {
59
+ throw new FlowError("INVALID_TARGET", `task ${[...new Set(duplicates)].sort().join(", ")} selected more than once`);
60
+ }
61
+ const admitted = new Set();
62
+ const tasks = [];
63
+ for (const rootId of rootIds) {
64
+ const root = board.byId.get(rootId);
65
+ if (!admitted.has(root.id)) {
66
+ admitted.add(root.id);
67
+ tasks.push(root);
68
+ }
69
+ for (const candidate of board.tasks) {
70
+ if (candidate.parent === null || admitted.has(candidate.id))
71
+ continue;
72
+ if (candidate.state === "done" || candidate.state === "drop")
73
+ continue;
74
+ if (isDescendantOf(board, candidate, rootId)) {
75
+ admitted.add(candidate.id);
76
+ tasks.push(candidate);
77
+ }
78
+ }
79
+ }
80
+ for (const task of tasks)
81
+ validateReadyForBind(board, task);
82
+ return tasks;
83
+ }
84
+ function isDescendantOf(board, task, ancestorId) {
85
+ let current = task.parent;
86
+ while (current !== null) {
87
+ if (current === ancestorId)
88
+ return true;
89
+ current = board.byId.get(current)?.parent ?? null;
90
+ }
91
+ return false;
92
+ }
93
+ export function tasksForContract(board, contractId) {
94
+ requireHealthySettlementBoard(board);
95
+ return board.tasks
61
96
  .filter((task) => task.contractId === contractId)
62
97
  .sort((left, right) => left.id.localeCompare(right.id));
63
- if (matches.length > 1) {
64
- throw new FlowError("INTERNAL_STATE", `contract ${contractId} is referenced by multiple tasks: ${matches.map((task) => task.id).join(", ")}`);
65
- }
66
- return matches[0];
98
+ }
99
+ export function doneTaskReplacements(board, contractId) {
100
+ return tasksForContract(board, contractId).map((task) => {
101
+ const settled = settlementDocument(task, undefined);
102
+ settled.state = "done";
103
+ delete settled.startedAt;
104
+ return settled;
105
+ });
67
106
  }
68
107
  export function requireSettledTask(board, id) {
69
108
  const task = requireHealthySettlementBoard(board).byId.get(id);
@@ -71,12 +110,3 @@ export function requireSettledTask(board, id) {
71
110
  throw new FlowError("INTERNAL_STATE", `task ${id} disappeared from the board`);
72
111
  return task;
73
112
  }
74
- export function doneTaskReplacement(board, contractId) {
75
- const bound = taskForContract(board, contractId);
76
- if (!bound)
77
- return undefined;
78
- const settled = settlementDocument(bound, undefined);
79
- settled.state = "done";
80
- delete settled.startedAt;
81
- return settled;
82
- }