@astrosheep/keiyaku 4.1.0 → 4.1.2

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 (278) hide show
  1. package/README.md +1 -1
  2. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -0
  3. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +3 -0
  4. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +3 -6
  5. package/build/src/akuma/akuma-errors.d.ts +6 -0
  6. package/build/src/akuma/akuma-errors.js +9 -0
  7. package/build/src/akuma/akuma-handle.d.ts +39 -0
  8. package/build/src/akuma/akuma-handle.js +263 -0
  9. package/build/src/akuma/akuma-observe.d.ts +19 -0
  10. package/build/src/akuma/akuma-observe.js +74 -0
  11. package/build/src/akuma/akuma-product-symbols.d.ts +1 -0
  12. package/build/src/akuma/akuma-product-symbols.js +1 -0
  13. package/build/src/akuma/akuma-product.d.ts +18 -0
  14. package/build/src/akuma/akuma-product.js +154 -0
  15. package/build/src/akuma/akuma.d.ts +37 -81
  16. package/build/src/akuma/akuma.js +39 -447
  17. package/build/src/akuma/allowed.d.ts +1 -0
  18. package/build/src/akuma/allowed.js +6 -0
  19. package/build/src/akuma/archetype.js +8 -5
  20. package/build/src/akuma/body-supervisor.d.ts +26 -0
  21. package/build/src/akuma/body-supervisor.js +89 -0
  22. package/build/src/akuma/body.d.ts +45 -4
  23. package/build/src/akuma/body.js +145 -35
  24. package/build/src/akuma/coordinate.js +17 -7
  25. package/build/src/akuma/heart/facts.js +1 -1
  26. package/build/src/akuma/heart/index.d.ts +1 -1
  27. package/build/src/akuma/heart/index.js +2 -4
  28. package/build/src/akuma/heart/request-authority.js +4 -4
  29. package/build/src/akuma/heart/request-rows.js +27 -16
  30. package/build/src/akuma/heart/rows.js +79 -41
  31. package/build/src/akuma/heart/soul.js +1 -1
  32. package/build/src/akuma/heart/storage.d.ts +1 -0
  33. package/build/src/akuma/heart/storage.js +76 -11
  34. package/build/src/akuma/heart/tells.js +35 -18
  35. package/build/src/akuma/heart/timeline.js +19 -11
  36. package/build/src/akuma/identity.d.ts +1 -0
  37. package/build/src/akuma/identity.js +1 -0
  38. package/build/src/akuma/index.d.ts +3 -3
  39. package/build/src/akuma/index.js +1 -1
  40. package/build/src/akuma/nuke.js +106 -14
  41. package/build/src/akuma/projection-read.d.ts +17 -0
  42. package/build/src/akuma/projection-read.js +135 -0
  43. package/build/src/akuma/projection.d.ts +1 -16
  44. package/build/src/akuma/projection.js +77 -154
  45. package/build/src/akuma/provider-recipe.js +11 -5
  46. package/build/src/akuma/provider.js +105 -62
  47. package/build/src/akuma/providers/acp/config.js +5 -6
  48. package/build/src/akuma/providers/acp/core.js +17 -10
  49. package/build/src/akuma/providers/acp/events.js +38 -29
  50. package/build/src/akuma/providers/acp/index.js +13 -9
  51. package/build/src/akuma/providers/claude/events.js +8 -12
  52. package/build/src/akuma/providers/claude/index.d.ts +1 -0
  53. package/build/src/akuma/providers/claude/index.js +55 -26
  54. package/build/src/akuma/providers/claude/input.js +6 -2
  55. package/build/src/akuma/providers/codex-app-server/events.js +61 -36
  56. package/build/src/akuma/providers/codex-app-server/index.js +25 -13
  57. package/build/src/akuma/providers/grok-build/index.js +22 -25
  58. package/build/src/akuma/providers/index.js +2 -18
  59. package/build/src/akuma/providers/opencode-sdk/events.js +32 -18
  60. package/build/src/akuma/providers/opencode-sdk/index.js +26 -17
  61. package/build/src/akuma/providers/opencode-sdk/session.js +21 -5
  62. package/build/src/akuma/providers/pi/events.js +26 -19
  63. package/build/src/akuma/providers/pi/index.js +58 -40
  64. package/build/src/akuma/publication.js +9 -9
  65. package/build/src/akuma/request-execution.d.ts +6 -0
  66. package/build/src/akuma/request-execution.js +86 -0
  67. package/build/src/akuma/request-serve.d.ts +12 -1
  68. package/build/src/akuma/request-serve.js +19 -122
  69. package/build/src/akuma/request-wire.d.ts +2 -2
  70. package/build/src/akuma/request-wire.js +75 -102
  71. package/build/src/akuma/requests.js +2 -2
  72. package/build/src/akuma/{body-turn.d.ts → turn-drive.d.ts} +2 -25
  73. package/build/src/akuma/{body-turn.js → turn-drive.js} +20 -103
  74. package/build/src/akuma-body.js +6 -5
  75. package/build/src/body/amend.d.ts +4 -1
  76. package/build/src/body/amend.js +14 -6
  77. package/build/src/body/decode.js +2 -4
  78. package/build/src/body/envelope.js +2 -4
  79. package/build/src/body/region.d.ts +1 -2
  80. package/build/src/body/region.js +2 -70
  81. package/build/src/body/render.js +17 -12
  82. package/build/src/body/shape.js +1 -7
  83. package/build/src/cli/accepted.d.ts +1 -1
  84. package/build/src/cli/accepted.js +8 -1
  85. package/build/src/cli/commands/akuma-invoke.js +18 -6
  86. package/build/src/cli/commands/akuma.d.ts +0 -6
  87. package/build/src/cli/commands/akuma.js +11 -24
  88. package/build/src/cli/commands/amend.d.ts +2 -2
  89. package/build/src/cli/commands/amend.js +2 -4
  90. package/build/src/cli/commands/bind.d.ts +4 -4
  91. package/build/src/cli/commands/bind.js +16 -6
  92. package/build/src/cli/commands/contract-invoke.d.ts +25 -0
  93. package/build/src/cli/commands/contract-invoke.js +193 -0
  94. package/build/src/cli/commands/contract.d.ts +109 -10
  95. package/build/src/cli/commands/contract.js +339 -16
  96. package/build/src/cli/commands/install.d.ts +1 -1
  97. package/build/src/cli/commands/install.js +23 -15
  98. package/build/src/cli/commands/task-invoke.js +187 -67
  99. package/build/src/cli/commands/task-query.js +48 -24
  100. package/build/src/cli/commands/task.js +150 -38
  101. package/build/src/cli/coordinates.js +2 -4
  102. package/build/src/cli/invoke.js +92 -208
  103. package/build/src/cli/main.js +4 -2
  104. package/build/src/cli/parse.d.ts +3 -90
  105. package/build/src/cli/parse.js +27 -233
  106. package/build/src/cli/render/akuma-activity.d.ts +38 -0
  107. package/build/src/cli/render/akuma-activity.js +362 -0
  108. package/build/src/cli/render/akuma-tool-command.js +6 -7
  109. package/build/src/cli/render/akuma-tool.js +10 -8
  110. package/build/src/cli/render/akuma.d.ts +1 -1
  111. package/build/src/cli/render/akuma.js +61 -353
  112. package/build/src/cli/render/audit.js +5 -15
  113. package/build/src/cli/render/board.d.ts +1 -0
  114. package/build/src/cli/render/board.js +17 -0
  115. package/build/src/cli/render/catalog.js +81 -8
  116. package/build/src/cli/render/contract-history.d.ts +2 -0
  117. package/build/src/cli/render/contract-history.js +83 -0
  118. package/build/src/cli/render/contract-observation.d.ts +10 -0
  119. package/build/src/cli/render/contract-observation.js +58 -0
  120. package/build/src/cli/render/contract.d.ts +1 -2
  121. package/build/src/cli/render/contract.js +41 -142
  122. package/build/src/cli/render/kanshi.js +239 -215
  123. package/build/src/cli/render/receipt.js +2 -9
  124. package/build/src/cli/render/refusal.js +10 -15
  125. package/build/src/cli/render/region.js +22 -6
  126. package/build/src/cli/render/settings.js +4 -4
  127. package/build/src/cli/render/task.js +34 -14
  128. package/build/src/cli/render/terminal.js +16 -5
  129. package/build/src/cli/result.d.ts +3 -3
  130. package/build/src/cli/runtime.d.ts +6 -1
  131. package/build/src/cli/runtime.js +84 -63
  132. package/build/src/cli/selectors.js +2 -4
  133. package/build/src/cli/usage.js +6 -5
  134. package/build/src/contract-worktree.d.ts +1 -23
  135. package/build/src/contract-worktree.js +79 -155
  136. package/build/src/coordination/durable-file.js +7 -3
  137. package/build/src/coordination/sqlite-transaction-lock.js +29 -12
  138. package/build/src/core/facts/codec.js +7 -3
  139. package/build/src/core/facts/types.d.ts +1 -1
  140. package/build/src/core/subject.js +16 -9
  141. package/build/src/core/verbs/abandon.d.ts +1 -1
  142. package/build/src/core/verbs/abandon.js +1 -1
  143. package/build/src/core/verbs/amend.d.ts +1 -1
  144. package/build/src/core/verbs/amend.js +7 -7
  145. package/build/src/core/verbs/attestation.js +4 -1
  146. package/build/src/core/verbs/bind.d.ts +1 -1
  147. package/build/src/core/verbs/bind.js +1 -1
  148. package/build/src/core/verbs/placement.d.ts +1 -1
  149. package/build/src/core/verbs/placement.js +10 -8
  150. package/build/src/dispatch/index.js +8 -6
  151. package/build/src/duration.js +5 -1
  152. package/build/src/git/admission.js +5 -3
  153. package/build/src/git/hooks.d.ts +8 -0
  154. package/build/src/git/hooks.js +63 -14
  155. package/build/src/git/identity.js +1 -4
  156. package/build/src/git/integration.d.ts +2 -3
  157. package/build/src/git/integration.js +32 -39
  158. package/build/src/git/nuke.d.ts +1 -1
  159. package/build/src/git/nuke.js +110 -57
  160. package/build/src/git/observe.d.ts +13 -1
  161. package/build/src/git/observe.js +51 -44
  162. package/build/src/git/process.js +4 -2
  163. package/build/src/git/read-observation.js +10 -8
  164. package/build/src/git/reconcile.d.ts +22 -2
  165. package/build/src/git/reconcile.js +38 -274
  166. package/build/src/git/repository.d.ts +2 -0
  167. package/build/src/git/repository.js +13 -22
  168. package/build/src/git/scratch.d.ts +8 -5
  169. package/build/src/git/scratch.js +29 -2
  170. package/build/src/git/target-placement.d.ts +9 -10
  171. package/build/src/git/target-placement.js +59 -62
  172. package/build/src/git/tender.js +13 -46
  173. package/build/src/git/terminal-reconcile.d.ts +21 -0
  174. package/build/src/git/terminal-reconcile.js +278 -0
  175. package/build/src/git/terminal-seal.js +15 -12
  176. package/build/src/git/tree.js +5 -2
  177. package/build/src/git/workspace.d.ts +24 -2
  178. package/build/src/git/workspace.js +120 -16
  179. package/build/src/identity/coordinates.js +3 -1
  180. package/build/src/identity/selector.js +6 -3
  181. package/build/src/kanshi/fleet.d.ts +6 -0
  182. package/build/src/kanshi/fleet.js +27 -0
  183. package/build/src/kanshi/index.d.ts +1 -1
  184. package/build/src/kanshi/read.js +60 -48
  185. package/build/src/kanshi/report.d.ts +11 -23
  186. package/build/src/kanshi/select.js +25 -24
  187. package/build/src/library/address.d.ts +2 -1
  188. package/build/src/library/address.js +7 -8
  189. package/build/src/library/akuma-creation.js +20 -12
  190. package/build/src/library/audit.d.ts +0 -1
  191. package/build/src/library/audit.js +1 -2
  192. package/build/src/library/bind.d.ts +53 -5
  193. package/build/src/library/bind.js +98 -22
  194. package/build/src/library/catalog.d.ts +1 -0
  195. package/build/src/library/catalog.js +6 -3
  196. package/build/src/library/configuration.js +3 -1
  197. package/build/src/library/continuation.d.ts +15 -4
  198. package/build/src/library/continuation.js +24 -16
  199. package/build/src/library/contract-bind.d.ts +26 -0
  200. package/build/src/library/contract-bind.js +106 -0
  201. package/build/src/library/contract-operations.d.ts +77 -0
  202. package/build/src/library/contract-operations.js +130 -0
  203. package/build/src/library/contract-types.d.ts +95 -0
  204. package/build/src/library/contract-types.js +1 -0
  205. package/build/src/library/contract.d.ts +19 -154
  206. package/build/src/library/contract.js +46 -261
  207. package/build/src/library/fleet.d.ts +1 -1
  208. package/build/src/library/fleet.js +19 -11
  209. package/build/src/library/input.d.ts +2 -0
  210. package/build/src/library/input.js +14 -0
  211. package/build/src/library/keiyaku.d.ts +8 -7
  212. package/build/src/library/keiyaku.js +3 -3
  213. package/build/src/library/nuke.js +14 -5
  214. package/build/src/library/reconcile.d.ts +9 -4
  215. package/build/src/library/reconcile.js +30 -24
  216. package/build/src/library/refusal.d.ts +13 -5
  217. package/build/src/library/refusal.js +7 -0
  218. package/build/src/library/region.d.ts +8 -7
  219. package/build/src/library/region.js +31 -11
  220. package/build/src/library/repo.js +2 -2
  221. package/build/src/markdown/lex.js +9 -2
  222. package/build/src/markdown/parse.js +11 -7
  223. package/build/src/markdown/query.js +4 -2
  224. package/build/src/protocol/amend.js +3 -1
  225. package/build/src/protocol/attempt.d.ts +7 -3
  226. package/build/src/protocol/attempt.js +9 -5
  227. package/build/src/protocol/audit.d.ts +1 -1
  228. package/build/src/protocol/audit.js +4 -14
  229. package/build/src/protocol/bind.d.ts +19 -6
  230. package/build/src/protocol/bind.js +28 -13
  231. package/build/src/protocol/completion.d.ts +0 -1
  232. package/build/src/protocol/completion.js +1 -3
  233. package/build/src/protocol/deliver.d.ts +1 -3
  234. package/build/src/protocol/deliver.js +15 -39
  235. package/build/src/protocol/intent.d.ts +1 -0
  236. package/build/src/protocol/intent.js +9 -7
  237. package/build/src/protocol/operations.d.ts +3 -7
  238. package/build/src/protocol/operations.js +5 -3
  239. package/build/src/protocol/placement.d.ts +0 -1
  240. package/build/src/protocol/placement.js +6 -6
  241. package/build/src/protocol/read/observation.d.ts +11 -0
  242. package/build/src/protocol/read/observation.js +37 -0
  243. package/build/src/protocol/read/status.d.ts +23 -12
  244. package/build/src/protocol/read/status.js +90 -72
  245. package/build/src/protocol/reconcile.d.ts +1 -0
  246. package/build/src/protocol/reconcile.js +2 -3
  247. package/build/src/protocol/reintegrate.js +1 -1
  248. package/build/src/protocol/review.d.ts +0 -1
  249. package/build/src/protocol/review.js +15 -22
  250. package/build/src/protocol/run.d.ts +1 -0
  251. package/build/src/protocol/run.js +4 -5
  252. package/build/src/runtime/proc/launch.d.ts +33 -0
  253. package/build/src/runtime/proc/launch.js +98 -0
  254. package/build/src/runtime/proc/line-rpc.js +9 -3
  255. package/build/src/runtime/proc/run.d.ts +12 -6
  256. package/build/src/runtime/proc/run.js +78 -21
  257. package/build/src/runtime/proc/stdio.js +7 -10
  258. package/build/src/settings.js +6 -2
  259. package/build/src/settlement/holder.js +13 -10
  260. package/build/src/settlement/settle.js +39 -6
  261. package/build/src/task/board.js +12 -6
  262. package/build/src/task/compose.js +85 -22
  263. package/build/src/task/context.js +21 -8
  264. package/build/src/task/document.js +46 -10
  265. package/build/src/task/identity.js +5 -2
  266. package/build/src/task/index.d.ts +0 -1
  267. package/build/src/task/index.js +119 -34
  268. package/build/src/task/input.js +29 -7
  269. package/build/src/task/mutation.js +61 -19
  270. package/build/src/task/operations.d.ts +6 -1
  271. package/build/src/task/operations.js +83 -29
  272. package/build/src/task/query.js +55 -26
  273. package/build/src/task/store.d.ts +3 -1
  274. package/build/src/task/store.js +81 -17
  275. package/build/src/verification/execution.js +14 -11
  276. package/build/src/workspace-place.js +19 -7
  277. package/build/src/world.js +16 -6
  278. package/package.json +8 -3
@@ -3,6 +3,8 @@ import { open } from "node:fs/promises";
3
3
  import { setTimeout as delay } from "node:timers/promises";
4
4
  import { promisify } from "node:util";
5
5
  import crossSpawn from "cross-spawn";
6
+ import { spawnOptionsFor } from "./launch.js";
7
+ export { handoffProcess } from "./launch.js";
6
8
  const TERMINATION_GRACE_MS = 250;
7
9
  const STREAM_TAIL_BYTES = 16 * 1024;
8
10
  function tailCapture(limit) {
@@ -27,9 +29,7 @@ function tailCapture(limit) {
27
29
  result() {
28
30
  const size = Math.min(total, limit);
29
31
  const start = total > limit ? cursor : 0;
30
- const value = start === 0
31
- ? bytes.subarray(0, size)
32
- : Buffer.concat([bytes.subarray(start), bytes.subarray(0, start)]);
32
+ const value = start === 0 ? bytes.subarray(0, size) : Buffer.concat([bytes.subarray(start), bytes.subarray(0, start)]);
33
33
  return { text: value.toString("utf8"), truncated: total > limit };
34
34
  },
35
35
  };
@@ -38,6 +38,9 @@ function ignoreMissingProcess(error) {
38
38
  if (error.code !== "ESRCH")
39
39
  throw error;
40
40
  }
41
+ function detachedExitStatus(code, signal) {
42
+ return code === null ? `signal ${signal ?? "unknown"}` : `exit ${code}`;
43
+ }
41
44
  const WINDOWS_TERMINATION_TIMEOUT_MS = 1_000;
42
45
  const execFileAsync = promisify(execFile);
43
46
  async function terminateWindowsTree(pid) {
@@ -52,8 +55,14 @@ export async function terminateOwnedProcess(child, force = false) {
52
55
  return;
53
56
  let exited = false;
54
57
  const exit = new Promise((resolve) => {
55
- child.once("exit", () => { exited = true; resolve(); });
56
- child.once("close", () => { exited = true; resolve(); });
58
+ child.once("exit", () => {
59
+ exited = true;
60
+ resolve();
61
+ });
62
+ child.once("close", () => {
63
+ exited = true;
64
+ resolve();
65
+ });
57
66
  });
58
67
  if (process.platform === "win32") {
59
68
  await terminateWindowsTree(pid);
@@ -89,16 +98,39 @@ export async function terminateOwnedProcess(child, force = false) {
89
98
  }
90
99
  await exit;
91
100
  }
101
+ async function retainDetachedExitEvidence(log, path, from, code, signal) {
102
+ const status = detachedExitStatus(code, signal);
103
+ const marker = Buffer.from(`[child ${status}]\n`);
104
+ let written = 0;
105
+ while (written < marker.byteLength) {
106
+ const write = await log.write(marker, written, marker.byteLength - written);
107
+ if (write.bytesWritten === 0)
108
+ throw new Error("run log exit marker write made no progress");
109
+ written += write.bytesWritten;
110
+ }
111
+ const evidence = await log.stat();
112
+ if (evidence.size < from)
113
+ throw new Error("run log shrank before exit evidence was retained");
114
+ const referenced = await open(path, "r");
115
+ try {
116
+ const current = await referenced.stat();
117
+ if (current.dev !== evidence.dev || current.ino !== evidence.ino) {
118
+ throw new Error("run log path changed before exit evidence was retained");
119
+ }
120
+ if (current.size < evidence.size)
121
+ throw new Error("run log path size changed before exit evidence was retained");
122
+ }
123
+ finally {
124
+ await referenced.close();
125
+ }
126
+ return { code, signal, log: { path, from, to: evidence.size } };
127
+ }
92
128
  export async function spawnDetachedProcess(input) {
93
129
  const log = await open(input.log, "a");
130
+ let launched = false;
94
131
  try {
95
- const child = spawn(input.argv[0], input.argv.slice(1), {
96
- cwd: input.cwd,
97
- env: input.env,
98
- detached: true,
99
- stdio: ["ignore", log.fd, log.fd],
100
- windowsHide: true,
101
- });
132
+ const from = (await log.stat()).size;
133
+ const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", input, ["ignore", log.fd, log.fd]));
102
134
  await new Promise((resolve, reject) => {
103
135
  child.once("spawn", resolve);
104
136
  child.once("error", reject);
@@ -106,13 +138,43 @@ export async function spawnDetachedProcess(input) {
106
138
  if (child.pid === undefined)
107
139
  throw new Error("detached process spawned without a pid");
108
140
  const pid = child.pid;
141
+ const exited = new Promise((resolve, reject) => {
142
+ child.once("close", (code, signal) => {
143
+ void (async () => {
144
+ const status = detachedExitStatus(code, signal);
145
+ let failure;
146
+ let result;
147
+ try {
148
+ result = await retainDetachedExitEvidence(log, input.log, from, code, signal);
149
+ }
150
+ catch (error) {
151
+ failure = error;
152
+ }
153
+ try {
154
+ await log.close();
155
+ }
156
+ catch (error) {
157
+ failure ??= error;
158
+ }
159
+ if (failure !== undefined) {
160
+ throw new Error(`pre-admission ${status}: run-log evidence unavailable: ${failure instanceof Error ? failure.message : String(failure)}`);
161
+ }
162
+ resolve(result);
163
+ })().catch(reject);
164
+ });
165
+ });
166
+ void exited.catch(() => undefined);
109
167
  let state = "active";
110
168
  let termination;
111
- const invalidate = () => { state = "inert"; };
169
+ const invalidate = () => {
170
+ state = "inert";
171
+ };
112
172
  child.once("exit", invalidate);
113
173
  child.once("close", invalidate);
174
+ launched = true;
114
175
  return {
115
176
  pid,
177
+ exited,
116
178
  terminate(force = false) {
117
179
  if (state === "inert")
118
180
  return Promise.resolve();
@@ -131,7 +193,8 @@ export async function spawnDetachedProcess(input) {
131
193
  };
132
194
  }
133
195
  finally {
134
- await log.close();
196
+ if (!launched)
197
+ await log.close();
135
198
  }
136
199
  }
137
200
  function terminalOutcome(code, stdout, stderr, consuming) {
@@ -146,13 +209,7 @@ function terminalOutcome(code, stdout, stderr, consuming) {
146
209
  async function executeProcess(input, timeoutMs, consumeStdout, spawnProcess = spawn) {
147
210
  if (input.signal?.aborted === true)
148
211
  return { outcome: { kind: "cancelled" }, pid: null };
149
- const child = spawnProcess(input.argv[0], input.argv.slice(1), {
150
- cwd: input.cwd,
151
- env: input.env,
152
- detached: true,
153
- stdio: ["ignore", "pipe", "pipe"],
154
- windowsHide: true,
155
- });
212
+ const child = spawnProcess(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", input, ["ignore", "pipe", "pipe"]));
156
213
  const stdout = tailCapture(STREAM_TAIL_BYTES);
157
214
  const stderr = tailCapture(STREAM_TAIL_BYTES);
158
215
  let stop;
@@ -1,20 +1,20 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { setTimeout as delay } from "node:timers/promises";
3
+ import { spawnOptionsFor } from "./launch.js";
3
4
  import { terminateOwnedProcess } from "./run.js";
4
5
  const DEFAULT_DRAIN_TIMEOUT_MS = 1_000;
5
6
  export function spawnStdioProcess(input) {
6
- const child = spawn(input.argv[0], input.argv.slice(1), {
7
+ const child = spawn(input.argv[0], input.argv.slice(1), spawnOptionsFor("retained", {
7
8
  cwd: input.cwd,
8
9
  env: input.env ?? process.env,
9
- detached: true,
10
- stdio: ["pipe", "pipe", "pipe"],
11
- windowsHide: true,
12
- });
10
+ }, ["pipe", "pipe", "pipe"]));
13
11
  let stderr = "";
14
12
  let inputEnded = false;
15
13
  let forced;
16
14
  child.stderr.setEncoding("utf8");
17
- child.stderr.on("data", (chunk) => { stderr = `${stderr}${chunk}`.slice(-4_000); });
15
+ child.stderr.on("data", (chunk) => {
16
+ stderr = `${stderr}${chunk}`.slice(-4_000);
17
+ });
18
18
  const exited = new Promise((resolve) => {
19
19
  child.once("close", (code, signal) => resolve({ code, signal, stderr: stderr.trim() }));
20
20
  child.once("error", (error) => resolve({ code: null, signal: null, stderr: error.message }));
@@ -38,10 +38,7 @@ export function spawnStdioProcess(input) {
38
38
  await forced;
39
39
  return;
40
40
  }
41
- const timedOut = await Promise.race([
42
- exited.then(() => false),
43
- delay(timeoutMs, true, { ref: false }),
44
- ]);
41
+ const timedOut = await Promise.race([exited.then(() => false), delay(timeoutMs, true, { ref: false })]);
45
42
  if (timedOut)
46
43
  await forceClose();
47
44
  };
@@ -59,7 +59,9 @@ function namespaceEntries(name, scope, loaded) {
59
59
  return { entries: [], failure: { scope, diagnostic: `${name} must be an object of named values` } };
60
60
  }
61
61
  return {
62
- entries: Object.keys(value).sort().map((entryName) => ({
62
+ entries: Object.keys(value)
63
+ .sort()
64
+ .map((entryName) => ({
63
65
  name: entryName,
64
66
  value: value[entryName],
65
67
  source: scope,
@@ -110,5 +112,7 @@ export async function settings(input = {}) {
110
112
  export async function projectSettings(root) {
111
113
  if (typeof root !== "string" || root.trim().length === 0)
112
114
  throw new TypeError("settings root must be a nonblank string");
113
- return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), { state: { kind: "absent" } });
115
+ return settingsFromScopes(await readScope(join(resolve(root), ".keiyaku", "settings.json")), {
116
+ state: { kind: "absent" },
117
+ });
114
118
  }
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { AuthorityCorruptionError } from "../core/facts/errors.js";
3
3
  import { contractId } from "../core/facts/types.js";
4
- import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit, } from "../git/repository.js";
4
+ import { GIT_REF, updateGitTree, updateRefsAtomically, writeBlob, writeCommit } from "../git/repository.js";
5
5
  import { withGitTargetedReadObservation, } from "../git/read-observation.js";
6
6
  import { parseTaskId } from "../task/identity.js";
7
7
  import { acquireTaskSettlementFence } from "./fence.js";
@@ -107,7 +107,9 @@ async function admitWithTaskHolderFence(repository, taskId, action) {
107
107
  try {
108
108
  held.close();
109
109
  }
110
- catch { /* The operation failure remains decisive before admission. */ }
110
+ catch {
111
+ /* The operation failure remains decisive before admission. */
112
+ }
111
113
  throw error;
112
114
  }
113
115
  return finishTaskHolderAdmission(taskId, result, () => held.close());
@@ -117,9 +119,7 @@ export function claimTaskHolderWithFence(repository, taskId, action) {
117
119
  }
118
120
  export async function releaseTaskHolder(channel, observation, owner) {
119
121
  const current = (await readTaskHolderProjectionFromDecision(channel, observation)).get(owner) ?? null;
120
- return current === null || current.disposition !== "held"
121
- ? null
122
- : update({ ...current, disposition: "released" });
122
+ return current === null || current.disposition !== "held" ? null : update({ ...current, disposition: "released" });
123
123
  }
124
124
  /** Publish the released holder against the same frozen observation that decided the release. */
125
125
  export async function publishTaskHolderRelease(repository, channel, observation, owner) {
@@ -133,16 +133,20 @@ export async function publishTaskHolderRelease(repository, channel, observation,
133
133
  parent: observation.admission.snapshot.commit,
134
134
  message: `release held Task completion: ${owner}`,
135
135
  });
136
- const publication = await updateRefsAtomically(repository, [{
136
+ const publication = await updateRefsAtomically(repository, [
137
+ {
137
138
  ref: GIT_REF,
138
139
  newOid: commit,
139
140
  expectedOid: observation.admission.snapshot.commit,
140
- }]);
141
+ },
142
+ ]);
141
143
  if (publication.kind === "published")
142
144
  return { kind: "released" };
143
145
  return {
144
146
  kind: "non-published",
145
- diagnostic: publication.kind === "non-published" ? detail(publication.error) : "Task holder release publication state is unknown",
147
+ diagnostic: publication.kind === "non-published"
148
+ ? detail(publication.error)
149
+ : "Task holder release publication state is unknown",
146
150
  };
147
151
  }
148
152
  export async function readTaskHolderProjectionFromDecision(channel, observation) {
@@ -179,8 +183,7 @@ export async function releaseTaskHolderWithFence(repository, channel, owner, act
179
183
  : admitWithTaskHolderFence(repository, taskId, action);
180
184
  }
181
185
  export async function readTaskHoldersAt(observation) {
182
- const entries = [...observation.snapshot.paths]
183
- .filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
186
+ const entries = [...observation.snapshot.paths].filter(([path, entry]) => path.startsWith(HOLDER_PREFIX) && entry.type === "blob");
184
187
  const blobs = await observation.readBlobs(entries.map(([, entry]) => entry.oid));
185
188
  const holders = [];
186
189
  const seen = new Set();
@@ -25,7 +25,12 @@ async function settleTasks(input) {
25
25
  observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
26
26
  }
27
27
  catch (error) {
28
- lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
28
+ lags.push({
29
+ kind: "settlement-failed",
30
+ surface: "task-holder",
31
+ contractId: candidate.id,
32
+ diagnostic: diagnostic(error),
33
+ });
29
34
  return;
30
35
  }
31
36
  const state = observation.journals.get(candidate.id)?.state ?? null;
@@ -36,7 +41,12 @@ async function settleTasks(input) {
36
41
  holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
37
42
  }
38
43
  catch (error) {
39
- lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
44
+ lags.push({
45
+ kind: "settlement-failed",
46
+ surface: "task-holder",
47
+ contractId: candidate.id,
48
+ diagnostic: diagnostic(error),
49
+ });
40
50
  return;
41
51
  }
42
52
  if (holder === null || holder.disposition !== "held")
@@ -47,13 +57,25 @@ async function settleTasks(input) {
47
57
  result = await settleTask(world, taskId);
48
58
  }
49
59
  catch (error) {
50
- lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
60
+ lags.push({
61
+ kind: "settlement-failed",
62
+ surface: "task",
63
+ contractId: candidate.id,
64
+ taskId,
65
+ diagnostic: diagnostic(error),
66
+ });
51
67
  return;
52
68
  }
53
69
  if (result.kind === "changed")
54
70
  actions.push({ kind: "task", taskId, action: "done" });
55
71
  else if (result.kind !== "unchanged") {
56
- lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: taskFailure(result) });
72
+ lags.push({
73
+ kind: "settlement-failed",
74
+ surface: "task",
75
+ contractId: candidate.id,
76
+ taskId,
77
+ diagnostic: taskFailure(result),
78
+ });
57
79
  return;
58
80
  }
59
81
  try {
@@ -69,7 +91,13 @@ async function settleTasks(input) {
69
91
  }
70
92
  }
71
93
  catch (error) {
72
- lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
94
+ lags.push({
95
+ kind: "settlement-failed",
96
+ surface: "task-holder",
97
+ contractId: candidate.id,
98
+ taskId,
99
+ diagnostic: diagnostic(error),
100
+ });
73
101
  }
74
102
  }
75
103
  async function settleNamespace(state, effects, actions, lags) {
@@ -111,7 +139,12 @@ async function settleObserved(input) {
111
139
  });
112
140
  }
113
141
  catch (error) {
114
- lags.push({ kind: "settlement-failed", surface: "task", contractId: input.state.id, diagnostic: diagnostic(error) });
142
+ lags.push({
143
+ kind: "settlement-failed",
144
+ surface: "task",
145
+ contractId: input.state.id,
146
+ diagnostic: diagnostic(error),
147
+ });
115
148
  }
116
149
  }
117
150
  await settleNamespace(input.state, input.effects, actions, lags);
@@ -73,12 +73,16 @@ export function projectDetailFacts(board, id, relations) {
73
73
  needs,
74
74
  blockers: needs.filter((need) => !need.released).map(({ released: _released, ...ref }) => ref),
75
75
  blocks: relations.blocks(id),
76
- parent: task.parent === null ? null : taskRef(board, task.parent), children: relations.children(id),
77
- supersedes: task.supersedes.map((target) => taskRef(board, target)), supersededBy: relations.supersededBy(id),
76
+ parent: task.parent === null ? null : taskRef(board, task.parent),
77
+ children: relations.children(id),
78
+ supersedes: task.supersedes.map((target) => taskRef(board, target)),
79
+ supersededBy: relations.supersededBy(id),
78
80
  related: relations.related(id),
79
81
  };
80
82
  }
81
- function inScope(task, scope) { return scope === null || sameNamespace(parseTaskId(task.id).namespace, scope); }
83
+ function inScope(task, scope) {
84
+ return scope === null || sameNamespace(parseTaskId(task.id).namespace, scope);
85
+ }
82
86
  export function projectTaskRow(board, relations, task) {
83
87
  const children = relations.children(task.id);
84
88
  const total = children.length;
@@ -95,8 +99,10 @@ export function projectTaskRow(board, relations, task) {
95
99
  };
96
100
  }
97
101
  export function projectRows(board, relations, scope, selection) {
98
- return [...board.tasks.values()].filter((task) => inScope(task, scope)).filter((task) => selection === "all"
99
- || (selection === "closed") === terminal(task.state)).map((task) => projectTaskRow(board, relations, task))
102
+ return [...board.tasks.values()]
103
+ .filter((task) => inScope(task, scope))
104
+ .filter((task) => selection === "all" || (selection === "closed") === terminal(task.state))
105
+ .map((task) => projectTaskRow(board, relations, task))
100
106
  .sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
101
107
  }
102
108
  export function projectBlocked(board, scope, relations) {
@@ -123,7 +129,7 @@ export function projectTaskBoardObservation(board) {
123
129
  };
124
130
  }
125
131
  function relationIds(task, relation) {
126
- return relation === "parent" ? task.parent === null ? [] : [task.parent] : task[relation];
132
+ return relation === "parent" ? (task.parent === null ? [] : [task.parent]) : task[relation];
127
133
  }
128
134
  export function relationProblem(board, before, after) {
129
135
  for (const relation of ["needs", "parent", "supersedes", "relates"]) {
@@ -4,7 +4,9 @@ import { serializeTaskDocument } from "./document.js";
4
4
  import { allocateLocalId, deriveLocalStem, formatTaskId, parseTaskId, sameNamespace } from "./identity.js";
5
5
  import { authorityPath, readBoard, replaceAuthority, withTaskLocks } from "./store.js";
6
6
  import { advanceTaskTimestamp } from "./operations.js";
7
- function failure(diagnostic) { return { kind: "invalid-composition", diagnostic }; }
7
+ function failure(diagnostic) {
8
+ return { kind: "invalid-composition", diagnostic };
9
+ }
8
10
  function parseNamespace(value) {
9
11
  if (value === "")
10
12
  return [];
@@ -30,14 +32,32 @@ function parseNode(text, depth, index) {
30
32
  const title = values.slice(0, at < 0 ? values.length : at).join(" ");
31
33
  if (title.trim().length === 0)
32
34
  throw new TypeError("compose + node requires a title");
33
- return { index, depth, kind: "new", title, assignments: (at < 0 ? [] : values.slice(at)).map((token) => assignment(token) ?? (() => { throw new TypeError(`invalid assignment token: ${token}`); })()) };
35
+ return {
36
+ index,
37
+ depth,
38
+ kind: "new",
39
+ title,
40
+ assignments: (at < 0 ? [] : values.slice(at)).map((token) => assignment(token) ??
41
+ (() => {
42
+ throw new TypeError(`invalid assignment token: ${token}`);
43
+ })()),
44
+ };
34
45
  }
35
46
  const [rawId, ...rest] = tokens;
36
47
  if (rawId === undefined || !rawId.startsWith("@task/"))
37
48
  throw new TypeError("compose node must begin with + or @task/");
38
49
  const id = `task/${rawId.slice(1).slice(5)}`;
39
50
  parseTaskId(id);
40
- return { index, depth, kind: "existing", id, assignments: rest.map((token) => assignment(token) ?? (() => { throw new TypeError(`invalid assignment token: ${token}`); })()) };
51
+ return {
52
+ index,
53
+ depth,
54
+ kind: "existing",
55
+ id,
56
+ assignments: rest.map((token) => assignment(token) ??
57
+ (() => {
58
+ throw new TypeError(`invalid assignment token: ${token}`);
59
+ })()),
60
+ };
41
61
  }
42
62
  function parseSketch(markdown) {
43
63
  try {
@@ -73,7 +93,10 @@ function parseSketch(markdown) {
73
93
  }
74
94
  current.body.push(text.startsWith("\\") ? text.slice(1) : text);
75
95
  }
76
- const nodes = mutable.map(({ node, body }) => ({ ...node, ...(body.length === 0 ? {} : { body: body.join("\n") }) }));
96
+ const nodes = mutable.map(({ node, body }) => ({
97
+ ...node,
98
+ ...(body.length === 0 ? {} : { body: body.join("\n") }),
99
+ }));
77
100
  return { ...(namespace === undefined ? {} : { namespace }), nodes };
78
101
  }
79
102
  catch (error) {
@@ -83,14 +106,23 @@ function parseSketch(markdown) {
83
106
  function ids(value) {
84
107
  if (value === "")
85
108
  return [];
86
- const parsed = value.split(",").map((raw) => { if (!raw.startsWith("@task/"))
87
- throw new TypeError("relation values must be @TaskId"); const id = raw.slice(1); parseTaskId(id); return id; });
109
+ const parsed = value.split(",").map((raw) => {
110
+ if (!raw.startsWith("@task/"))
111
+ throw new TypeError("relation values must be @TaskId");
112
+ const id = raw.slice(1);
113
+ parseTaskId(id);
114
+ return id;
115
+ });
88
116
  if (new Set(parsed).size !== parsed.length)
89
117
  throw new TypeError("relation values must not contain duplicates");
90
118
  return parsed;
91
119
  }
92
- function scalarId(value) { const values = ids(value); if (values.length > 1)
93
- throw new TypeError("parent accepts at most one TaskId"); return values[0] ?? null; }
120
+ function scalarId(value) {
121
+ const values = ids(value);
122
+ if (values.length > 1)
123
+ throw new TypeError("parent accepts at most one TaskId");
124
+ return values[0] ?? null;
125
+ }
94
126
  function changed(current, value, append) {
95
127
  return append ? [...current, ...value.filter((id) => !current.includes(id))] : value;
96
128
  }
@@ -122,9 +154,19 @@ function currentTimestamp() {
122
154
  }
123
155
  function createdTask(id, title, at, actor) {
124
156
  return {
125
- id, title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "",
157
+ id,
158
+ title,
159
+ state: "open",
160
+ priority: 2,
161
+ needs: [],
162
+ parent: null,
163
+ supersedes: [],
164
+ relates: [],
165
+ note: "",
126
166
  ...(actor === undefined ? {} : { createdBy: actor }),
127
- createdAt: at, updatedAt: at, body: "",
167
+ createdAt: at,
168
+ updatedAt: at,
169
+ body: "",
128
170
  };
129
171
  }
130
172
  function plan(sketch, board, defaultNamespace, at, actor) {
@@ -148,7 +190,7 @@ function plan(sketch, board, defaultNamespace, at, actor) {
148
190
  const byDepth = [], addressed = new Set();
149
191
  const planned = [];
150
192
  for (const node of sketch.nodes) {
151
- const before = node.kind === "new" ? null : board.tasks.get(node.id) ?? null;
193
+ const before = node.kind === "new" ? null : (board.tasks.get(node.id) ?? null);
152
194
  let current = node.kind === "new" ? allocations.get(node.index) : all.get(node.id);
153
195
  if (current === undefined)
154
196
  throw new TypeError(`compose task does not exist: ${node.id}`);
@@ -164,8 +206,8 @@ function plan(sketch, board, defaultNamespace, at, actor) {
164
206
  current = { ...current, parent };
165
207
  }
166
208
  current = applyAssignments(current, node.assignments, node.body);
167
- if (before !== null
168
- && !Buffer.from(serializeTaskDocument(current)).equals(Buffer.from(serializeTaskDocument(before)))) {
209
+ if (before !== null &&
210
+ !Buffer.from(serializeTaskDocument(current)).equals(Buffer.from(serializeTaskDocument(before)))) {
169
211
  current = {
170
212
  ...current,
171
213
  updatedAt: advanceTaskTimestamp(before.updatedAt, at),
@@ -187,15 +229,22 @@ function plan(sketch, board, defaultNamespace, at, actor) {
187
229
  return failure(error instanceof Error ? error.message : String(error));
188
230
  }
189
231
  }
190
- function ref(id) { return `@${id}`; }
191
- function bodyLines(body) { return body.split("\n").map((line) => /^[\\]|^\+ |^@task\//u.test(line) ? `\\${line}` : line); }
232
+ function ref(id) {
233
+ return `@${id}`;
234
+ }
235
+ function bodyLines(body) {
236
+ return body.split("\n").map((line) => (/^[\\]|^\+ |^@task\//u.test(line) ? `\\${line}` : line));
237
+ }
192
238
  function draft(namespace, remaining) {
193
239
  const lines = [`ns=${namespace.join("/")}`];
194
240
  for (const item of remaining) {
195
241
  const task = item.after;
196
242
  const assignments = [
197
- `pri=${task.priority}`, `needs=${task.needs.map(ref).join(",")}`, `parent=${task.parent === null ? "" : ref(task.parent)}`,
198
- `supersedes=${task.supersedes.map(ref).join(",")}`, `relates=${task.relates.map(ref).join(",")}`,
243
+ `pri=${task.priority}`,
244
+ `needs=${task.needs.map(ref).join(",")}`,
245
+ `parent=${task.parent === null ? "" : ref(task.parent)}`,
246
+ `supersedes=${task.supersedes.map(ref).join(",")}`,
247
+ `relates=${task.relates.map(ref).join(",")}`,
199
248
  ];
200
249
  lines.push(`${item.node.kind === "new" ? `+ ${task.title}` : ref(task.id)} ${assignments.join(" ")}${task.body === "" ? " body=" : ""}`);
201
250
  if (task.body !== "")
@@ -224,19 +273,33 @@ export async function composeTasks(world, markdown, signal, actor, defaultNamesp
224
273
  for (let index = 0; index < queue.length; index += 1) {
225
274
  signal?.throwIfAborted();
226
275
  const item = queue[index], path = authorityPath(world, item.after.id);
227
- const beforeBytes = item.before === null ? null : fresh.bytes.get(item.after.id) ?? null;
276
+ const beforeBytes = item.before === null ? null : (fresh.bytes.get(item.after.id) ?? null);
228
277
  const afterBytes = serializeTaskDocument(item.after);
229
278
  const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
230
279
  if (before === after)
231
280
  continue;
232
- if (await replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
281
+ if ((await replaceAuthority({ path, expected: beforeBytes, next: afterBytes })) !== "replaced")
233
282
  return {
234
- kind: "incomplete", documentChanges: changes, stopped: { kind: "retry", reason: "concurrent-modification" }, draft: draft(namespace, queue.slice(index)),
283
+ kind: "incomplete",
284
+ documentChanges: changes,
285
+ stopped: { kind: "retry", reason: "concurrent-modification" },
286
+ draft: draft(namespace, queue.slice(index)),
235
287
  };
236
288
  const label = `${item.after.id}.md`;
237
- changes.push({ taskId: item.after.id, kind: item.before === null ? "created" : "updated", documentDiff: documentDiff(label, label, before, after) });
289
+ changes.push({
290
+ taskId: item.after.id,
291
+ kind: item.before === null ? "created" : "updated",
292
+ documentDiff: documentDiff(label, label, before, after),
293
+ });
238
294
  }
239
295
  return { kind: "accepted", documentChanges: changes };
240
296
  });
241
- return result === "busy" ? { kind: "incomplete", documentChanges: [], stopped: { kind: "retry", reason: "busy" }, draft: draft(namespace, ordered) } : result;
297
+ return result === "busy"
298
+ ? {
299
+ kind: "incomplete",
300
+ documentChanges: [],
301
+ stopped: { kind: "retry", reason: "busy" },
302
+ draft: draft(namespace, ordered),
303
+ }
304
+ : result;
242
305
  }
@@ -2,13 +2,23 @@ import { lstat, mkdir, readFile } from "node:fs/promises";
2
2
  import { dirname, join, resolve } from "node:path";
3
3
  import { repairDerivedFile, replaceFileDurably } from "../coordination/durable-file.js";
4
4
  import { normalizeIdentityStem } from "../identity/normalize.js";
5
- function directory(root) { return join(root, ".keiyaku", "namespace"); }
6
- function currentPath(root) { return join(directory(root), "current"); }
7
- function ignorePath(root) { return join(directory(root), ".gitignore"); }
5
+ function directory(root) {
6
+ return join(root, ".keiyaku", "namespace");
7
+ }
8
+ function currentPath(root) {
9
+ return join(directory(root), "current");
10
+ }
11
+ function ignorePath(root) {
12
+ return join(directory(root), ".gitignore");
13
+ }
8
14
  function validNamespaceSegments(value) {
9
- return Array.isArray(value) && value.every((segment) => typeof segment === "string"
10
- && segment.length > 0 && normalizeIdentityStem({ source: segment }) === segment
11
- && !segment.includes("/") && segment !== "." && segment !== "..");
15
+ return (Array.isArray(value) &&
16
+ value.every((segment) => typeof segment === "string" &&
17
+ segment.length > 0 &&
18
+ normalizeIdentityStem({ source: segment }) === segment &&
19
+ !segment.includes("/") &&
20
+ segment !== "." &&
21
+ segment !== ".."));
12
22
  }
13
23
  async function readAt(root) {
14
24
  let bytes;
@@ -66,8 +76,11 @@ export async function repairNamespaceContext(root, segments) {
66
76
  }
67
77
  export async function resolveTaskNamespaceContext(input) {
68
78
  const selected = await readNamespaceContext(input);
69
- return selected === "absent" ? [] : typeof selected === "object" && "kind" in selected
70
- ? { kind: "invalid-namespace-context", path: selected.path } : selected;
79
+ return selected === "absent"
80
+ ? []
81
+ : typeof selected === "object" && "kind" in selected
82
+ ? { kind: "invalid-namespace-context", path: selected.path }
83
+ : selected;
71
84
  }
72
85
  export async function writeTaskNamespaceContext(directory, value) {
73
86
  await installNamespaceContext(directory, value);