@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
@@ -0,0 +1,57 @@
1
+ import { createGit, errorContainsAnyPattern, MISSING_HEAD_PATTERNS, wrapGitError } from "./core.js";
2
+ const ZERO_OBJECT_ID = "0000000000000000000000000000000000000000";
3
+ async function resolveRefOrNull(cwd, ref) {
4
+ const git = createGit(cwd);
5
+ try {
6
+ return (await git.raw(["rev-parse", "--verify", ref])).trim();
7
+ }
8
+ catch (error) {
9
+ if (errorContainsAnyPattern(error, MISSING_HEAD_PATTERNS))
10
+ return null;
11
+ throw wrapGitError(`rev-parse ${ref}`, error, cwd);
12
+ }
13
+ }
14
+ /** Create an immutable recovery ref, accepting an earlier identical attempt. */
15
+ export async function createRecoveryRef(cwd, ref, objectId) {
16
+ const existing = await resolveRefOrNull(cwd, ref);
17
+ if (existing === objectId)
18
+ return;
19
+ if (existing !== null) {
20
+ throw new Error(`recovery ref ${ref} already resolves to ${existing}, not ${objectId}`);
21
+ }
22
+ const git = createGit(cwd);
23
+ try {
24
+ await git.raw(["update-ref", ref, objectId, ZERO_OBJECT_ID]);
25
+ }
26
+ catch (error) {
27
+ // Another identical attempt may have won the create race.
28
+ if (await resolveRefOrNull(cwd, ref) === objectId)
29
+ return;
30
+ throw wrapGitError(`update-ref ${ref}`, error, cwd);
31
+ }
32
+ }
33
+ /** Remove only the exact attempt ref, returning truthful non-fatal cleanup facts. */
34
+ export async function deleteRecoveryRef(cwd, ref, objectId) {
35
+ try {
36
+ const existing = await resolveRefOrNull(cwd, ref);
37
+ if (existing === null)
38
+ return { ref, status: "already-absent" };
39
+ if (existing !== objectId) {
40
+ return {
41
+ ref,
42
+ status: "retained",
43
+ diagnostic: `ref resolves to ${existing}, not expected object ${objectId}`,
44
+ };
45
+ }
46
+ const git = createGit(cwd);
47
+ await git.raw(["update-ref", "-d", ref, objectId]);
48
+ return { ref, status: "removed" };
49
+ }
50
+ catch (error) {
51
+ return {
52
+ ref,
53
+ status: "retained",
54
+ diagnostic: wrapGitError(`update-ref -d ${ref}`, error, cwd).message,
55
+ };
56
+ }
57
+ }
@@ -0,0 +1,301 @@
1
+ import { spawn } from "node:child_process";
2
+ import { normalizeFilesystemCoordinate } from "../fs/path-coordinate.js";
3
+ import { gitProcessEnv, resolveGitTimeoutMs, } from "./core.js";
4
+ /** Default long-lived batch argv. One-shot calls must not use this gate. */
5
+ export const GIT_STREAMING_BATCH_DEFAULT_ARGS = ["cat-file", "--batch"];
6
+ /** Safety cap for a single response header line before the body frame. */
7
+ export const GIT_STREAMING_BATCH_MAX_HEADER_BYTES = 64 * 1024;
8
+ /** Wire/framing failure while reading batch stdout. */
9
+ export class GitStreamingBatchFramingError extends Error {
10
+ name = "GitStreamingBatchFramingError";
11
+ }
12
+ /** Stdout ended before a complete frame arrived. */
13
+ export class GitStreamingBatchResponseEndedError extends Error {
14
+ name = "GitStreamingBatchResponseEndedError";
15
+ }
16
+ class BufferedReader {
17
+ #iterator;
18
+ #buffer = Buffer.alloc(0);
19
+ #ended = false;
20
+ constructor(stream) {
21
+ this.#iterator = stream[Symbol.asyncIterator]();
22
+ }
23
+ async #fill() {
24
+ if (this.#ended) {
25
+ throw new GitStreamingBatchResponseEndedError("git streaming batch response ended unexpectedly");
26
+ }
27
+ const next = await this.#iterator.next();
28
+ if (next.done) {
29
+ this.#ended = true;
30
+ throw new GitStreamingBatchResponseEndedError("git streaming batch response ended unexpectedly");
31
+ }
32
+ const chunk = Buffer.isBuffer(next.value) ? next.value : Buffer.from(next.value);
33
+ this.#buffer = this.#buffer.length === 0 ? chunk : Buffer.concat([this.#buffer, chunk]);
34
+ }
35
+ async readLine(maxBytes) {
36
+ for (;;) {
37
+ const newline = this.#buffer.indexOf(0x0a);
38
+ if (newline >= 0) {
39
+ if (newline > maxBytes) {
40
+ throw new GitStreamingBatchFramingError(`git streaming batch header exceeds ${maxBytes} bytes`);
41
+ }
42
+ const line = this.#buffer.subarray(0, newline).toString("ascii");
43
+ this.#buffer = this.#buffer.subarray(newline + 1);
44
+ return line;
45
+ }
46
+ if (this.#buffer.length > maxBytes) {
47
+ throw new GitStreamingBatchFramingError(`git streaming batch header exceeds ${maxBytes} bytes without a newline`);
48
+ }
49
+ try {
50
+ await this.#fill();
51
+ }
52
+ catch (error) {
53
+ if (error instanceof GitStreamingBatchResponseEndedError) {
54
+ throw new GitStreamingBatchFramingError("git streaming batch response ended before a header newline");
55
+ }
56
+ throw error;
57
+ }
58
+ }
59
+ }
60
+ async readExactly(length) {
61
+ while (this.#buffer.length < length)
62
+ await this.#fill();
63
+ const value = this.#buffer.subarray(0, length);
64
+ this.#buffer = this.#buffer.subarray(length);
65
+ return value;
66
+ }
67
+ async discardExactly(length) {
68
+ let remaining = length;
69
+ while (remaining > 0) {
70
+ if (this.#buffer.length === 0)
71
+ await this.#fill();
72
+ const consumed = Math.min(remaining, this.#buffer.length);
73
+ this.#buffer = this.#buffer.subarray(consumed);
74
+ remaining -= consumed;
75
+ }
76
+ }
77
+ }
78
+ /**
79
+ * Long-lived Git batch subprocess: raw request lines, framed response records,
80
+ * and exit/timeout/close facts only. No domain vocabulary or FlowError decisions.
81
+ */
82
+ export class GitStreamingBatchSession {
83
+ cwd;
84
+ #child;
85
+ #reader;
86
+ #stderr = [];
87
+ #timeout;
88
+ #closed;
89
+ #maxHeaderBytes;
90
+ #state = { kind: "open" };
91
+ #timedOut = false;
92
+ #aborted = false;
93
+ #inputError = null;
94
+ #requestInFlight = false;
95
+ #closePromise = null;
96
+ constructor(cwd, child, timeoutMs, maxHeaderBytes) {
97
+ this.cwd = cwd;
98
+ this.#child = child;
99
+ this.#reader = new BufferedReader(this.#child.stdout);
100
+ this.#maxHeaderBytes = maxHeaderBytes;
101
+ this.#child.stderr.on("data", (chunk) => this.#stderr.push(chunk));
102
+ this.#child.stdin.on("error", (error) => {
103
+ this.#inputError = error;
104
+ });
105
+ this.#closed = new Promise((resolve) => {
106
+ this.#child.once("error", (error) => {
107
+ resolve({ code: null, signal: null, processError: error });
108
+ });
109
+ this.#child.once("close", (code, signal) => {
110
+ resolve({ code, signal });
111
+ });
112
+ });
113
+ this.#timeout = setTimeout(() => {
114
+ this.#timedOut = true;
115
+ this.#child.kill("SIGKILL");
116
+ }, timeoutMs);
117
+ }
118
+ /**
119
+ * Open the sole long-lived Git batch process for `cwd`.
120
+ * Default argv is `cat-file --batch`; callers supply compatible batch modes only.
121
+ */
122
+ static open(cwd, options) {
123
+ const args = options?.args ?? GIT_STREAMING_BATCH_DEFAULT_ARGS;
124
+ const timeoutMs = options?.timeoutMs ?? resolveGitTimeoutMs();
125
+ const maxHeaderBytes = options?.maxHeaderBytes ?? GIT_STREAMING_BATCH_MAX_HEADER_BYTES;
126
+ const child = spawn("git", [...args], {
127
+ cwd: normalizeFilesystemCoordinate(cwd),
128
+ env: gitProcessEnv(),
129
+ shell: false,
130
+ stdio: ["pipe", "pipe", "pipe"],
131
+ });
132
+ return new GitStreamingBatchSession(cwd, child, timeoutMs, maxHeaderBytes);
133
+ }
134
+ /**
135
+ * Write one request line and read one framed response record.
136
+ * Honors stdin backpressure. Concurrent requests are rejected.
137
+ */
138
+ async request(expression, options) {
139
+ if (this.#state.kind !== "open") {
140
+ throw new Error("git streaming batch session is not open");
141
+ }
142
+ if (this.#requestInFlight) {
143
+ throw new Error("git streaming batch rejects concurrent requests");
144
+ }
145
+ this.#requestInFlight = true;
146
+ try {
147
+ if (this.#inputError)
148
+ throw this.#inputError;
149
+ const line = `${expression}\n`;
150
+ if (!this.#child.stdin.write(line)) {
151
+ await new Promise((resolve, reject) => {
152
+ const onDrain = () => {
153
+ this.#child.stdin.off("error", onError);
154
+ resolve();
155
+ };
156
+ const onError = (error) => {
157
+ this.#child.stdin.off("drain", onDrain);
158
+ reject(error);
159
+ };
160
+ this.#child.stdin.once("drain", onDrain);
161
+ this.#child.stdin.once("error", onError);
162
+ });
163
+ }
164
+ let header;
165
+ try {
166
+ header = await this.#reader.readLine(this.#maxHeaderBytes);
167
+ }
168
+ catch (error) {
169
+ if (error instanceof GitStreamingBatchFramingError
170
+ || error instanceof GitStreamingBatchResponseEndedError) {
171
+ this.abort();
172
+ }
173
+ throw error;
174
+ }
175
+ if (header === `${expression} missing`) {
176
+ return { kind: "missing", expression };
177
+ }
178
+ if (header === `${expression} ambiguous`) {
179
+ return { kind: "ambiguous", expression };
180
+ }
181
+ const parts = header.split(" ");
182
+ const [oid, type, sizeText, ...unexpected] = parts;
183
+ const size = Number(sizeText);
184
+ if (unexpected.length > 0
185
+ || !oid
186
+ || !/^[0-9a-f]{40,64}$/.test(oid)
187
+ || !type
188
+ || !Number.isSafeInteger(size)
189
+ || size < 0) {
190
+ this.abort();
191
+ throw new GitStreamingBatchFramingError(`git streaming batch returned invalid object header: ${header}`);
192
+ }
193
+ if (options?.maxContentBytes !== undefined && size > options.maxContentBytes) {
194
+ try {
195
+ await this.#reader.discardExactly(size + 1);
196
+ }
197
+ catch (error) {
198
+ this.abort();
199
+ throw error;
200
+ }
201
+ // Stream stays aligned after discard; oversized is a record limit, not a framing break.
202
+ throw new Error(`git streaming batch object ${oid} exceeds ${options.maxContentBytes} bytes`);
203
+ }
204
+ let content;
205
+ let terminator;
206
+ try {
207
+ content = await this.#reader.readExactly(size);
208
+ terminator = await this.#reader.readExactly(1);
209
+ }
210
+ catch (error) {
211
+ this.abort();
212
+ throw error;
213
+ }
214
+ if (terminator[0] !== 0x0a) {
215
+ this.abort();
216
+ throw new GitStreamingBatchFramingError(`git streaming batch object ${oid} has an invalid frame terminator`);
217
+ }
218
+ return {
219
+ kind: "object",
220
+ record: { oid, type, size, content },
221
+ };
222
+ }
223
+ finally {
224
+ this.#requestInFlight = false;
225
+ }
226
+ }
227
+ /**
228
+ * Idempotent kill of the child after a framing or stream-end failure.
229
+ * Immediately leaves `open` so no later request can write/read the destroyed child.
230
+ */
231
+ abort() {
232
+ if (this.#aborted)
233
+ return;
234
+ this.#aborted = true;
235
+ if (this.#state.kind === "open") {
236
+ this.#state = { kind: "closing" };
237
+ }
238
+ this.#child.stdin.destroy();
239
+ this.#child.kill("SIGKILL");
240
+ }
241
+ /**
242
+ * End stdin, await process exit, and return structured close facts.
243
+ * First close may reject on process spawn error after storing the closed fact;
244
+ * a later close returns that stored fact. Successful and aborted close stay idempotent.
245
+ */
246
+ close() {
247
+ if (this.#state.kind === "closed")
248
+ return Promise.resolve(this.#state.fact);
249
+ if (this.#closePromise)
250
+ return this.#closePromise;
251
+ this.#state = { kind: "closing" };
252
+ // Clear the in-flight promise after settlement so a later close() returns the
253
+ // stored fact instead of replaying a rejected process-error promise. The
254
+ // returned chain is what callers await; bookkeeping must not create a second
255
+ // unhandled rejection.
256
+ this.#closePromise = this.#closeOnce().then((fact) => {
257
+ this.#closePromise = null;
258
+ return fact;
259
+ }, (error) => {
260
+ this.#closePromise = null;
261
+ throw error;
262
+ });
263
+ return this.#closePromise;
264
+ }
265
+ async #closeOnce() {
266
+ if (!this.#aborted && this.#child.stdin?.writable) {
267
+ this.#child.stdin.end();
268
+ }
269
+ let exit;
270
+ try {
271
+ exit = await this.#closed;
272
+ }
273
+ finally {
274
+ clearTimeout(this.#timeout);
275
+ }
276
+ if (exit.processError && !this.#aborted) {
277
+ const fact = {
278
+ code: null,
279
+ signal: null,
280
+ timedOut: this.#timedOut,
281
+ aborted: this.#aborted,
282
+ stderr: Buffer.concat(this.#stderr),
283
+ };
284
+ this.#state = { kind: "closed", fact };
285
+ throw exit.processError;
286
+ }
287
+ const fact = {
288
+ code: exit.code,
289
+ signal: exit.signal,
290
+ timedOut: this.#timedOut,
291
+ aborted: this.#aborted,
292
+ stderr: Buffer.concat(this.#stderr),
293
+ };
294
+ this.#state = { kind: "closed", fact };
295
+ return fact;
296
+ }
297
+ }
298
+ /** Open the sole long-lived Git batch subprocess primitive. */
299
+ export function openGitStreamingBatch(cwd, options) {
300
+ return GitStreamingBatchSession.open(cwd, options);
301
+ }
@@ -1,3 +1,6 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { FlowError } from "../flow-error.js";
1
4
  import { createGit, wrapGitError } from "./core.js";
2
5
  export const DIRTY_FILE_CATEGORY = {
3
6
  modified: "modified",
@@ -8,6 +11,8 @@ export const DIRTY_FILE_CATEGORY = {
8
11
  copied: "copied",
9
12
  untracked: "untracked",
10
13
  };
14
+ const DIRTY_WORKTREE_LIST_LIMIT = 10;
15
+ const KEIYAKU_ARTIFACT_DIR_PREFIX = ".keiyaku/";
11
16
  function normalizeGitPath(filePath) {
12
17
  return filePath.trim().replace(/^\.\/+/, "");
13
18
  }
@@ -67,6 +72,28 @@ export async function getDirtyFiles(cwd) {
67
72
  category: deriveDirtyFileCategory(file, statusPathSets),
68
73
  }));
69
74
  }
75
+ function normalizePathForDirtyMatch(cwd, filePath) {
76
+ const normalizedPath = filePath.trim();
77
+ if (!normalizedPath)
78
+ return "";
79
+ const repoRelativePath = path.isAbsolute(normalizedPath) ? path.relative(cwd, normalizedPath) : normalizedPath;
80
+ const platformNormalizedPath = path.normalize(repoRelativePath.replace(/[\\/]+/g, path.sep));
81
+ return platformNormalizedPath.split(path.sep).join("/").replace(/^\.\/+/, "").replace(/^\/+/, "");
82
+ }
83
+ /** Refuse mutations that can overwrite tracked changes in this worktree. */
84
+ export async function assertCleanTrackedWorkingTree(cwd) {
85
+ const dirtyFiles = (await getDirtyFiles(cwd)).filter((file) => file.category !== DIRTY_FILE_CATEGORY.untracked);
86
+ if (dirtyFiles.length === 0)
87
+ return;
88
+ const shown = dirtyFiles.slice(0, DIRTY_WORKTREE_LIST_LIMIT);
89
+ const list = shown.map((file) => ` ${renderDirtyFileStatusLine(file)}`).join("\n");
90
+ const overflowCount = dirtyFiles.length - shown.length;
91
+ const overflowLine = overflowCount > 0 ? `\n ... (+${overflowCount} more)` : "";
92
+ const hasDirtyKeiyakuArtifacts = dirtyFiles.some((file) => normalizePathForDirtyMatch(cwd, file.path).startsWith(KEIYAKU_ARTIFACT_DIR_PREFIX));
93
+ throw new FlowError("DIRTY_WORKTREE", `Uncommitted changes detected in working tree:\n${list}${overflowLine}\n\nBlocking changes must be committed or stashed before proceeding.`, hasDirtyKeiyakuArtifacts
94
+ ? { facts: { kind: "dirty_worktree", hasKeiyakuArtifacts: true } }
95
+ : undefined);
96
+ }
70
97
  export async function getUntrackedFiles(cwd) {
71
98
  const git = createGit(cwd);
72
99
  let output;
@@ -82,6 +109,44 @@ export async function getUntrackedFiles(cwd) {
82
109
  .filter((line) => line.length > 0);
83
110
  return Array.from(new Set(files));
84
111
  }
112
+ /** Capture conflicted paths while the index still contains unmerged entries. */
113
+ export async function getUnmergedPaths(cwd) {
114
+ const git = createGit(cwd);
115
+ let output;
116
+ try {
117
+ output = await git.raw(["diff", "--name-only", "--diff-filter=U", "-z"]);
118
+ }
119
+ catch (err) {
120
+ throw wrapGitError("diff --name-only --diff-filter=U", err, cwd);
121
+ }
122
+ return Array.from(new Set(output.split("\0").filter((filePath) => filePath.length > 0)));
123
+ }
124
+ async function directoryExists(directory) {
125
+ try {
126
+ return (await fs.stat(directory)).isDirectory();
127
+ }
128
+ catch (error) {
129
+ if (error.code === "ENOENT")
130
+ return false;
131
+ throw error;
132
+ }
133
+ }
134
+ /** Git owns these paths; a lingering REBASE_HEAD alone is not active-rebase evidence. */
135
+ export async function hasActiveRebase(cwd) {
136
+ const git = createGit(cwd);
137
+ let paths;
138
+ try {
139
+ paths = await Promise.all([
140
+ git.raw(["rev-parse", "--git-path", "rebase-merge"]),
141
+ git.raw(["rev-parse", "--git-path", "rebase-apply"]),
142
+ ]);
143
+ }
144
+ catch (error) {
145
+ throw wrapGitError("rev-parse --git-path rebase state", error, cwd);
146
+ }
147
+ const directories = paths.map((candidate) => path.resolve(cwd, candidate.trim()));
148
+ return (await Promise.all(directories.map(directoryExists))).some(Boolean);
149
+ }
85
150
  export async function isPathTracked(cwd, filePath) {
86
151
  const git = createGit(cwd);
87
152
  let output;
package/build/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { VERSION } from "./generated/version.js";
2
+ import { VERSION, GIT_HASH } from "./generated/version.js";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { getConfig, initializeConfig } from "./config/env.js";
5
5
  import { ENV_KEYS } from "./config/env-keys.js";
6
6
  import { logErrorWithCause } from "./telemetry/logger.js";
7
- import { runProjectionGenerationRuntime } from "./core/projection-generation-runtime.js";
7
+ import { runProjectionGenerationRuntime } from "./core/projection/index.js";
8
8
  import { renderCliHelp, runCliCommand } from "./cli/index.js";
9
9
  import { installCliOutputStreamErrorPolicy } from "./cli/output-stream-error-policy.js";
10
10
  import { assertSubagentCliCommandAllowed } from "./cli/subagent-guard.js";
@@ -45,7 +45,7 @@ async function main() {
45
45
  args: cliArgs,
46
46
  });
47
47
  if (cliArgs.length === 1 && cliArgs[0] === "--version") {
48
- console.log(packageVersion);
48
+ console.log(GIT_HASH ? `${packageVersion} (${GIT_HASH})` : packageVersion);
49
49
  return;
50
50
  }
51
51
  if (cliArgs.length === 0 || (cliArgs.length === 1 && (cliArgs[0] === "--help" || cliArgs[0] === "-h"))) {
package/build/keiyaku.js CHANGED
@@ -1,10 +1,8 @@
1
1
  import * as path from "path";
2
2
  import { ACTOR_IDENTITY, SETTINGS_PATH_SEGMENTS } from "./keiyaku-constants.js";
3
3
  export { ACTOR_IDENTITY } from "./keiyaku-constants.js";
4
+ export { DRAFT_ARTIFACT_NOTICE, formatArtifactDirectories, KEIYAKU_ARTIFACT_DIRS, KEIYAKU_DRAFT_DIR, RESPONSE_ARTIFACT_NOTICE, RESPONSE_HISTORY_DIR, } from "./artifact-directories.js";
4
5
  export const KEIYAKU_FILE = path.join(".keiyaku", "KEIYAKU.md");
5
- export const KEIYAKU_DRAFT_DIR = path.join(".keiyaku", "draft");
6
- export const RESPONSE_HISTORY_DIR = path.join(".keiyaku", "response");
7
- export const KEIYAKU_ARTIFACT_DIRS = [KEIYAKU_DRAFT_DIR, RESPONSE_HISTORY_DIR];
8
6
  export const TOOL_NAMES = {
9
7
  bind: "bind",
10
8
  arc: "arc",
@@ -16,44 +14,12 @@ export const TOOL_NAMES = {
16
14
  help: "help",
17
15
  };
18
16
  export const ACTOR_IDENTITY_PLURAL = `${ACTOR_IDENTITY}s`;
19
- function formatArtifactDirectory(directory) {
20
- return `\`${directory}/\``;
21
- }
22
- export function formatArtifactDirectories(directories) {
23
- const formatted = directories.map(formatArtifactDirectory);
24
- if (formatted.length === 0)
25
- return "";
26
- if (formatted.length === 1)
27
- return formatted[0];
28
- if (formatted.length === 2)
29
- return `${formatted[0]} and ${formatted[1]}`;
30
- return `${formatted.slice(0, -1).join(", ")}, and ${formatted[formatted.length - 1]}`;
31
- }
32
- function buildArtifactNotice(directory) {
33
- return `Auto-generated artifact. Add ${formatArtifactDirectory(directory)} to \`.gitignore\` if you do not want it tracked.`;
34
- }
35
- export const DRAFT_ARTIFACT_NOTICE = buildArtifactNotice(KEIYAKU_ARTIFACT_DIRS[0]);
36
- export const RESPONSE_ARTIFACT_NOTICE = buildArtifactNotice(KEIYAKU_ARTIFACT_DIRS[1]);
37
17
  export const KEIYAKU_BRANCH_PREFIX = "keiyaku/";
38
18
  export const BASE_RULES_FILE = path.join(".keiyaku", "base-rules.md");
39
19
  export const SETTINGS_FILE = path.join(...SETTINGS_PATH_SEGMENTS);
40
- export const SUBAGENT_PROVIDERS = [
41
- "codex-sdk",
42
- "codex-app-server",
43
- "claude-agent-sdk",
44
- "opencode-sdk",
45
- "pi",
46
- ];
47
- export function isSubagentProvider(value) {
48
- return SUBAGENT_PROVIDERS.includes(value);
49
- }
20
+ export { SUBAGENT_PROVIDERS, isSubagentProvider, BUILTIN_AGENT_PROFILES, BUILTIN_AGENT_NAMES, } from "./agents/providers/registry.js";
50
21
  export const CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY = "model_reasoning_effort";
51
- export const BUILTIN_AGENT_NAMES = ["akuma-codex", "akuma-claude"];
52
22
  export const DEFAULT_AGENT_NAME = "akuma-codex";
53
- export const BUILTIN_AGENT_PROFILES = {
54
- "akuma-codex": { provider: "codex-sdk", description: "Deprecated Codex SDK Akuma; use codex-app-server." },
55
- "akuma-claude": { provider: "claude-agent-sdk", description: "Default Claude Agent SDK Akuma." },
56
- };
57
23
  export const CLAUDE_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];
58
24
  export const CLAUDE_SETTING_SOURCES = ["project", "user", "local"];
59
25
  export const CLAUDE_DEFAULT_SETTING_SOURCES = ["project"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "2.9.2",
3
+ "version": "2.9.4",
4
4
  "description": "CLI for running iterative keiyaku workflows with Codex subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,12 +24,6 @@
24
24
  },
25
25
  "scripts": {
26
26
  "sync:version": "node scripts/generate-version.mjs",
27
- "predev": "npm run sync:version --silent",
28
- "prebuild": "npm run sync:version --silent",
29
- "pretest:unit": "npm run sync:version --silent",
30
- "pretest:typecheck": "npm run sync:version --silent",
31
- "pretest:integration": "npm run sync:version --silent",
32
- "prestart": "npm run sync:version --silent",
33
27
  "preversion": "npm test --silent",
34
28
  "version": "npm run sync:version --silent && git add src/generated/version.ts",
35
29
  "dev": "npx tsx src/index.ts",
@@ -37,12 +31,13 @@
37
31
  "test:typecheck": "npx tsc --project tsconfig.tests.json",
38
32
  "prepublishOnly": "npm run build",
39
33
  "start": "node build/index.js",
40
- "test:unit": "node --import ./tests/support/real-provider-path-sentinel.mjs --import tsx --test --test-concurrency=8 tests/unit/*.test.ts",
34
+ "test:unit": "node scripts/run-unit-tests.mjs",
35
+ "test:boundaries": "node scripts/check-import-boundaries.mjs",
41
36
  "test:integration": "node --import ./tests/support/real-provider-path-sentinel.mjs --import tsx --test tests/integration/integration.test.ts",
42
37
  "pretest:cli-expect": "npm run build",
43
38
  "test:cli-expect": "node --import ./tests/support/real-provider-path-sentinel.mjs --test --test-concurrency=8 tests/cli-expect/suite/*.test.mjs",
44
39
  "test:real-providers": "KEIYAKU_REAL_PROVIDERS=1 node --import tsx --test --test-concurrency=1 tests/real-provider/matrix.test.ts",
45
- "test": "npm run test:unit && npm run test:typecheck && npm run test:integration && npm run test:cli-expect",
40
+ "test": "npm run test:unit && npm run test:typecheck && npm run test:boundaries && npm run test:integration && npm run test:cli-expect",
46
41
  "release": "npm version patch && npm publish"
47
42
  },
48
43
  "dependencies": {
@@ -50,7 +45,6 @@
50
45
  "@anthropic-ai/sdk": "^0.110.0",
51
46
  "@earendil-works/pi-coding-agent": "^0.80.10",
52
47
  "@modelcontextprotocol/sdk": "^1.29.0",
53
- "@openai/codex-sdk": "^0.113.0",
54
48
  "@opencode-ai/sdk": "1.18.3",
55
49
  "simple-git": "^3.21.0",
56
50
  "yaml": "^2.9.0",
@@ -21,8 +21,9 @@ Three kinds of objects:
21
21
  ```bash
22
22
  keiyaku task add "fix the flaky pump test" # write the idea down → k-3f9a21c4b0d2
23
23
  keiyaku bind --task k-3f9a21c4b0d2 # start it: branch + worktree
24
- cd .keiyaku/wt/<place> # work and commit here — yourself,
25
- # or: keiyaku call <akuma> "fix it"
24
+ cd .keiyaku/wt/<place> # work here yourself, or call an Akuma
25
+ keiyaku call <akuma> "fix it" # worker leaves reviewed source as a dirty tree
26
+ # host coordinator reviews, then git -C <worktree> add/commit accepted paths
26
27
  keiyaku status # is anything stuck? what's next?
27
28
  keiyaku petition <<'EOF' # done: seal + settle; the oath is required
28
29
  ## Oath
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: keiyaku-akuma
3
- description: Use when you need an Akuma to work for you — call a devil, watch it, send more instructions, kill it, or revive it from its record.
3
+ description: Use whenever a request mentions an Akuma, akuma, devil, demon, or 恶魔 and the work may involve calling, watching, telling, killing, or reviving one; covers Keiyaku Akuma profiles, projections, artifacts, lifecycle control, and rejoin operations.
4
4
  allowed-tools: Bash(keiyaku *)
5
5
  ---
6
6
 
@@ -14,7 +14,9 @@ Running devils. Contract lifecycle = `keiyaku-workflow` skill.
14
14
  - **projection** (`name/8hex`) — one live run of a devil. This is the id for `wait` / `tell` / `kill`.
15
15
  - **artifact** (`rsp_…`) — what a finished run left behind. This is the id for `revive`.
16
16
 
17
- Rule of thumb: alive → projection id; want to continue a finished/dead one → artifact id.
17
+ Rule of thumb: continue the same conversation with its projection id. Use an
18
+ artifact id with `revive` only when deliberately starting a new projection;
19
+ failed/dead state alone is not a reason to revive.
18
20
 
19
21
  ## Common usage
20
22
 
@@ -26,8 +28,7 @@ keiyaku call NAME --detach "do the thing" # fire and forget; note the projec
26
28
  keiyaku call NAME - < prompt.md # long prompt: literal `-` reads stdin
27
29
  keiyaku wait <proj-id> # join a running one; prints its activity
28
30
  keiyaku wait <proj-id> --timeout 5m # peek for 5m; timeout = snapshot, not death
29
- keiyaku tell <proj-id> "also do X" # drop instructions in its mailbox
30
- keiyaku tell <proj-id> --wait 5m "also do X" # tell, then watch the response
31
+ keiyaku tell <proj-id> [--wait DURATION] <BODY|-> # optionally tell, then bounded-join
31
32
  keiyaku revive rsp_XXXX "continue..." # new run continuing from a record
32
33
  keiyaku kill <proj-id> # stop it; record survives, revive still works
33
34
  keiyaku status # forgot an id? it's on the board
@@ -35,14 +36,17 @@ keiyaku status # forgot an id? it's on the board
35
36
 
36
37
  ## Expectations
37
38
 
38
- - `tell` is silent on success: exit 0, zero output. Silence **is** the receipt — do not re-send. Effects show up in `wait`/`status`.
39
+ - Omit tell's bracketed `--wait DURATION` option for silent success: exit 0,
40
+ zero output. Supply it to perform the ordinary bounded join after acceptance.
41
+ Silence **is** the receipt — do not re-send. Effects show up in `wait`/`status`.
39
42
  - Body is required for `call`/`tell`. Piping stdin without a literal `-` does nothing.
40
43
  - A `--wait`/`--timeout` expiry prints a live snapshot and exits 0 — the run continues; rejoin with `wait` anytime.
41
44
  - `wait` is repeatable: on a finished run it shows the final transcript again.
42
- - `--contract ADDR` attaches a call to a contract (by place, slug, or full id); `--bare` skips attachment explicitly. `call` only — `revive` takes neither.
45
+ - Only `--contract ADDR` attaches a new call to a contract (by place, slug, or full id). Without it, call is standalone; `--bare` remains an explicit synonym. `call` only — `revive` takes neither and follows artifact provenance.
43
46
 
44
47
  ## Occasional flags
45
48
 
46
49
  - `--incognito` — no artifact written, no revive possible (call)
47
50
  - `--effort LEVEL` — provider effort override; needs a model set (call, revive, tell)
48
51
  - `-C DIR` — run relative to another directory (all akuma commands)
52
+ - `--repo DIR` — you almost never need this: the repo is inferred from where you stand (`-C` included). Only for the rare case where the contract ledger lives in a *different* repo. Never pass it the same value as `-C`.
@@ -24,7 +24,7 @@ open ⇄ in_progress → done | drop
24
24
  keiyaku task add "fix the flaky pump test" # body: pipe stdin for details
25
25
  keiyaku task add "title" --pri 1 --needs k-aaa # priority + dependency at birth
26
26
  keiyaku task ls # all open, priority order
27
- keiyaku task view k-xxxx # full detail incl. needs/blocks
27
+ keiyaku task show k-xxxx # full detail and parent-children tree
28
28
  keiyaku task start k-xxxx # mark in_progress (start ≠ execute)
29
29
  keiyaku task stop k-xxxx # back to open
30
30
  keiyaku task done k-xxxx