@astrosheep/keiyaku 2.8.4 → 2.9.0

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 (229) 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 +39 -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 +35 -0
  55. package/build/cli/commands/projection/kill/meta.js +11 -0
  56. package/build/cli/commands/projection/revive/handler.js +53 -0
  57. package/build/cli/commands/projection/revive/meta.js +9 -0
  58. package/build/cli/commands/projection/status/handler.js +23 -0
  59. package/build/cli/commands/projection/status/meta.js +9 -0
  60. package/build/cli/commands/projection/tell/handler.js +34 -0
  61. package/build/cli/commands/projection/tell/meta.js +9 -0
  62. package/build/cli/commands/projection/wait/handler.js +21 -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.js +159 -72
  106. package/build/cli/projection-address.js +5 -0
  107. package/build/cli/render/arc.js +1 -1
  108. package/build/cli/render/errors.js +4 -3
  109. package/build/cli/render/format.js +14 -0
  110. package/build/cli/render/kanshi.js +106 -0
  111. package/build/cli/render/petition.js +24 -1
  112. package/build/cli/render/projection-activity.js +5 -1
  113. package/build/cli/render/shared.js +2 -2
  114. package/build/cli/render/status.js +79 -13
  115. package/build/cli/render/task.js +113 -0
  116. package/build/cli/render/wait.js +23 -3
  117. package/build/cli/types.js +1 -1
  118. package/build/config/{akuma-loader_v2.js → akuma-loader.js} +2 -2
  119. package/build/config/env.js +9 -4
  120. package/build/config/settings/disease.js +26 -0
  121. package/build/config/settings/knobs.js +8 -0
  122. package/build/config/settings/loader.js +244 -0
  123. package/build/config/settings/schema.js +276 -0
  124. package/build/core/addressing.js +1 -1
  125. package/build/core/amend.js +3 -3
  126. package/build/core/arc.js +5 -4
  127. package/build/core/bind-workspace.js +123 -0
  128. package/build/core/bind.js +75 -155
  129. package/build/core/call/call.js +224 -0
  130. package/build/core/call/context.js +238 -0
  131. package/build/core/call/execution.js +217 -0
  132. package/build/core/call/prompt.js +42 -0
  133. package/build/core/call/reference.js +24 -0
  134. package/build/core/call-persist.js +1 -1
  135. package/build/core/claim-delivery.js +247 -0
  136. package/build/core/claim.js +39 -277
  137. package/build/core/context.js +2 -1
  138. package/build/core/{render.js → contract-view.js} +12 -140
  139. package/build/core/draft.js +107 -9
  140. package/build/core/entry.js +9 -1
  141. package/build/core/execution-coordinate.js +4 -20
  142. package/build/core/execution-pact.js +16 -31
  143. package/build/core/forfeit.js +35 -22
  144. package/build/core/hints.js +2 -2
  145. package/build/core/log.js +2 -2
  146. package/build/core/petition-claim-gates.js +8 -6
  147. package/build/core/petition-preview.js +97 -0
  148. package/build/core/petition-run.js +3 -2
  149. package/build/core/petition.js +39 -26
  150. package/build/core/projection/generation/database.js +306 -0
  151. package/build/core/projection/generation/identity.js +84 -0
  152. package/build/core/projection/generation/model.js +201 -0
  153. package/build/core/projection/generation/store.js +73 -0
  154. package/build/core/projection/generation/transitions.js +258 -0
  155. package/build/core/projection/heart.js +0 -307
  156. package/build/core/projection/tell/database.js +109 -0
  157. package/build/core/projection/tell/model.js +97 -0
  158. package/build/core/{projection-tells.js → projection/tell/store.js} +3 -158
  159. package/build/core/projection-activity.js +23 -2
  160. package/build/core/projection-core.js +2 -1
  161. package/build/core/projection-execution-observer.js +259 -0
  162. package/build/core/projection-generation-continuation.js +1 -1
  163. package/build/core/projection-generation-execution.js +90 -20
  164. package/build/core/projection-generation-launcher.js +1 -1
  165. package/build/core/projection-generation-runner.js +198 -4
  166. package/build/core/projection-identity.js +1 -1
  167. package/build/core/projection-kill.js +50 -17
  168. package/build/core/projection-life-observer.js +1 -1
  169. package/build/core/projection-life-protocol.js +10 -0
  170. package/build/core/projection-mint.js +1 -1
  171. package/build/core/projection-status.js +45 -32
  172. package/build/core/projection-wait.js +12 -2
  173. package/build/core/projection-wake.js +5 -2
  174. package/build/core/{queue_v2.js → queue.js} +2 -1
  175. package/build/core/registry.js +1 -1
  176. package/build/core/renew.js +7 -7
  177. package/build/core/response-artifact-catalog-sqlite.js +112 -0
  178. package/build/core/response-artifact-catalog.js +7 -0
  179. package/build/core/seal.js +44 -16
  180. package/build/core/status/board.js +286 -0
  181. package/build/core/status/drift.js +99 -0
  182. package/build/core/status/lifecycle.js +84 -0
  183. package/build/core/status/reconciliation.js +131 -0
  184. package/build/core/stored-agent-event.js +28 -4
  185. package/build/core/target-ref.js +10 -0
  186. package/build/core/task/board.js +175 -0
  187. package/build/core/task/commands.js +211 -0
  188. package/build/core/task/document.js +189 -0
  189. package/build/core/task/model.js +1 -0
  190. package/build/core/task/settlement-git.js +207 -0
  191. package/build/core/task/settlement-policy.js +82 -0
  192. package/build/core/task/source-board.js +84 -0
  193. package/build/core/task/source-model.js +1 -0
  194. package/build/core/task/validation.js +44 -0
  195. package/build/core/task-contract.js +224 -0
  196. package/build/core/task-doctor.js +14 -0
  197. package/build/core/task-git-actor.js +38 -0
  198. package/build/core/task-git-runtime.js +34 -0
  199. package/build/core/task-git-store.js +375 -0
  200. package/build/core/task-repository.js +1 -0
  201. package/build/core/task-store-repository.js +86 -0
  202. package/build/core/task-store.js +1 -0
  203. package/build/core/task-worktree.js +51 -0
  204. package/build/core/task.js +4 -0
  205. package/build/core/transcripts.js +138 -56
  206. package/build/core/validation.js +17 -0
  207. package/build/core/verdict.js +2 -2
  208. package/build/core/worktree-path.js +24 -6
  209. package/build/flow-error.js +3 -2
  210. package/build/generated/version.js +1 -1
  211. package/build/git/commits.js +3 -0
  212. package/build/git/diff/parsers.js +0 -94
  213. package/build/index.js +2 -0
  214. package/build/keiyaku-constants.js +2 -0
  215. package/build/keiyaku.js +4 -3
  216. package/package.json +3 -2
  217. package/skills/keiyaku/SKILL.md +1 -1
  218. package/skills/keiyaku-akuma/SKILL.md +11 -6
  219. package/build/agents/harness/pump.js +0 -158
  220. package/build/agents/providers/codex-app-server.js +0 -864
  221. package/build/agents/providers/opencode-sdk.js +0 -363
  222. package/build/agents/providers/pi.js +0 -557
  223. package/build/config/settings.js +0 -533
  224. package/build/core/call.js +0 -686
  225. package/build/core/projection/generation.js +0 -412
  226. package/build/core/projection/identity.js +0 -81
  227. package/build/core/projection/mint.js +0 -174
  228. package/build/core/projection-generation-store.js +0 -707
  229. package/build/core/status.js +0 -539
@@ -0,0 +1,86 @@
1
+ import { requireHealthy } from "./task/board.js";
2
+ import { parseTaskDocument, serializeTaskDocument } from "./task/document.js";
3
+ import { buildTaskSourceBoard } from "./task/source-board.js";
4
+ function taskBoardFromSourceBoard(sourceBoard) {
5
+ const tasks = sourceBoard.tasks.map((task) => ({
6
+ id: task.id,
7
+ title: task.title,
8
+ state: task.state,
9
+ pri: task.pri,
10
+ needs: [...task.needs],
11
+ parent: task.parent,
12
+ from: [...task.from],
13
+ createdAt: task.createdAt,
14
+ creator: task.creator,
15
+ ...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
16
+ ...(task.contractId === undefined ? {} : { contractId: task.contractId }),
17
+ body: task.body,
18
+ blocks: [...task.blocks],
19
+ blocked: task.blocked,
20
+ releasedNeeds: [...task.releasedNeeds],
21
+ }));
22
+ return {
23
+ tasks,
24
+ diseases: sourceBoard.diseases.map((disease) => ({
25
+ code: disease.code,
26
+ message: disease.message,
27
+ ...(disease.id === undefined ? {} : { id: disease.id }),
28
+ ...(disease.relatedIds === undefined ? {} : { relatedIds: [...disease.relatedIds] }),
29
+ })),
30
+ byId: new Map(tasks.map((task) => [task.id, task])),
31
+ };
32
+ }
33
+ export function prepareTaskDocumentReplacements(sources, replacements) {
34
+ const serialized = replacements.map((document) => ({
35
+ id: document.id,
36
+ text: serializeTaskDocument(document),
37
+ }));
38
+ const replacementIds = new Set(serialized.map((replacement) => replacement.id));
39
+ const paths = new Map();
40
+ const retained = sources.filter((source) => {
41
+ try {
42
+ const id = parseTaskDocument(source).id;
43
+ paths.set(id, source.path);
44
+ return !replacementIds.has(id);
45
+ }
46
+ catch {
47
+ // Malformed sources remain quarantined and visible to the resulting board.
48
+ return true;
49
+ }
50
+ });
51
+ const replacementSources = serialized.map((replacement) => ({
52
+ path: paths.get(replacement.id) ?? `${replacement.id}.md`,
53
+ text: replacement.text,
54
+ }));
55
+ const resultingSources = [...retained, ...replacementSources];
56
+ const board = taskBoardFromSourceBoard(buildTaskSourceBoard(resultingSources));
57
+ requireHealthy(board);
58
+ return {
59
+ sources: resultingSources,
60
+ writes: serialized,
61
+ board,
62
+ };
63
+ }
64
+ function mutationPlan(sources, mutation) {
65
+ const prepared = prepareTaskDocumentReplacements(sources, mutation.replacements);
66
+ return {
67
+ writes: prepared.writes,
68
+ value: mutation.select(prepared.board),
69
+ };
70
+ }
71
+ export function taskRepositoryFromStore(store) {
72
+ return {
73
+ read: async () => taskBoardFromSourceBoard(buildTaskSourceBoard(await store.read())),
74
+ mutate: async (apply) => store.mutate(async (sources) => mutationPlan(sources, await apply(taskBoardFromSourceBoard(buildTaskSourceBoard(sources))))),
75
+ };
76
+ }
77
+ export function taskRepositoryFromLockedBoard(transaction) {
78
+ return {
79
+ read: async () => taskBoardFromSourceBoard(buildTaskSourceBoard(transaction.sources)),
80
+ mutate: async (apply) => {
81
+ const plan = mutationPlan(transaction.sources, await apply(taskBoardFromSourceBoard(buildTaskSourceBoard(transaction.sources))));
82
+ await transaction.replace(plan.writes);
83
+ return plan.value;
84
+ },
85
+ };
86
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { FlowError } from "../flow-error.js";
2
+ function documentFacts(task) {
3
+ return JSON.stringify({
4
+ id: task.id,
5
+ title: task.title,
6
+ state: task.state,
7
+ pri: task.pri,
8
+ needs: [...task.needs],
9
+ parent: task.parent,
10
+ from: [...task.from],
11
+ createdAt: task.createdAt,
12
+ creator: task.creator,
13
+ ...(task.startedAt === undefined ? {} : { startedAt: task.startedAt }),
14
+ ...(task.contractId === undefined ? {} : { contractId: task.contractId }),
15
+ body: task.body,
16
+ });
17
+ }
18
+ function isNeedsAppend(prior, next) {
19
+ return next.needs.length > prior.needs.length
20
+ && prior.needs.every((need, index) => next.needs[index] === need)
21
+ && new Set(next.needs).size === next.needs.length
22
+ && documentFacts(prior) === documentFacts({ ...next, needs: prior.needs });
23
+ }
24
+ export function validateTaskWorktreeMutation(before, replacements, after, contractId) {
25
+ const existing = new Map(before.tasks.map((task) => [task.id, task]));
26
+ const bound = before.tasks.filter((task) => task.contractId === contractId);
27
+ for (const replacement of replacements) {
28
+ const prior = existing.get(replacement.id);
29
+ if (!prior)
30
+ continue;
31
+ const next = after.byId.get(replacement.id);
32
+ if (bound.length === 1 && prior.id === bound[0].id && next && isNeedsAppend(prior, next))
33
+ continue;
34
+ throw new FlowError("UNSUPPORTED_POLICY", `task ${replacement.id} is not yours; this checkout is bound to contract ${contractId}`);
35
+ }
36
+ }
37
+ export function withTaskWorktreePolicy(repository, contractId) {
38
+ return {
39
+ read: repository.read.bind(repository),
40
+ mutate: async (apply) => repository.mutate(async (before) => {
41
+ const mutation = await apply(before);
42
+ return {
43
+ replacements: mutation.replacements,
44
+ select(after) {
45
+ validateTaskWorktreeMutation(before, mutation.replacements, after, contractId);
46
+ return mutation.select(after);
47
+ },
48
+ };
49
+ }),
50
+ };
51
+ }
@@ -0,0 +1,4 @@
1
+ export { parseTaskDocument, serializeTaskDocument, } from "./task/document.js";
2
+ export { resolveTaskReadId, } from "./task/board.js";
3
+ export { buildTaskBoard } from "./task/source-board.js";
4
+ export { readTaskBoard, createTask, updateTask, startTask, stopTask, doneTask, dropTask, selectSoleInProgress, rollbackOrphanTaskPointers, } from "./task/commands.js";
@@ -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,7 @@ 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
8
  export function findBindEntry(entries) {
9
9
  return entries.find((entry) => entry.kind === "bind");
10
10
  }
@@ -94,11 +94,11 @@ export async function findContractWorktreePath(cwd, contractId) {
94
94
  return (await findContractWorktree(cwd, contractId)).path;
95
95
  }
96
96
  /**
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.
97
+ * Resolve the contract id for an engine-owned bound worktree containing cwd.
98
+ * Matches the canonical bind place path and worktree workspace only; ledger
99
+ * terminal state is optional so residual worktrees after cleanup still bind.
100
100
  */
101
- export async function contractPresentedByWorktree(cwd) {
101
+ async function contractIdForEngineOwnedWorktree(cwd, options) {
102
102
  const stableRoot = await stableRepoRoot(cwd);
103
103
  const canonicalCwd = await fs.realpath(cwd);
104
104
  for (const worktree of await listWorktrees(stableRoot)) {
@@ -107,7 +107,9 @@ export async function contractPresentedByWorktree(cwd) {
107
107
  continue;
108
108
  const contractId = worktree.branch.slice(branchPrefix.length);
109
109
  const ledger = await readLedger(stableRoot, contractId);
110
- if (!ledger || isTerminalState(deriveContractState(ledger.entries)))
110
+ if (!ledger)
111
+ continue;
112
+ if (options.requireActive && isTerminalState(deriveContractState(ledger.entries)))
111
113
  continue;
112
114
  const bind = findBindEntry(ledger.entries);
113
115
  if (!bind || bind.data.workspace !== "worktree")
@@ -128,6 +130,22 @@ export async function contractPresentedByWorktree(cwd) {
128
130
  }
129
131
  return undefined;
130
132
  }
133
+ /**
134
+ * Resolve the active commission presented by an engine-owned contract
135
+ * worktree containing cwd. Ordinary repository directories and arbitrary
136
+ * worktrees never present a commission address.
137
+ */
138
+ export async function contractPresentedByWorktree(cwd) {
139
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: true });
140
+ }
141
+ /**
142
+ * Resolve the contract bound by an engine-owned worktree containing cwd,
143
+ * including when the ledger is already terminal. Task CLI contract context
144
+ * uses this residual binding; call/addressing keep active-only presentation.
145
+ */
146
+ export async function contractBoundByEngineWorktree(cwd) {
147
+ return contractIdForEngineOwnedWorktree(cwd, { requireActive: false });
148
+ }
131
149
  export async function cleanupContractWorkspace(cwd, contractId) {
132
150
  const stableRoot = await stableRepoRoot(cwd);
133
151
  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",
@@ -63,7 +64,7 @@ function resolveFlowErrorHint(code, context) {
63
64
  case "INVALID_CLOSE_INTENT":
64
65
  return "Invalid close intent. Valid values are `CLAIM` and `FORFEIT`.";
65
66
  case "INVALID_SETTINGS":
66
- return `Fix invalid configuration in \`${SETTINGS_FILE}\` and retry.`;
67
+ return `Fix invalid configuration in \`${SETTINGS_DISPLAY_PATH}\` and retry.`;
67
68
  case "INVALID_EFFORT_OVERRIDE":
68
69
  return "Choose an effort value declared by the selected Akuma's effortOptions.";
69
70
  case "CONCURRENT_MODIFICATION":
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs
2
- export const VERSION = "2.8.4";
2
+ export const VERSION = "2.8.5";
@@ -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 {
@@ -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"];