@astrosheep/keiyaku 2.8.5 → 2.9.1

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/README.md +35 -15
  2. package/build/.tsbuildinfo +1 -1
  3. package/build/agents/{call-terms_v2.js → call-terms.js} +12 -8
  4. package/build/agents/harness/control-types.js +1 -0
  5. package/build/agents/harness/execution-handle.js +23 -3
  6. package/build/agents/harness/index.js +6 -607
  7. package/build/agents/harness/outcome.js +141 -0
  8. package/build/agents/harness/projection.js +310 -0
  9. package/build/agents/harness/router.js +201 -0
  10. package/build/agents/harness/runtime.js +2 -1
  11. package/build/agents/{launch-snapshot_v2.js → launch-snapshot/model.js} +69 -74
  12. package/build/agents/launch-snapshot/resolve.js +72 -0
  13. package/build/agents/opencode-sdk.js +24 -3
  14. package/build/agents/providers/{claude-agent-sdk.js → claude-agent-sdk/adapter.js} +46 -368
  15. package/build/agents/providers/claude-agent-sdk/events.js +218 -0
  16. package/build/agents/providers/claude-agent-sdk/session.js +158 -0
  17. package/build/agents/providers/codex-app-server/adapter.js +347 -0
  18. package/build/agents/providers/codex-app-server/events.js +352 -0
  19. package/build/agents/providers/codex-app-server/session.js +266 -0
  20. package/build/agents/providers/codex-sdk.js +23 -5
  21. package/build/agents/providers/opencode-sdk/adapter.js +344 -0
  22. package/build/agents/providers/opencode-sdk/events.js +354 -0
  23. package/build/agents/providers/opencode-sdk/session.js +180 -0
  24. package/build/agents/providers/pi/adapter.js +269 -0
  25. package/build/agents/providers/pi/checkpoint.js +41 -0
  26. package/build/agents/providers/pi/events.js +282 -0
  27. package/build/agents/{selector_v2.js → selector.js} +8 -6
  28. package/build/cli/commands/akuma/akuma/handler.js +4 -0
  29. package/build/cli/commands/akuma/akuma/meta.js +16 -0
  30. package/build/cli/commands/akuma/catalog.js +8 -0
  31. package/build/cli/commands/akuma/list/handler.js +15 -0
  32. package/build/cli/commands/akuma/list/meta-ls.js +9 -0
  33. package/build/cli/commands/akuma/view/handler.js +34 -0
  34. package/build/cli/commands/akuma/view/meta.js +8 -0
  35. package/build/cli/commands/akuma.js +1 -1
  36. package/build/cli/commands/contract/amend/handler.js +10 -0
  37. package/build/cli/commands/contract/amend/meta.js +12 -0
  38. package/build/cli/commands/contract/arc/handler.js +9 -0
  39. package/build/cli/commands/contract/arc/meta.js +26 -0
  40. package/build/cli/commands/contract/bind/handler.js +23 -0
  41. package/build/cli/commands/contract/bind/meta.js +30 -0
  42. package/build/cli/commands/contract/catalog.js +16 -0
  43. package/build/cli/commands/contract/forfeit/handler.js +13 -0
  44. package/build/cli/commands/contract/forfeit/meta.js +12 -0
  45. package/build/cli/commands/contract/log/handler.js +12 -0
  46. package/build/cli/commands/contract/log/meta.js +12 -0
  47. package/build/cli/commands/contract/petition/handler.js +26 -0
  48. package/build/cli/commands/contract/petition/meta.js +23 -0
  49. package/build/cli/commands/contract/renew/handler.js +10 -0
  50. package/build/cli/commands/contract/renew/meta.js +17 -0
  51. package/build/cli/commands/projection/call/handler.js +42 -0
  52. package/build/cli/commands/projection/call/meta.js +9 -0
  53. package/build/cli/commands/projection/catalog.js +14 -0
  54. package/build/cli/commands/projection/kill/handler.js +37 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +56 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +25 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +36 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +23 -0
  63. package/build/cli/commands/projection/wait/meta.js +11 -0
  64. package/build/cli/commands/registry.js +100 -0
  65. package/build/cli/commands/shared.js +71 -0
  66. package/build/cli/commands/skills/catalog.js +6 -0
  67. package/build/cli/commands/skills/install/handler.js +10 -0
  68. package/build/cli/commands/skills/install/meta.js +9 -0
  69. package/build/cli/commands/skills/skills/handler.js +4 -0
  70. package/build/cli/commands/skills/skills/meta.js +10 -0
  71. package/build/cli/commands/system/catalog.js +8 -0
  72. package/build/cli/commands/system/completion/handler.js +10 -0
  73. package/build/cli/commands/system/completion/meta.js +9 -0
  74. package/build/cli/commands/system/dump-env/handler.js +3 -0
  75. package/build/cli/commands/system/dump-env/meta.js +10 -0
  76. package/build/cli/commands/system/guide/handler.js +4 -0
  77. package/build/cli/commands/system/guide/meta.js +9 -0
  78. package/build/cli/commands/task/add/handler.js +21 -0
  79. package/build/cli/commands/task/add/meta.js +8 -0
  80. package/build/cli/commands/task/catalog.js +22 -0
  81. package/build/cli/commands/task/doctor/handler.js +4 -0
  82. package/build/cli/commands/task/doctor/meta.js +8 -0
  83. package/build/cli/commands/task/done/handler.js +17 -0
  84. package/build/cli/commands/task/done/meta.js +8 -0
  85. package/build/cli/commands/task/drop/handler.js +6 -0
  86. package/build/cli/commands/task/drop/meta.js +8 -0
  87. package/build/cli/commands/task/ls/handler.js +13 -0
  88. package/build/cli/commands/task/ls/meta.js +8 -0
  89. package/build/cli/commands/task/shared.js +131 -0
  90. package/build/cli/commands/task/start/handler.js +6 -0
  91. package/build/cli/commands/task/start/meta.js +8 -0
  92. package/build/cli/commands/task/stop/handler.js +17 -0
  93. package/build/cli/commands/task/stop/meta.js +8 -0
  94. package/build/cli/commands/task/task/handler.js +5 -0
  95. package/build/cli/commands/task/task/meta.js +11 -0
  96. package/build/cli/commands/task/update/handler.js +18 -0
  97. package/build/cli/commands/task/update/meta.js +8 -0
  98. package/build/cli/commands/task/view/handler.js +14 -0
  99. package/build/cli/commands/task/view/meta.js +8 -0
  100. package/build/cli/completion.js +26 -27
  101. package/build/cli/flags.js +79 -2
  102. package/build/cli/help.js +27 -49
  103. package/build/cli/index.js +121 -374
  104. package/build/cli/output-stream-error-policy.js +17 -0
  105. package/build/cli/parse-flags.js +197 -0
  106. package/build/cli/parse-metadata.js +129 -0
  107. package/build/cli/parse-selectors.js +65 -0
  108. package/build/cli/parse.js +46 -313
  109. package/build/cli/projection-address.js +5 -0
  110. package/build/cli/render/arc.js +1 -1
  111. package/build/cli/render/errors.js +4 -3
  112. package/build/cli/render/format.js +14 -0
  113. package/build/cli/render/kanshi.js +106 -0
  114. package/build/cli/render/petition.js +24 -1
  115. package/build/cli/render/projection-activity.js +5 -1
  116. package/build/cli/render/shared.js +2 -2
  117. package/build/cli/render/status.js +79 -13
  118. package/build/cli/render/task.js +113 -0
  119. package/build/cli/render/wait.js +23 -3
  120. package/build/cli/types.js +1 -1
  121. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +7 -4
  122. package/build/config/env.js +9 -4
  123. package/build/config/settings/disease.js +26 -0
  124. package/build/config/settings/knobs.js +8 -0
  125. package/build/config/settings/loader.js +244 -0
  126. package/build/config/settings/schema.js +276 -0
  127. package/build/core/addressing.js +1 -1
  128. package/build/core/amend.js +5 -8
  129. package/build/core/arc.js +7 -8
  130. package/build/core/bind-workspace.js +123 -0
  131. package/build/core/bind.js +75 -155
  132. package/build/core/call/call.js +229 -0
  133. package/build/core/call/context.js +251 -0
  134. package/build/core/call/execution.js +217 -0
  135. package/build/core/call/prompt.js +42 -0
  136. package/build/core/call/reference.js +24 -0
  137. package/build/core/call-persist.js +10 -3
  138. package/build/core/claim-delivery.js +247 -0
  139. package/build/core/claim.js +39 -277
  140. package/build/core/context.js +2 -1
  141. package/build/core/{render.js → contract-view.js} +12 -140
  142. package/build/core/draft.js +107 -9
  143. package/build/core/entry.js +9 -1
  144. package/build/core/execution-coordinate.js +4 -20
  145. package/build/core/execution-pact.js +16 -31
  146. package/build/core/forfeit.js +35 -22
  147. package/build/core/hints.js +2 -2
  148. package/build/core/log.js +2 -2
  149. package/build/core/path-coordinate.js +27 -0
  150. package/build/core/petition-claim-gates.js +8 -6
  151. package/build/core/petition-preview.js +97 -0
  152. package/build/core/petition-run.js +5 -7
  153. package/build/core/petition.js +39 -26
  154. package/build/core/projection/generation/database.js +306 -0
  155. package/build/core/projection/generation/identity.js +84 -0
  156. package/build/core/projection/generation/model.js +201 -0
  157. package/build/core/projection/generation/store.js +73 -0
  158. package/build/core/projection/generation/transitions.js +258 -0
  159. package/build/core/projection/heart.js +0 -307
  160. package/build/core/projection/tell/database.js +109 -0
  161. package/build/core/projection/tell/model.js +97 -0
  162. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  163. package/build/core/projection-activity.js +23 -2
  164. package/build/core/projection-coordinate.js +14 -2
  165. package/build/core/projection-core.js +2 -1
  166. package/build/core/projection-execution-observer.js +259 -0
  167. package/build/core/projection-generation-continuation.js +1 -1
  168. package/build/core/projection-generation-execution.js +90 -20
  169. package/build/core/projection-generation-launcher.js +1 -1
  170. package/build/core/projection-generation-runner.js +198 -4
  171. package/build/core/projection-identity.js +1 -1
  172. package/build/core/projection-kill.js +50 -17
  173. package/build/core/projection-life-observer.js +1 -1
  174. package/build/core/projection-life-protocol.js +10 -0
  175. package/build/core/projection-mint.js +1 -1
  176. package/build/core/projection-status.js +45 -32
  177. package/build/core/projection-wait.js +12 -2
  178. package/build/core/projection-wake.js +5 -2
  179. package/build/core/{queue_v2.js → queue.js} +2 -1
  180. package/build/core/registry.js +1 -1
  181. package/build/core/renew.js +9 -11
  182. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  183. package/build/core/response-artifact-catalog.js +7 -0
  184. package/build/core/seal.js +44 -16
  185. package/build/core/status/board.js +289 -0
  186. package/build/core/status/drift.js +99 -0
  187. package/build/core/status/lifecycle.js +84 -0
  188. package/build/core/status/reconciliation.js +131 -0
  189. package/build/core/stored-agent-event.js +28 -4
  190. package/build/core/target-ref.js +10 -0
  191. package/build/core/task/board.js +175 -0
  192. package/build/core/task/commands.js +211 -0
  193. package/build/core/task/document.js +189 -0
  194. package/build/core/task/model.js +1 -0
  195. package/build/core/task/settlement-git.js +207 -0
  196. package/build/core/task/settlement-policy.js +82 -0
  197. package/build/core/task/source-board.js +84 -0
  198. package/build/core/task/source-model.js +1 -0
  199. package/build/core/task/validation.js +44 -0
  200. package/build/core/task-contract.js +237 -0
  201. package/build/core/task-doctor.js +14 -0
  202. package/build/core/task-git-actor.js +38 -0
  203. package/build/core/task-git-runtime.js +37 -0
  204. package/build/core/task-git-store.js +385 -0
  205. package/build/core/task-repository.js +1 -0
  206. package/build/core/task-store-repository.js +86 -0
  207. package/build/core/task-store.js +1 -0
  208. package/build/core/task-worktree.js +51 -0
  209. package/build/core/task.js +4 -0
  210. package/build/core/transcripts.js +138 -56
  211. package/build/core/validation.js +17 -0
  212. package/build/core/verdict.js +2 -2
  213. package/build/core/worktree-path.js +33 -8
  214. package/build/flow-error.js +5 -2
  215. package/build/generated/version.js +1 -1
  216. package/build/git/branches.js +33 -9
  217. package/build/git/commits.js +3 -0
  218. package/build/git/core.js +9 -0
  219. package/build/git/diff/parsers.js +0 -94
  220. package/build/index.js +2 -0
  221. package/build/keiyaku-constants.js +2 -0
  222. package/build/keiyaku.js +4 -3
  223. package/package.json +3 -2
  224. package/skills/keiyaku/SKILL.md +30 -13
  225. package/skills/keiyaku-akuma/SKILL.md +30 -57
  226. package/skills/keiyaku-task/SKILL.md +51 -0
  227. package/skills/keiyaku-workflow/SKILL.md +71 -0
  228. package/build/agents/harness/pump.js +0 -158
  229. package/build/agents/providers/codex-app-server.js +0 -864
  230. package/build/agents/providers/opencode-sdk.js +0 -363
  231. package/build/agents/providers/pi.js +0 -557
  232. package/build/config/settings.js +0 -533
  233. package/build/core/call.js +0 -686
  234. package/build/core/projection/generation.js +0 -412
  235. package/build/core/projection/identity.js +0 -81
  236. package/build/core/projection/mint.js +0 -174
  237. package/build/core/projection-generation-store.js +0 -707
  238. package/build/core/status.js +0 -539
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
- import { parseResolvedAkumaLaunchSnapshot, } from "../agents/launch-snapshot_v2.js";
4
+ import { parseResolvedAkumaLaunchSnapshot, } from "../agents/launch-snapshot/model.js";
5
5
  import { resolvePersistedSubagentSession } from "../agents/session.js";
6
6
  import { isErrnoException } from "../errno.js";
7
7
  import { FlowError, requireText } from "../flow-error.js";
@@ -13,6 +13,8 @@ import { parseExecutionCoordinates } from "./execution-coordinate.js";
13
13
  import { createUlid } from "./ids.js";
14
14
  import { parseToAST } from "./markdown/parser.js";
15
15
  import { isResponseArtifactId, RESPONSE_ARTIFACT_ID_RE } from "./response-artifact-id.js";
16
+ import { getResponseArtifactCatalog, } from "./response-artifact-catalog.js";
17
+ import { assertExactFields, assertNonBlankString, isPlainRecord, } from "./validation.js";
16
18
  export { isResponseArtifactId, RESPONSE_ARTIFACT_ID_RE } from "./response-artifact-id.js";
17
19
  const RESPONSE_HISTORY_SLUG_MAX_LENGTH = 24;
18
20
  const RESPONSE_HISTORY_SESSION_KEY = "session";
@@ -30,43 +32,30 @@ const RESUMABLE_RESPONSE_HEADINGS = Object.values(RESPONSE_ARTIFACT_HEADINGS);
30
32
  function artifactError(locator, field, reason) {
31
33
  return new FlowError("INVALID_RESPONSE_PATH", `Invalid response artifact '${locator}': field '${field}' ${reason}.`);
32
34
  }
33
- function isRecord(value) {
34
- return value !== null && typeof value === "object" && !Array.isArray(value);
35
- }
36
- function assertExactFields(value, required, locator, prefix) {
37
- for (const field of required) {
38
- if (!(field in value))
39
- throw artifactError(locator, `${prefix}.${field}`, "is missing");
40
- }
41
- const allowed = new Set(required);
42
- const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
43
- if (unknown !== undefined)
44
- throw artifactError(locator, `${prefix}.${unknown}`, "is unknown");
45
- }
46
- function nonBlank(value, locator, field) {
47
- if (typeof value !== "string" || value.trim() === "")
48
- throw artifactError(locator, field, "must be a non-blank string");
35
+ function artifactInvalid(locator) {
36
+ return (field, reason) => artifactError(locator, field, reason);
49
37
  }
50
38
  /** The sole durable provenance parser. */
51
39
  export function parseResponseArtifactProvenance(value, locator) {
52
- if (!isRecord(value))
40
+ const invalid = artifactInvalid(locator);
41
+ if (!isPlainRecord(value))
53
42
  throw artifactError(locator, "provenance", "must be an object");
54
- assertExactFields(value, ["artifactId", "authority", "binding", "evidence"], locator, "provenance");
55
- nonBlank(value.artifactId, locator, "provenance.artifactId");
43
+ assertExactFields(value, ["artifactId", "authority", "binding", "evidence"], [], "provenance", invalid);
44
+ assertNonBlankString(value.artifactId, "provenance.artifactId", invalid);
56
45
  if (!RESPONSE_ARTIFACT_ID_RE.test(value.artifactId)) {
57
46
  throw artifactError(locator, "provenance.artifactId", "must be rsp_ followed by a ULID");
58
47
  }
59
48
  const { authority, binding } = parseExecutionCoordinates(value.authority, value.binding, (field, reason) => artifactError(locator, `provenance.${field}`, reason));
60
- if (!isRecord(value.evidence))
49
+ if (!isPlainRecord(value.evidence))
61
50
  throw artifactError(locator, "provenance.evidence", "must be an object");
62
51
  if (authority.kind === "repository") {
63
- assertExactFields(value.evidence, ["branch", "commit"], locator, "provenance.evidence");
64
- nonBlank(value.evidence.branch, locator, "provenance.evidence.branch");
65
- nonBlank(value.evidence.commit, locator, "provenance.evidence.commit");
52
+ assertExactFields(value.evidence, ["branch", "commit"], [], "provenance.evidence", invalid);
53
+ assertNonBlankString(value.evidence.branch, "provenance.evidence.branch", invalid);
54
+ assertNonBlankString(value.evidence.commit, "provenance.evidence.commit", invalid);
66
55
  return { artifactId: value.artifactId, authority, binding, evidence: { branch: value.evidence.branch, commit: value.evidence.commit } };
67
56
  }
68
- assertExactFields(value.evidence, ["workdir"], locator, "provenance.evidence");
69
- nonBlank(value.evidence.workdir, locator, "provenance.evidence.workdir");
57
+ assertExactFields(value.evidence, ["workdir"], [], "provenance.evidence", invalid);
58
+ assertNonBlankString(value.evidence.workdir, "provenance.evidence.workdir", invalid);
70
59
  if (!path.isAbsolute(value.evidence.workdir)) {
71
60
  throw artifactError(locator, "provenance.evidence.workdir", "must be an absolute path");
72
61
  }
@@ -130,6 +119,15 @@ export async function persistResponseHistory(input) {
130
119
  const responsePath = path.join(historyDir, `${fileStamp}_${input.tool.toUpperCase()}_${slug(input.title ?? "", input.tool)}.md`);
131
120
  await fs.mkdir(historyDir, { recursive: true });
132
121
  await fs.writeFile(responsePath, buildMarkdown(input, iso, provenance), "utf8");
122
+ try {
123
+ getResponseArtifactCatalog()?.register({
124
+ id: artifactId,
125
+ stableRoot: await fs.realpath(input.cwd),
126
+ });
127
+ }
128
+ catch {
129
+ // The catalog is a disposable read model; the persisted artifact remains truth.
130
+ }
133
131
  return { artifactId, responsePath: path.relative(input.cwd, responsePath) };
134
132
  }
135
133
  function detectTool(document) {
@@ -206,43 +204,30 @@ export async function readResponseArtifact(input) {
206
204
  }
207
205
  }
208
206
  const subagentName = frontmatter.subagent_name?.trim() || undefined;
209
- const storageRoot = path.resolve(path.dirname(absolutePath), "..", "..");
207
+ const storageRoot = await fs.realpath(path.resolve(path.dirname(absolutePath), "..", ".."));
210
208
  return { artifactId: provenance.artifactId, responsePath: path.relative(input.cwd, absolutePath), absolutePath, storageRoot, provenance, tool, ...(session ? { session } : {}), ...(launchSnapshot ? { launchSnapshot } : {}), ...(subagentName ? { subagentName } : {}) };
211
209
  }
212
- export async function locateResponseArtifact(input) {
213
- const carry = (error) => {
214
- throw attachAddressCarrier(error, input.attempt);
215
- };
216
- let identity;
217
- try {
218
- identity = requireText("artifactId", input.artifactId);
219
- }
220
- catch (error) {
221
- return carry(error);
222
- }
223
- if (!isResponseArtifactId(identity)) {
224
- return carry(new FlowError("INVALID_RESPONSE_PATH", `${identity} is not a response artifact handle; revive expects an artifact id`));
225
- }
226
- const historyDir = path.join(input.cwd, RESPONSE_HISTORY_DIR);
210
+ async function findResponseArtifactAtRoot(input) {
211
+ const physicalHistoryDir = path.join(input.stableRoot, RESPONSE_HISTORY_DIR);
212
+ const historyDir = path.join(input.displayRoot ?? input.stableRoot, RESPONSE_HISTORY_DIR);
227
213
  let names;
228
214
  try {
229
- names = await fs.readdir(historyDir);
215
+ names = await fs.readdir(physicalHistoryDir);
230
216
  }
231
217
  catch (error) {
232
- if (isErrnoException(error) && error.code === "ENOENT") {
233
- return carry(new FlowError("INVALID_RESPONSE_PATH", `Response artifact '${identity}' was not found in ${historyDir}.`));
234
- }
235
- return carry(error);
218
+ if (isErrnoException(error) && error.code === "ENOENT")
219
+ return { kind: "missing" };
220
+ return input.carry(error);
236
221
  }
237
222
  let match;
238
223
  for (const name of names.filter((candidate) => candidate.endsWith(".md")).sort()) {
239
224
  const responsePath = path.join(RESPONSE_HISTORY_DIR, name);
240
225
  let document;
241
226
  try {
242
- document = await fs.readFile(path.join(input.cwd, responsePath), "utf8");
227
+ document = await fs.readFile(path.join(input.stableRoot, responsePath), "utf8");
243
228
  }
244
229
  catch (error) {
245
- return carry(error);
230
+ return input.carry(error);
246
231
  }
247
232
  const encoded = parseToAST(document).frontmatter?.entries?.[RESPONSE_HISTORY_PROVENANCE_KEY];
248
233
  if (encoded === undefined)
@@ -254,26 +239,123 @@ export async function locateResponseArtifact(input) {
254
239
  catch {
255
240
  continue;
256
241
  }
257
- if (!isRecord(indexed) || indexed.artifactId !== identity)
242
+ if (!isPlainRecord(indexed) || indexed.artifactId !== input.artifactId)
258
243
  continue;
259
244
  let parsed;
260
245
  try {
261
- parsed = await readResponseArtifact({ cwd: input.cwd, responsePath });
246
+ parsed = await readResponseArtifact({ cwd: input.stableRoot, responsePath });
262
247
  }
263
248
  catch (error) {
264
- return carry(error);
249
+ return input.carry(error);
265
250
  }
266
251
  if (!parsed)
267
252
  continue;
268
253
  if (match) {
269
- return carry(new FlowError("INVALID_RESPONSE_PATH", `Response artifact identity '${identity}' is duplicated in ${historyDir}.`));
254
+ return input.carry(new FlowError("INVALID_RESPONSE_PATH", `Response artifact identity '${input.artifactId}' is duplicated in ${historyDir}.`));
270
255
  }
271
256
  match = parsed;
272
257
  }
273
- if (!match) {
274
- return carry(new FlowError("INVALID_RESPONSE_PATH", `Response artifact '${identity}' was not found in ${historyDir}.`));
258
+ return match ? { kind: "found", artifact: match } : { kind: "missing" };
259
+ }
260
+ function observeCatalogArtifact(catalog, artifact) {
261
+ try {
262
+ catalog?.touch({ id: artifact.artifactId, stableRoot: artifact.storageRoot });
263
+ }
264
+ catch {
265
+ // Artifact observation remains authoritative when the disposable catalog is unavailable.
266
+ }
267
+ }
268
+ export async function reconcileResponseArtifactCatalog(stableRoot) {
269
+ const catalog = getResponseArtifactCatalog();
270
+ if (!catalog)
271
+ return;
272
+ const physicalRoot = await fs.realpath(stableRoot);
273
+ const historyDir = path.join(physicalRoot, RESPONSE_HISTORY_DIR);
274
+ let names;
275
+ try {
276
+ names = await fs.readdir(historyDir);
277
+ }
278
+ catch (error) {
279
+ if (isErrnoException(error) && error.code === "ENOENT")
280
+ return;
281
+ throw error;
275
282
  }
276
- return match;
283
+ for (const name of names.filter((candidate) => candidate.endsWith(".md")).sort()) {
284
+ try {
285
+ const artifact = await readResponseArtifact({
286
+ cwd: physicalRoot,
287
+ responsePath: path.join(RESPONSE_HISTORY_DIR, name),
288
+ });
289
+ if (artifact)
290
+ catalog.register({ id: artifact.artifactId, stableRoot: artifact.storageRoot });
291
+ }
292
+ catch {
293
+ // Reconciliation observes valid local artifacts only; it never repairs artifact truth.
294
+ }
295
+ }
296
+ }
297
+ export async function locateResponseArtifact(input) {
298
+ const carry = (error) => {
299
+ throw attachAddressCarrier(error, input.attempt);
300
+ };
301
+ let identity;
302
+ try {
303
+ identity = requireText("artifactId", input.artifactId);
304
+ }
305
+ catch (error) {
306
+ return carry(error);
307
+ }
308
+ if (!isResponseArtifactId(identity)) {
309
+ return carry(new FlowError("INVALID_RESPONSE_PATH", `${identity} is not a response artifact handle; revive expects an artifact id`));
310
+ }
311
+ let physicalLocalRoot;
312
+ try {
313
+ physicalLocalRoot = await fs.realpath(input.cwd);
314
+ }
315
+ catch (error) {
316
+ if (!isErrnoException(error) || error.code !== "ENOENT")
317
+ return carry(error);
318
+ physicalLocalRoot = path.resolve(input.cwd);
319
+ }
320
+ const local = await findResponseArtifactAtRoot({
321
+ stableRoot: physicalLocalRoot,
322
+ displayRoot: input.cwd,
323
+ artifactId: identity,
324
+ carry,
325
+ });
326
+ const catalog = getResponseArtifactCatalog();
327
+ if (local.kind === "found") {
328
+ observeCatalogArtifact(catalog, local.artifact);
329
+ return local.artifact;
330
+ }
331
+ let cataloged;
332
+ try {
333
+ cataloged = catalog?.lookup(identity);
334
+ }
335
+ catch {
336
+ cataloged = undefined;
337
+ }
338
+ if (cataloged) {
339
+ const catalogedRoot = cataloged.stableRoot;
340
+ const remote = await findResponseArtifactAtRoot({
341
+ stableRoot: catalogedRoot,
342
+ artifactId: identity,
343
+ carry,
344
+ });
345
+ if (remote.kind === "found") {
346
+ observeCatalogArtifact(catalog, remote.artifact);
347
+ return remote.artifact;
348
+ }
349
+ try {
350
+ catalog?.removeDead({ id: identity, stableRoot: catalogedRoot });
351
+ }
352
+ catch {
353
+ // Dead-row cleanup is best-effort; the physical lookup result owns the error.
354
+ }
355
+ return carry(new FlowError("INVALID_RESPONSE_PATH", `${identity} not found at cataloged root ${catalogedRoot}`));
356
+ }
357
+ const historyDir = path.join(input.cwd, RESPONSE_HISTORY_DIR);
358
+ return carry(new FlowError("INVALID_RESPONSE_PATH", `Response artifact '${identity}' was not found in ${historyDir}.`));
277
359
  }
278
360
  export function requireResumableArtifact(artifact) {
279
361
  if (!artifact.session)
@@ -0,0 +1,17 @@
1
+ export function isPlainRecord(value) {
2
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3
+ }
4
+ export function assertExactFields(value, required, optional, prefix, invalid) {
5
+ for (const field of required) {
6
+ if (!(field in value))
7
+ throw invalid(prefix ? `${prefix}.${field}` : field, "is missing");
8
+ }
9
+ const allowed = new Set([...required, ...optional]);
10
+ const unknown = Object.keys(value).filter((field) => !allowed.has(field)).sort()[0];
11
+ if (unknown !== undefined)
12
+ throw invalid(prefix ? `${prefix}.${unknown}` : unknown, "is unknown");
13
+ }
14
+ export function assertNonBlankString(value, field, invalid) {
15
+ if (typeof value !== "string" || value.trim() === "")
16
+ throw invalid(field, "must be a non-blank string");
17
+ }
@@ -1,8 +1,8 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { claimContract } from "./claim.js";
3
3
  import { activeLedgerRef, appendWithRetry, readLedger } from "./ledger.js";
4
- import { assertClaimQueueHead, queueMembership, readQueueReadModel } from "./queue_v2.js";
5
- import { deriveContractState } from "./status.js";
4
+ import { assertClaimQueueHead, queueMembership, readQueueReadModel } from "./queue.js";
5
+ import { deriveContractState } from "./status/lifecycle.js";
6
6
  function latestVerdict(entries) {
7
7
  for (let index = entries.length - 1; index >= 0; index -= 1) {
8
8
  const entry = entries[index];
@@ -4,7 +4,8 @@ import { FlowError } from "../flow-error.js";
4
4
  import { createGit, wrapGitError } from "../git/core.js";
5
5
  import { readLedger } from "./ledger.js";
6
6
  import { listContractIds } from "./ledger.js";
7
- import { deriveContractState, isTerminalState } from "./status.js";
7
+ import { deriveContractState, isTerminalState } from "./status/lifecycle.js";
8
+ import { gitCommonRootPath, resolveGitCommonRoot } from "./path-coordinate.js";
8
9
  export function findBindEntry(entries) {
9
10
  return entries.find((entry) => entry.kind === "bind");
10
11
  }
@@ -58,10 +59,16 @@ export async function stableRepoRoot(cwd) {
58
59
  catch (error) {
59
60
  throw wrapGitError("rev-parse --path-format=absolute --git-common-dir", error, cwd);
60
61
  }
61
- if (!path.isAbsolute(commonDir)) {
62
+ const stableRoot = gitCommonRootPath(commonDir);
63
+ if (!path.isAbsolute(stableRoot)) {
62
64
  throw new FlowError("INTERNAL_STATE", `git common directory is not absolute: ${commonDir}`);
63
65
  }
64
- return path.dirname(commonDir);
66
+ try {
67
+ return await resolveGitCommonRoot(commonDir);
68
+ }
69
+ catch (error) {
70
+ throw new FlowError("INTERNAL_STATE", `cannot resolve git common directory ${stableRoot}`, { cause: error });
71
+ }
65
72
  }
66
73
  export async function contractWorktreePathFromBind(cwd, contractId, bind) {
67
74
  return path.join(await stableRepoRoot(cwd), ".keiyaku", "wt", bind?.data.place ?? contractId);
@@ -94,11 +101,11 @@ export async function findContractWorktreePath(cwd, contractId) {
94
101
  return (await findContractWorktree(cwd, contractId)).path;
95
102
  }
96
103
  /**
97
- * Resolve the active commission presented by an engine-owned contract
98
- * worktree containing cwd. Ordinary repository directories and arbitrary
99
- * worktrees never present a commission address.
104
+ * Resolve the contract id for an engine-owned bound worktree containing cwd.
105
+ * Matches the canonical bind place path and worktree workspace only; ledger
106
+ * terminal state is optional so residual worktrees after cleanup still bind.
100
107
  */
101
- export async function contractPresentedByWorktree(cwd) {
108
+ async function contractIdForEngineOwnedWorktree(cwd, options) {
102
109
  const stableRoot = await stableRepoRoot(cwd);
103
110
  const canonicalCwd = await fs.realpath(cwd);
104
111
  for (const worktree of await listWorktrees(stableRoot)) {
@@ -107,7 +114,9 @@ export async function contractPresentedByWorktree(cwd) {
107
114
  continue;
108
115
  const contractId = worktree.branch.slice(branchPrefix.length);
109
116
  const ledger = await readLedger(stableRoot, contractId);
110
- if (!ledger || isTerminalState(deriveContractState(ledger.entries)))
117
+ if (!ledger)
118
+ continue;
119
+ if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
111
120
  continue;
112
121
  const bind = findBindEntry(ledger.entries);
113
122
  if (!bind || bind.data.workspace !== "worktree")
@@ -128,6 +137,22 @@ export async function contractPresentedByWorktree(cwd) {
128
137
  }
129
138
  return undefined;
130
139
  }
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
+ /**
149
+ * Resolve the contract bound by an engine-owned worktree containing cwd,
150
+ * including when the ledger is already terminal. Task CLI contract context
151
+ * uses this residual binding; call/addressing keep active-only presentation.
152
+ */
153
+ export async function contractBoundByEngineWorktree(cwd) {
154
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
155
+ }
131
156
  export async function cleanupContractWorkspace(cwd, contractId) {
132
157
  const stableRoot = await stableRepoRoot(cwd);
133
158
  const git = createGit(stableRoot);
@@ -1,4 +1,5 @@
1
- import { ACTOR_IDENTITY, SETTINGS_FILE } from "./keiyaku.js";
1
+ import { ACTOR_IDENTITY, SETTINGS_PATH_SEGMENTS } from "./keiyaku-constants.js";
2
+ const SETTINGS_DISPLAY_PATH = SETTINGS_PATH_SEGMENTS.join("/");
2
3
  const DEFAULT_HINT_CONTEXT = {
3
4
  bindToolName: "bind",
4
5
  arcToolName: "arc",
@@ -20,6 +21,8 @@ function resolveFlowErrorHint(code, context) {
20
21
  switch (code) {
21
22
  case "NOT_GIT_REPO":
22
23
  return "The provided `cwd` is not a git repository.";
24
+ case "GIT_UNAVAILABLE":
25
+ return "Install Git, or use an explicitly bare command that does not require repository authority.";
23
26
  case "ACTIVE_KEIYAKU_EXISTS":
24
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.`;
25
28
  case "CONTRACT_AMBIGUOUS":
@@ -63,7 +66,7 @@ function resolveFlowErrorHint(code, context) {
63
66
  case "INVALID_CLOSE_INTENT":
64
67
  return "Invalid close intent. Valid values are `CLAIM` and `FORFEIT`.";
65
68
  case "INVALID_SETTINGS":
66
- return `Fix invalid configuration in \`${SETTINGS_FILE}\` and retry.`;
69
+ return `Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry.`;
67
70
  case "INVALID_EFFORT_OVERRIDE":
68
71
  return "Choose an effort value declared by the selected Akuma's effortOptions.";
69
72
  case "CONCURRENT_MODIFICATION":
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs
2
- export const VERSION = "2.8.5";
2
+ export const VERSION = "2.9.0";
@@ -1,17 +1,41 @@
1
1
  import { simpleGit } from "simple-git";
2
2
  import { FlowError } from "../flow-error.js";
3
- import { createGit, wrapGitError } from "./core.js";
4
- export async function isGitRepo(cwd) {
5
- const git = simpleGit(cwd);
6
- git.env("GIT_TERMINAL_PROMPT", "0");
7
- git.env("GCM_INTERACTIVE", "Never");
8
- git.env("GIT_MERGE_AUTOEDIT", "no");
9
- git.env("LC_ALL", "C");
3
+ import { createGit, errorContainsAnyPattern, isGitExecutableUnavailable, NOT_GIT_REPOSITORY_PATTERNS, wrapGitError, } from "./core.js";
4
+ export async function observeGitRepository(cwd) {
10
5
  try {
11
- return await git.checkIsRepo();
6
+ const git = simpleGit(cwd);
7
+ git.env("GIT_TERMINAL_PROMPT", "0");
8
+ git.env("GCM_INTERACTIVE", "Never");
9
+ git.env("GIT_MERGE_AUTOEDIT", "no");
10
+ git.env("LC_ALL", "C");
11
+ git.env("PATH", process.env.PATH ?? "");
12
+ const output = await git.raw(["rev-parse", "--is-inside-work-tree", "--is-bare-repository"]);
13
+ const [insideWorktree, bare] = output.trim().split(/\r?\n/);
14
+ return {
15
+ capability: "available",
16
+ coordinate: insideWorktree === "true" && bare !== "true" ? "repository" : "non-repository",
17
+ };
12
18
  }
13
19
  catch (err) {
14
- throw wrapGitError("check-is-repo", err, cwd);
20
+ if (isGitExecutableUnavailable(err))
21
+ return { capability: "unavailable" };
22
+ if (errorContainsAnyPattern(err, NOT_GIT_REPOSITORY_PATTERNS)) {
23
+ return { capability: "available", coordinate: "non-repository" };
24
+ }
25
+ return { capability: "unknown", error: wrapGitError("check-is-repo", err, cwd) };
26
+ }
27
+ }
28
+ export function assertGitObservation(observation, operation) {
29
+ if (observation.capability === "unavailable") {
30
+ throw new FlowError("GIT_UNAVAILABLE", `Git executable is unavailable; ${operation} requires repository authority.`);
31
+ }
32
+ if (observation.capability === "unknown")
33
+ throw observation.error;
34
+ return observation.coordinate;
35
+ }
36
+ export async function requireGitRepository(cwd, operation) {
37
+ if (assertGitObservation(await observeGitRepository(cwd), operation) === "non-repository") {
38
+ throw new FlowError("NOT_GIT_REPO", `${cwd} is not a git repository`);
15
39
  }
16
40
  }
17
41
  export async function getCurrentBranch(cwd) {
@@ -1,5 +1,8 @@
1
1
  import { FlowError } from "../flow-error.js";
2
2
  import { createGit, wrapGitError } from "./core.js";
3
+ export function shortCommitHash(hash) {
4
+ return hash.slice(0, 12);
5
+ }
3
6
  export async function resolveCommitRef(cwd, ref) {
4
7
  const git = createGit(cwd);
5
8
  try {
package/build/git/core.js CHANGED
@@ -10,6 +10,14 @@ export const MISSING_HEAD_PATTERNS = [
10
10
  "unknown revision or path not in the working tree",
11
11
  "bad revision 'HEAD'",
12
12
  ];
13
+ /** Git is an optional adapter for directory-local product surfaces. */
14
+ export function isGitExecutableUnavailable(error) {
15
+ const source = (error ?? {});
16
+ if (source.code === "ENOENT")
17
+ return true;
18
+ const message = typeof source.message === "string" ? source.message : String(error);
19
+ return /spawn\s+git\s+ENOENT|git(?:\.exe)?["']?\s*:\s*(?:not found|no such file)/i.test(message);
20
+ }
13
21
  export function compactText(input, maxChars = GIT_ERROR_COMPACT_MAX_CHARS) {
14
22
  const text = input.trim();
15
23
  if (!text)
@@ -58,5 +66,6 @@ export function createGit(cwd) {
58
66
  git.env("GCM_INTERACTIVE", "Never");
59
67
  git.env("GIT_MERGE_AUTOEDIT", "no");
60
68
  git.env("LC_ALL", "C");
69
+ git.env("PATH", process.env.PATH ?? "");
61
70
  return git;
62
71
  }
@@ -1,17 +1,4 @@
1
- import { Buffer } from "node:buffer";
2
- const DIFF_GIT_HEADER_PREFIX = "diff --git ";
3
1
  const DIFF_TRUNCATED_MARKER = "...[truncated]";
4
- const GIT_QUOTED_PATH_ESCAPES = {
5
- '"': 34,
6
- "\\": 92,
7
- a: 7,
8
- b: 8,
9
- f: 12,
10
- n: 10,
11
- r: 13,
12
- t: 9,
13
- v: 11,
14
- };
15
2
  export function splitDiffByFile(content) {
16
3
  const sections = [];
17
4
  let current = [];
@@ -94,87 +81,6 @@ export function isMissingBaseRevisionError(err) {
94
81
  text.includes("unknown revision or path not in the working tree") ||
95
82
  text.includes("ambiguous argument"));
96
83
  }
97
- function unescapeQuotedDiffPath(value) {
98
- const bytes = [];
99
- for (let index = 0; index < value.length; index += 1) {
100
- const char = value[index];
101
- if (char !== "\\") {
102
- bytes.push(...Buffer.from(char ?? "", "utf8"));
103
- continue;
104
- }
105
- const escaped = value[index + 1];
106
- if (escaped === undefined) {
107
- bytes.push(92);
108
- continue;
109
- }
110
- if (escaped >= "0" && escaped <= "7") {
111
- let octal = escaped;
112
- let cursor = index + 2;
113
- while (cursor < value.length && octal.length < 3) {
114
- const digit = value[cursor];
115
- if (digit === undefined || digit < "0" || digit > "7")
116
- break;
117
- octal += digit;
118
- cursor += 1;
119
- }
120
- bytes.push(Number.parseInt(octal, 8));
121
- index = cursor - 1;
122
- continue;
123
- }
124
- const decoded = GIT_QUOTED_PATH_ESCAPES[escaped];
125
- if (decoded !== undefined) {
126
- bytes.push(decoded);
127
- index += 1;
128
- continue;
129
- }
130
- bytes.push(...Buffer.from(escaped, "utf8"));
131
- index += 1;
132
- }
133
- return Buffer.from(bytes).toString("utf8");
134
- }
135
- function readDiffHeaderToken(line, startIndex) {
136
- let index = startIndex;
137
- while (index < line.length && line[index] === " ") {
138
- index += 1;
139
- }
140
- if (index >= line.length)
141
- return null;
142
- if (line[index] !== '"') {
143
- let endIndex = index;
144
- while (endIndex < line.length && line[endIndex] !== " ") {
145
- endIndex += 1;
146
- }
147
- return {
148
- value: line.slice(index, endIndex),
149
- nextIndex: endIndex,
150
- };
151
- }
152
- let endIndex = index + 1;
153
- let escaped = false;
154
- while (endIndex < line.length) {
155
- const char = line[endIndex];
156
- if (!escaped && char === '"') {
157
- return {
158
- value: unescapeQuotedDiffPath(line.slice(index + 1, endIndex)),
159
- nextIndex: endIndex + 1,
160
- };
161
- }
162
- escaped = !escaped && char === "\\";
163
- endIndex += 1;
164
- }
165
- return null;
166
- }
167
- export function parseDiffPathFromHeader(diffGitLine) {
168
- if (!diffGitLine.startsWith(DIFF_GIT_HEADER_PREFIX))
169
- return null;
170
- const aPart = readDiffHeaderToken(diffGitLine, DIFF_GIT_HEADER_PREFIX.length);
171
- if (!aPart)
172
- return null;
173
- const bPart = readDiffHeaderToken(diffGitLine, aPart.nextIndex)?.value ?? "";
174
- if (!bPart.startsWith("b/"))
175
- return null;
176
- return bPart.slice(2);
177
- }
178
84
  export function totalJoinedLength(blocks) {
179
85
  if (blocks.length === 0)
180
86
  return 0;
package/build/index.js CHANGED
@@ -6,7 +6,9 @@ import { ENV_KEYS } from "./config/env-keys.js";
6
6
  import { logErrorWithCause } from "./telemetry/logger.js";
7
7
  import { runProjectionGenerationRuntime } from "./core/projection-generation-runtime.js";
8
8
  import { renderCliHelp, runCliCommand } from "./cli/index.js";
9
+ import { installCliOutputStreamErrorPolicy } from "./cli/output-stream-error-policy.js";
9
10
  import { assertSubagentCliCommandAllowed } from "./cli/subagent-guard.js";
11
+ installCliOutputStreamErrorPolicy(process.stdout, process.stderr);
10
12
  const packageVersion = VERSION;
11
13
  function findCliCwd(args) {
12
14
  for (let index = 0; index < args.length; index += 1) {
@@ -0,0 +1,2 @@
1
+ export const ACTOR_IDENTITY = "Akuma";
2
+ export const SETTINGS_PATH_SEGMENTS = [".keiyaku", "settings.json"];
package/build/keiyaku.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import * as path from "path";
2
+ import { ACTOR_IDENTITY, SETTINGS_PATH_SEGMENTS } from "./keiyaku-constants.js";
3
+ export { ACTOR_IDENTITY } from "./keiyaku-constants.js";
2
4
  export const KEIYAKU_FILE = path.join(".keiyaku", "KEIYAKU.md");
3
5
  export const KEIYAKU_DRAFT_DIR = path.join(".keiyaku", "draft");
4
6
  export const RESPONSE_HISTORY_DIR = path.join(".keiyaku", "response");
@@ -13,7 +15,6 @@ export const TOOL_NAMES = {
13
15
  status: "status",
14
16
  help: "help",
15
17
  };
16
- export const ACTOR_IDENTITY = "Akuma";
17
18
  export const ACTOR_IDENTITY_PLURAL = `${ACTOR_IDENTITY}s`;
18
19
  function formatArtifactDirectory(directory) {
19
20
  return `\`${directory}/\``;
@@ -35,7 +36,7 @@ export const DRAFT_ARTIFACT_NOTICE = buildArtifactNotice(KEIYAKU_ARTIFACT_DIRS[0
35
36
  export const RESPONSE_ARTIFACT_NOTICE = buildArtifactNotice(KEIYAKU_ARTIFACT_DIRS[1]);
36
37
  export const KEIYAKU_BRANCH_PREFIX = "keiyaku/";
37
38
  export const BASE_RULES_FILE = path.join(".keiyaku", "base-rules.md");
38
- export const SETTINGS_FILE = path.join(".keiyaku", "settings.json");
39
+ export const SETTINGS_FILE = path.join(...SETTINGS_PATH_SEGMENTS);
39
40
  export const SUBAGENT_PROVIDERS = [
40
41
  "codex-sdk",
41
42
  "codex-app-server",
@@ -50,7 +51,7 @@ export const CODEX_MODEL_REASONING_EFFORT_CONFIG_KEY = "model_reasoning_effort";
50
51
  export const BUILTIN_AGENT_NAMES = ["akuma-codex", "akuma-claude"];
51
52
  export const DEFAULT_AGENT_NAME = "akuma-codex";
52
53
  export const BUILTIN_AGENT_PROFILES = {
53
- "akuma-codex": { provider: "codex-sdk", description: "Default Codex SDK Akuma." },
54
+ "akuma-codex": { provider: "codex-sdk", description: "Deprecated Codex SDK Akuma; use codex-app-server." },
54
55
  "akuma-claude": { provider: "claude-agent-sdk", description: "Default Claude Agent SDK Akuma." },
55
56
  };
56
57
  export const CLAUDE_PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions", "plan", "dontAsk", "auto"];