@astrosheep/keiyaku 4.5.11 → 4.5.13

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 (272) hide show
  1. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +135 -377
  2. package/build/integrations/pi/keiyaku.ts +11 -116
  3. package/build/src/akuma/akuma-errors.d.ts +1 -0
  4. package/build/src/akuma/akuma-errors.js +1 -0
  5. package/build/src/akuma/akuma-handle.d.ts +1 -0
  6. package/build/src/akuma/akuma-handle.js +6 -7
  7. package/build/src/akuma/akuma-product.d.ts +6 -8
  8. package/build/src/akuma/akuma-product.js +141 -57
  9. package/build/src/akuma/akuma.d.ts +5 -7
  10. package/build/src/akuma/akuma.js +1 -22
  11. package/build/src/akuma/archetype.d.ts +6 -5
  12. package/build/src/akuma/archetype.js +137 -38
  13. package/build/src/akuma/body.d.ts +5 -5
  14. package/build/src/akuma/body.js +223 -63
  15. package/build/src/akuma/call-input.d.ts +4 -0
  16. package/build/src/akuma/call-input.js +21 -0
  17. package/build/src/akuma/call-request.d.ts +5 -8
  18. package/build/src/akuma/call-request.js +28 -73
  19. package/build/src/akuma/fleet-execution.d.ts +26 -0
  20. package/build/src/akuma/fleet-execution.js +109 -0
  21. package/build/src/{library/fleet-result.d.ts → akuma/fleet-observation.d.ts} +13 -57
  22. package/build/src/{library/fleet-result.js → akuma/fleet-observation.js} +4 -25
  23. package/build/src/akuma/fleet-request.d.ts +122 -0
  24. package/build/src/akuma/fleet-request.js +168 -0
  25. package/build/src/akuma/heart/facts.d.ts +23 -0
  26. package/build/src/akuma/heart/facts.js +7 -0
  27. package/build/src/akuma/heart/index.d.ts +43 -4
  28. package/build/src/akuma/heart/index.js +107 -7
  29. package/build/src/akuma/heart/request-authority.d.ts +2 -0
  30. package/build/src/akuma/heart/request-authority.js +28 -3
  31. package/build/src/akuma/heart/request-rows.d.ts +2 -0
  32. package/build/src/akuma/heart/request-rows.js +24 -5
  33. package/build/src/akuma/heart/rows.d.ts +4 -1
  34. package/build/src/akuma/heart/rows.js +46 -19
  35. package/build/src/akuma/heart/schema.d.ts +1 -1
  36. package/build/src/akuma/heart/schema.js +28 -6
  37. package/build/src/akuma/heart/storage.d.ts +1 -1
  38. package/build/src/akuma/heart/tells.d.ts +29 -0
  39. package/build/src/akuma/heart/tells.js +136 -5
  40. package/build/src/akuma/heart/timeline.js +9 -3
  41. package/build/src/akuma/projection-read.js +36 -3
  42. package/build/src/akuma/projection.js +3 -4
  43. package/build/src/akuma/provider.d.ts +4 -0
  44. package/build/src/akuma/provider.js +56 -3
  45. package/build/src/akuma/providers/claude/index.js +3 -0
  46. package/build/src/akuma/providers/codex-app-server/index.js +14 -12
  47. package/build/src/akuma/providers/pi/index.js +7 -1
  48. package/build/src/akuma/publication.js +65 -49
  49. package/build/src/akuma/request-lifecycle.d.ts +5 -4
  50. package/build/src/akuma/request-lifecycle.js +29 -16
  51. package/build/src/akuma/request-rendezvous.d.ts +15 -12
  52. package/build/src/akuma/request-rendezvous.js +80 -51
  53. package/build/src/akuma/request-serve.d.ts +1 -2
  54. package/build/src/akuma/request-serve.js +94 -39
  55. package/build/src/akuma/request-wire.d.ts +57 -26
  56. package/build/src/akuma/request-wire.js +64 -25
  57. package/build/src/akuma/turn-drive.d.ts +5 -4
  58. package/build/src/akuma/turn-drive.js +27 -11
  59. package/build/src/akuma-body.d.ts +4 -6
  60. package/build/src/akuma-body.js +19 -17
  61. package/build/src/bounded-list.d.ts +8 -0
  62. package/build/src/bounded-list.js +14 -0
  63. package/build/src/cli/accepted.d.ts +1 -1
  64. package/build/src/cli/accepted.js +1 -0
  65. package/build/src/cli/commands/akuma-invoke.js +1 -25
  66. package/build/src/cli/commands/contract-help.d.ts +5 -2
  67. package/build/src/cli/commands/contract-help.js +62 -12
  68. package/build/src/cli/commands/contract-invoke.d.ts +5 -0
  69. package/build/src/cli/commands/contract-invoke.js +26 -1
  70. package/build/src/cli/commands/contract.js +50 -18
  71. package/build/src/cli/commands/task-invoke.js +10 -2
  72. package/build/src/cli/commands/task-query.js +3 -4
  73. package/build/src/cli/coordinates.d.ts +13 -0
  74. package/build/src/cli/coordinates.js +38 -24
  75. package/build/src/cli/draft.d.ts +1 -1
  76. package/build/src/cli/invoke.js +14 -1
  77. package/build/src/cli/render/akuma-activity.d.ts +0 -1
  78. package/build/src/cli/render/akuma-activity.js +5 -38
  79. package/build/src/cli/render/akuma.js +4 -1
  80. package/build/src/cli/render/audit.js +4 -1
  81. package/build/src/cli/render/catalog.js +13 -39
  82. package/build/src/cli/render/contract.js +4 -14
  83. package/build/src/cli/render/kanshi-akuma.js +3 -8
  84. package/build/src/cli/render/kanshi.js +8 -34
  85. package/build/src/cli/render/nuke.js +7 -3
  86. package/build/src/cli/render/receipt.d.ts +1 -0
  87. package/build/src/cli/render/receipt.js +8 -0
  88. package/build/src/cli/render/refusal.js +4 -3
  89. package/build/src/cli/render/task.js +4 -37
  90. package/build/src/cli/render/terminal.d.ts +2 -6
  91. package/build/src/cli/render/terminal.js +4 -9
  92. package/build/src/cli/result.d.ts +2 -1
  93. package/build/src/contract-guidance.js +13 -3
  94. package/build/src/contract-worktree.d.ts +1 -0
  95. package/build/src/contract-worktree.js +26 -2
  96. package/build/src/core/facts/codec.d.ts +2 -1
  97. package/build/src/core/facts/codec.js +3 -0
  98. package/build/src/core/facts/gate.d.ts +2 -1
  99. package/build/src/core/facts/gate.js +63 -0
  100. package/build/src/core/verbs/abandon.d.ts +2 -1
  101. package/build/src/core/verbs/abandon.js +16 -0
  102. package/build/src/core/verbs/amend.d.ts +2 -1
  103. package/build/src/core/verbs/amend.js +20 -0
  104. package/build/src/core/verbs/arc.d.ts +2 -1
  105. package/build/src/core/verbs/arc.js +16 -0
  106. package/build/src/core/verbs/attestation.d.ts +2 -1
  107. package/build/src/core/verbs/attestation.js +16 -0
  108. package/build/src/core/verbs/bind.d.ts +2 -1
  109. package/build/src/core/verbs/bind.js +16 -0
  110. package/build/src/core/verbs/deliver.d.ts +2 -1
  111. package/build/src/core/verbs/deliver.js +16 -0
  112. package/build/src/core/verbs/placement.d.ts +2 -1
  113. package/build/src/core/verbs/placement.js +58 -1
  114. package/build/src/dispatch/association.d.ts +13 -0
  115. package/build/src/dispatch/association.js +20 -0
  116. package/build/src/dispatch/index.d.ts +6 -0
  117. package/build/src/dispatch/index.js +27 -3
  118. package/build/src/git/hooks.d.ts +9 -1
  119. package/build/src/git/hooks.js +79 -1
  120. package/build/src/git/nuke.d.ts +2 -1
  121. package/build/src/git/nuke.js +6 -11
  122. package/build/src/git/private-state-seat.d.ts +26 -1
  123. package/build/src/git/private-state-seat.js +78 -13
  124. package/build/src/git/process.d.ts +2 -0
  125. package/build/src/git/reconcile.d.ts +1 -2
  126. package/build/src/git/reconcile.js +0 -6
  127. package/build/src/git/repository.d.ts +0 -2
  128. package/build/src/git/repository.js +27 -25
  129. package/build/src/git/result-codec.d.ts +29 -0
  130. package/build/src/git/result-codec.js +306 -0
  131. package/build/src/git/target-placement.js +2 -8
  132. package/build/src/git/workspace.d.ts +2 -0
  133. package/build/src/git/workspace.js +4 -0
  134. package/build/src/index.d.ts +1 -0
  135. package/build/src/kanshi/fleet.js +1 -1
  136. package/build/src/kanshi/read.js +55 -28
  137. package/build/src/kanshi/report.d.ts +4 -3
  138. package/build/src/kanshi/select.js +14 -1
  139. package/build/src/library/address.d.ts +1 -0
  140. package/build/src/library/address.js +4 -7
  141. package/build/src/library/akuma-creation.d.ts +5 -3
  142. package/build/src/library/akuma-creation.js +90 -33
  143. package/build/src/library/audit.js +2 -1
  144. package/build/src/library/catalog.d.ts +10 -2
  145. package/build/src/library/catalog.js +65 -25
  146. package/build/src/library/composition.d.ts +1 -0
  147. package/build/src/library/composition.js +1 -0
  148. package/build/src/library/configuration.d.ts +4 -21
  149. package/build/src/library/configuration.js +2 -158
  150. package/build/src/library/continuation.d.ts +20 -1
  151. package/build/src/library/continuation.js +36 -0
  152. package/build/src/library/contract-bind.d.ts +1 -0
  153. package/build/src/library/contract-bind.js +16 -5
  154. package/build/src/library/contract-forwarding.d.ts +2 -2
  155. package/build/src/library/contract-forwarding.js +18 -4
  156. package/build/src/library/contract-handle.js +4 -3
  157. package/build/src/library/contract-operations.d.ts +5 -9
  158. package/build/src/library/contract-operations.js +34 -47
  159. package/build/src/library/contract.d.ts +11 -2
  160. package/build/src/library/contract.js +21 -1
  161. package/build/src/library/delivery.d.ts +4 -1
  162. package/build/src/library/delivery.js +47 -0
  163. package/build/src/library/fleet.d.ts +4 -136
  164. package/build/src/library/fleet.js +31 -315
  165. package/build/src/library/keiyaku.d.ts +7 -3
  166. package/build/src/library/keiyaku.js +1 -0
  167. package/build/src/library/mutation.d.ts +431 -3
  168. package/build/src/library/mutation.js +192 -19
  169. package/build/src/library/nuke.d.ts +7 -1
  170. package/build/src/library/nuke.js +18 -4
  171. package/build/src/library/reconcile.d.ts +14 -0
  172. package/build/src/library/reconcile.js +22 -8
  173. package/build/src/library/refusal.d.ts +9 -1
  174. package/build/src/library/refusal.js +76 -0
  175. package/build/src/library/region.d.ts +0 -8
  176. package/build/src/library/region.js +1 -29
  177. package/build/src/library/result-codec.d.ts +2 -0
  178. package/build/src/library/result-codec.js +12 -0
  179. package/build/src/markdown/lex.js +9 -2
  180. package/build/src/markdown/types.d.ts +2 -1
  181. package/build/src/markdown/types.js +3 -1
  182. package/build/src/plugin/akuma-signals.d.ts +12 -0
  183. package/build/src/plugin/akuma-signals.js +14 -0
  184. package/build/src/plugin/public.d.ts +51 -0
  185. package/build/src/plugin/public.js +1 -0
  186. package/build/src/plugin/runtime.d.ts +14 -0
  187. package/build/src/plugin/runtime.js +357 -0
  188. package/build/src/protocol/amend.js +3 -3
  189. package/build/src/protocol/attempt.d.ts +2 -1
  190. package/build/src/protocol/attempt.js +1 -1
  191. package/build/src/protocol/audit.d.ts +1 -1
  192. package/build/src/protocol/audit.js +1 -1
  193. package/build/src/protocol/bind.d.ts +3 -1
  194. package/build/src/protocol/bind.js +26 -0
  195. package/build/src/protocol/completion.js +0 -1
  196. package/build/src/protocol/deliver.d.ts +1 -1
  197. package/build/src/protocol/deliver.js +136 -57
  198. package/build/src/protocol/intent.d.ts +0 -1
  199. package/build/src/protocol/intent.js +0 -1
  200. package/build/src/protocol/operations.d.ts +9 -2
  201. package/build/src/protocol/operations.js +16 -1
  202. package/build/src/protocol/outcome.d.ts +3 -0
  203. package/build/src/protocol/outcome.js +17 -1
  204. package/build/src/protocol/placement.js +10 -3
  205. package/build/src/protocol/read/status.d.ts +8 -0
  206. package/build/src/protocol/read/status.js +49 -0
  207. package/build/src/protocol/reintegrate.js +10 -3
  208. package/build/src/protocol/result-codec.d.ts +22 -0
  209. package/build/src/protocol/result-codec.js +442 -0
  210. package/build/src/protocol/review.d.ts +1 -1
  211. package/build/src/protocol/review.js +81 -46
  212. package/build/src/protocol/run.d.ts +22 -2
  213. package/build/src/protocol/run.js +119 -29
  214. package/build/src/runtime/proc/lifecycle.js +1 -1
  215. package/build/src/runtime/proc/line-rpc.d.ts +7 -1
  216. package/build/src/runtime/proc/line-rpc.js +49 -9
  217. package/build/src/runtime/proc/run.d.ts +0 -1
  218. package/build/src/runtime/proc/run.js +1 -11
  219. package/build/src/runtime/proc/termination.d.ts +1 -0
  220. package/build/src/runtime/proc/termination.js +63 -18
  221. package/build/src/runtime/proc/windows-launch.exe +0 -0
  222. package/build/src/settings.d.ts +10 -0
  223. package/build/src/settings.js +88 -2
  224. package/build/src/settlement/result-codec.d.ts +2 -0
  225. package/build/src/settlement/result-codec.js +56 -0
  226. package/build/src/settlement/settle.d.ts +5 -12
  227. package/build/src/settlement/settle.js +60 -55
  228. package/build/src/task/board.d.ts +3 -0
  229. package/build/src/task/board.js +22 -4
  230. package/build/src/task/catalog.d.ts +13 -2
  231. package/build/src/task/catalog.js +59 -5
  232. package/build/src/task/compose.d.ts +4 -2
  233. package/build/src/task/compose.js +56 -30
  234. package/build/src/task/created-observation.d.ts +39 -0
  235. package/build/src/task/created-observation.js +26 -0
  236. package/build/src/task/identity.d.ts +6 -0
  237. package/build/src/task/identity.js +48 -7
  238. package/build/src/task/index.d.ts +4 -3
  239. package/build/src/task/index.js +19 -16
  240. package/build/src/task/input.d.ts +3 -1
  241. package/build/src/task/input.js +7 -4
  242. package/build/src/task/lifecycle-operations.d.ts +6 -0
  243. package/build/src/task/lifecycle-operations.js +116 -0
  244. package/build/src/task/mutation-result.d.ts +762 -20
  245. package/build/src/task/mutation-result.js +34 -9
  246. package/build/src/task/mutation.d.ts +4 -9
  247. package/build/src/task/mutation.js +17 -52
  248. package/build/src/task/operation-types.d.ts +114 -0
  249. package/build/src/task/operation-types.js +1 -0
  250. package/build/src/task/operations.d.ts +6 -113
  251. package/build/src/task/operations.js +75 -117
  252. package/build/src/task/query.d.ts +8 -11
  253. package/build/src/task/query.js +7 -14
  254. package/build/src/task/store.d.ts +11 -1
  255. package/build/src/task/store.js +169 -55
  256. package/build/src/task/view.d.ts +4 -0
  257. package/build/src/task/view.js +7 -0
  258. package/build/src/verification/declaration.d.ts +1 -0
  259. package/build/src/verification/declaration.js +18 -1
  260. package/build/src/verification/execution.d.ts +0 -1
  261. package/build/src/verification/execution.js +5 -5
  262. package/build/src/world.d.ts +0 -2
  263. package/build/src/world.js +3 -7
  264. package/package.json +17 -5
  265. package/build/src/cli/square-edge.d.ts +0 -7
  266. package/build/src/cli/square-edge.js +0 -159
  267. package/build/src/git/ref-migration.d.ts +0 -22
  268. package/build/src/git/ref-migration.js +0 -55
  269. package/build/src/library/contract-forwarding-reconciliation-result.d.ts +0 -102
  270. package/build/src/library/contract-forwarding-reconciliation-result.js +0 -92
  271. package/build/src/library/contract-forwarding-result.d.ts +0 -4270
  272. package/build/src/library/contract-forwarding-result.js +0 -521
@@ -3,8 +3,9 @@ import { closeSync, fsyncSync, openSync, renameSync, writeFileSync } from "node:
3
3
  import { lstat, mkdir, readFile, readdir, rmdir, unlink } from "node:fs/promises";
4
4
  import { dirname, relative, resolve, sep } from "node:path";
5
5
  import { acquireSqliteTransactionLock, SqliteTransactionLockError, } from "../coordination/sqlite-transaction-lock.js";
6
+ import { boundedListLimit, projectBoundedList } from "../bounded-list.js";
6
7
  import { parseTaskDocument } from "./document.js";
7
- import { formatTaskId, parseTaskId, taskAuthorityPath } from "./identity.js";
8
+ import { formatTaskId, parseTaskId, physicalTaskSegment, TASK_LOCAL_ID_BYTES, sameNamespace, taskAuthorityPath, } from "./identity.js";
8
9
  export const DEFAULT_TASK_LOCK_TIMEOUT_MS = 3_000;
9
10
  function syncTaskDirectory(path) {
10
11
  if (process.platform === "win32")
@@ -23,22 +24,73 @@ function tasksDirectory(world) {
23
24
  function allocationLockPath(world) {
24
25
  return resolve(world, ".keiyaku", "locks", "task-allocation.sqlite");
25
26
  }
26
- async function authorityFiles(directory) {
27
- const files = [];
28
- let entries;
27
+ function custodyError(path) {
28
+ return new Error(`Task authority custody violation: symlink or non-authority component: ${path}`);
29
+ }
30
+ async function requireDirectory(path, mode) {
29
31
  try {
30
- entries = await readdir(directory, { withFileTypes: true });
32
+ const stat = await lstat(path);
33
+ if (stat.isSymbolicLink() || !stat.isDirectory())
34
+ throw custodyError(path);
35
+ return true;
36
+ }
37
+ catch (error) {
38
+ if (error.code !== "ENOENT")
39
+ throw error;
40
+ if (mode === "read")
41
+ return false;
42
+ try {
43
+ await mkdir(path);
44
+ syncTaskDirectory(dirname(path));
45
+ }
46
+ catch (mkdirError) {
47
+ if (mkdirError.code !== "EEXIST")
48
+ throw mkdirError;
49
+ }
50
+ const stat = await lstat(path);
51
+ if (stat.isSymbolicLink() || !stat.isDirectory())
52
+ throw custodyError(path);
53
+ return true;
54
+ }
55
+ }
56
+ async function resolveAuthority(world, id, mode) {
57
+ const coordinate = parseTaskId(id);
58
+ const root = tasksDirectory(world);
59
+ const safePath = taskAuthorityPath(root, coordinate);
60
+ if (!(await requireDirectory(root, mode)))
61
+ return { path: safePath, exists: false };
62
+ let parent = root;
63
+ for (const segment of coordinate.namespace) {
64
+ parent = resolve(parent, physicalTaskSegment(segment));
65
+ if (!(await requireDirectory(parent, mode)))
66
+ return { path: safePath, exists: false };
67
+ }
68
+ const path = safePath;
69
+ try {
70
+ const stat = await lstat(path);
71
+ if (stat.isSymbolicLink() || !stat.isFile())
72
+ throw custodyError(path);
73
+ return { path, exists: true };
31
74
  }
32
75
  catch (error) {
33
76
  if (error.code === "ENOENT")
34
- return [];
77
+ return { path, exists: false };
35
78
  throw error;
36
79
  }
80
+ }
81
+ async function authorityFiles(directory) {
82
+ if (!(await requireDirectory(directory, "read")))
83
+ return [];
84
+ const files = [];
85
+ const entries = await readdir(directory);
37
86
  for (const entry of entries) {
38
- const path = resolve(directory, entry.name);
39
- if (entry.isDirectory())
87
+ const path = resolve(directory, entry);
88
+ const stat = await lstat(path);
89
+ if (stat.isSymbolicLink())
90
+ throw custodyError(path);
91
+ if (stat.isDirectory())
40
92
  files.push(...(await authorityFiles(path)));
41
- else if (entry.isFile() && entry.name.endsWith(".md"))
93
+ else if (stat.isFile() && entry.endsWith(".md"))
42
94
  files.push(path);
43
95
  }
44
96
  return files.sort((a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)));
@@ -47,38 +99,33 @@ function coordinateFromPath(tasksDirectory, path) {
47
99
  const local = relative(tasksDirectory, path);
48
100
  if (local.startsWith("..") || resolve(tasksDirectory, local) !== path || !local.endsWith(".md"))
49
101
  throw new Error(`invalid Task authority path: ${path}`);
50
- return parseTaskId(`task/${local.slice(0, -3).split(sep).join("/")}`);
51
- }
52
- function ownedTaskId(tasksDirectory, path) {
53
- try {
54
- return formatTaskId(coordinateFromPath(tasksDirectory, path));
55
- }
56
- catch {
57
- return null;
58
- }
102
+ const segments = local.slice(0, -3).split(sep);
103
+ segments.forEach((segment) => physicalTaskSegment(segment));
104
+ return parseTaskId(`task/${segments.join("/")}`);
59
105
  }
60
106
  async function ownedAuthorityCandidates(directory) {
61
107
  const candidates = [];
62
108
  for (const path of await authorityFiles(directory)) {
63
- const id = ownedTaskId(directory, path);
64
- if (id !== null)
65
- candidates.push({ id, path });
109
+ try {
110
+ candidates.push({ id: formatTaskId(coordinateFromPath(directory, path)), path });
111
+ }
112
+ catch {
113
+ // Files outside the Task identity grammar are not owned candidates.
114
+ }
66
115
  }
67
116
  return candidates;
68
117
  }
69
118
  async function removeEmptyTaskDirectories(directory, removeSelf) {
70
- let entries;
71
- try {
72
- entries = await readdir(directory, { withFileTypes: true });
73
- }
74
- catch (error) {
75
- if (error.code === "ENOENT")
76
- return;
77
- throw error;
78
- }
119
+ if (!(await requireDirectory(directory, "read")))
120
+ return;
121
+ const entries = await readdir(directory);
79
122
  for (const entry of entries) {
80
- if (entry.isDirectory())
81
- await removeEmptyTaskDirectories(resolve(directory, entry.name), true);
123
+ const path = resolve(directory, entry);
124
+ const stat = await lstat(path);
125
+ if (stat.isSymbolicLink())
126
+ throw custodyError(path);
127
+ if (stat.isDirectory())
128
+ await removeEmptyTaskDirectories(path, true);
82
129
  }
83
130
  if (!removeSelf)
84
131
  return;
@@ -108,22 +155,42 @@ export async function readBoard(world) {
108
155
  }
109
156
  return { board: { tasks }, bytes };
110
157
  }
158
+ function compareRecentTasks(left, right) {
159
+ const updated = right.updatedAt.localeCompare(left.updatedAt);
160
+ return updated === 0 ? Buffer.compare(Buffer.from(left.id), Buffer.from(right.id)) : updated;
161
+ }
162
+ export async function readRecentTaskDocuments(world, input) {
163
+ const limit = boundedListLimit(input.limit);
164
+ const directory = tasksDirectory(world);
165
+ const tasks = [];
166
+ for (const path of await authorityFiles(directory)) {
167
+ const metadata = await lstat(path);
168
+ if (!metadata.isFile() || metadata.isSymbolicLink())
169
+ throw new Error(`Task authority is not a regular file: ${path}`);
170
+ const coordinate = coordinateFromPath(directory, path);
171
+ if (input.namespace !== undefined && !sameNamespace(coordinate.namespace, input.namespace))
172
+ continue;
173
+ const task = parseTaskDocument(await readFile(path), coordinate);
174
+ if (input.selection === "active" && (task.state === "done" || task.state === "drop"))
175
+ continue;
176
+ tasks.push(task);
177
+ }
178
+ return projectBoundedList(tasks.sort(compareRecentTasks), limit);
179
+ }
180
+ export async function readTaskDocument(world, id) {
181
+ const resolved = await resolveAuthority(world, id, "read");
182
+ return resolved.exists ? parseTaskDocument(await readFile(resolved.path), parseTaskId(id)) : undefined;
183
+ }
111
184
  function equal(left, right) {
112
185
  return left === null || right === null ? left === right : Buffer.from(left).equals(Buffer.from(right));
113
186
  }
114
- async function currentBytes(path) {
115
- try {
116
- return await readFile(path);
117
- }
118
- catch (error) {
119
- if (error.code === "ENOENT")
120
- return null;
121
- throw error;
122
- }
187
+ async function currentBytes(world, id) {
188
+ const resolved = await resolveAuthority(world, id, "read");
189
+ return resolved.exists ? readFile(resolved.path) : null;
123
190
  }
124
191
  export async function replaceAuthority(input) {
125
- const parent = dirname(input.path);
126
- await mkdir(parent, { recursive: true });
192
+ const resolved = await resolveAuthority(input.world, input.id, "write");
193
+ const parent = dirname(resolved.path);
127
194
  const temporary = resolve(parent, `.tmp-${randomBytes(8).toString("hex")}`);
128
195
  let descriptor;
129
196
  try {
@@ -132,9 +199,9 @@ export async function replaceAuthority(input) {
132
199
  fsyncSync(descriptor);
133
200
  closeSync(descriptor);
134
201
  descriptor = undefined;
135
- if (!equal(await currentBytes(input.path), input.expected))
202
+ if (!equal(await currentBytes(input.world, input.id), input.expected))
136
203
  return "concurrent-modification";
137
- renameSync(temporary, input.path);
204
+ renameSync(temporary, resolved.path);
138
205
  syncTaskDirectory(parent);
139
206
  return "replaced";
140
207
  }
@@ -149,9 +216,31 @@ export async function replaceAuthority(input) {
149
216
  }
150
217
  }
151
218
  }
219
+ export async function authorityBytesMatch(world, id, expected) {
220
+ return equal(await currentBytes(world, id), expected);
221
+ }
152
222
  function lockPath(world, id) {
153
223
  const coordinate = parseTaskId(id);
154
- return resolve(world, ".keiyaku", "locks", "task", ...coordinate.namespace, `${coordinate.localId}.sqlite`);
224
+ return resolve(world, ".keiyaku", "locks", "task", ...coordinate.namespace.map((segment) => physicalTaskSegment(segment)), `${physicalTaskSegment(coordinate.localId, TASK_LOCAL_ID_BYTES)}.sqlite`);
225
+ }
226
+ function errorDetail(error) {
227
+ return error instanceof Error ? error.message : String(error);
228
+ }
229
+ function preserveCommittedResult(result, errors) {
230
+ if (typeof result === "object" && result !== null && "kind" in result) {
231
+ const kind = result.kind;
232
+ if (kind === "accepted" || kind === "changed" || kind === "incomplete") {
233
+ const previous = result.cleanup;
234
+ return {
235
+ ...result,
236
+ cleanup: {
237
+ kind: "lock-release-failed",
238
+ diagnostics: [...(previous?.diagnostics ?? []), ...errors.map(errorDetail)],
239
+ },
240
+ };
241
+ }
242
+ }
243
+ throw new AggregateError(errors, "Task lock cleanup failed");
155
244
  }
156
245
  export async function nukeTaskAuthority(world, options) {
157
246
  const directory = tasksDirectory(world);
@@ -162,9 +251,9 @@ export async function nukeTaskAuthority(world, options) {
162
251
  return await withTaskLocks({ world, allocation: false, ids, ...lockOptions }, async () => {
163
252
  for (const candidate of candidates) {
164
253
  try {
165
- const stat = await lstat(candidate.path);
166
- if (stat.isFile() && !stat.isSymbolicLink())
167
- await unlink(candidate.path);
254
+ const resolved = await resolveAuthority(world, candidate.id, "read");
255
+ if (resolved.exists)
256
+ await unlink(resolved.path);
168
257
  }
169
258
  catch (error) {
170
259
  if (error.code !== "ENOENT")
@@ -185,6 +274,8 @@ export async function withTaskLocks(input, action) {
185
274
  .map((id) => lockPath(input.world, id)),
186
275
  ];
187
276
  const held = [];
277
+ let result;
278
+ let failure;
188
279
  try {
189
280
  for (const path of paths)
190
281
  held.push(await acquireSqliteTransactionLock({
@@ -193,17 +284,40 @@ export async function withTaskLocks(input, action) {
193
284
  timeoutMs: input.timeoutMs ?? DEFAULT_TASK_LOCK_TIMEOUT_MS,
194
285
  ...(input.signal === undefined ? {} : { signal: input.signal }),
195
286
  }));
196
- return await action();
287
+ result = await action();
197
288
  }
198
289
  catch (error) {
199
- if (error instanceof SqliteTransactionLockError && error.reason === "timeout")
200
- return "busy";
201
- throw error;
290
+ failure = error;
202
291
  }
203
292
  finally {
204
- for (const lock of held.reverse())
205
- lock.close();
293
+ const cleanupErrors = [];
294
+ for (const lock of held.reverse()) {
295
+ try {
296
+ lock.close();
297
+ }
298
+ catch (error) {
299
+ cleanupErrors.push(error);
300
+ }
301
+ }
302
+ if (cleanupErrors.length > 0) {
303
+ if (failure === undefined && result !== undefined)
304
+ result = preserveCommittedResult(result, cleanupErrors);
305
+ else if (failure === undefined)
306
+ failure = new AggregateError(cleanupErrors, "Task lock cleanup failed");
307
+ else {
308
+ const originalFailure = failure;
309
+ failure = new AggregateError([originalFailure, ...cleanupErrors], errorDetail(originalFailure), {
310
+ cause: originalFailure,
311
+ });
312
+ }
313
+ }
314
+ }
315
+ if (failure !== undefined) {
316
+ if (failure instanceof SqliteTransactionLockError && failure.reason === "timeout")
317
+ return "busy";
318
+ throw failure;
206
319
  }
320
+ return result;
207
321
  }
208
322
  export function authorityPath(world, id) {
209
323
  return taskAuthorityPath(tasksDirectory(world), parseTaskId(id));
@@ -0,0 +1,4 @@
1
+ import type { TaskDocument } from "./document.js";
2
+ import type { TaskView } from "./operation-types.js";
3
+ export declare function taskView(document: TaskDocument): TaskView;
4
+ export declare function advanceTaskTimestamp(previous: string, candidate: string): string;
@@ -0,0 +1,7 @@
1
+ import { parseTaskId } from "./identity.js";
2
+ export function taskView(document) {
3
+ return { ...document, namespace: parseTaskId(document.id).namespace };
4
+ }
5
+ export function advanceTaskTimestamp(previous, candidate) {
6
+ return candidate > previous ? candidate : new Date(Date.parse(previous) + 1).toISOString();
7
+ }
@@ -14,6 +14,7 @@ export type VerificationDeclarationRefusal = Readonly<{
14
14
  kind: "verification-declaration-invalid";
15
15
  contractId?: ContractId;
16
16
  }>;
17
+ export declare function decodeVerificationDeclarationRefusal(value: unknown): VerificationDeclarationRefusal;
17
18
  export type VerificationDeclarationPreparation = Preparation<VerificationDefinition | null, VerificationDeclarationRefusal>;
18
19
  export declare const VERIFIED: Gate;
19
20
  export declare function prepareVerificationDeclaration(input: Readonly<{
@@ -1,4 +1,21 @@
1
- import { gate } from "../core/facts/types.js";
1
+ import { contractId, gate } from "../core/facts/types.js";
2
+ export function decodeVerificationDeclarationRefusal(value) {
3
+ if (value === null || typeof value !== "object" || Array.isArray(value))
4
+ throw new Error("malformed verification declaration refusal");
5
+ const object = value;
6
+ if (object.kind !== "verification-declaration-invalid")
7
+ throw new Error("malformed verification declaration refusal");
8
+ if (Object.keys(object).some((key) => key !== "kind" && key !== "contractId"))
9
+ throw new Error("malformed verification declaration refusal");
10
+ if (object.contractId === undefined)
11
+ return { kind: "verification-declaration-invalid" };
12
+ try {
13
+ return { kind: "verification-declaration-invalid", contractId: contractId(String(object.contractId)) };
14
+ }
15
+ catch {
16
+ throw new Error("malformed verification declaration refusal");
17
+ }
18
+ }
2
19
  export const VERIFIED = gate("verified");
3
20
  export function prepareVerificationDeclaration(input) {
4
21
  if (input.definition !== null || !input.gates.includes(VERIFIED)) {
@@ -41,7 +41,6 @@ export type ExecuteVerificationInput = Readonly<{
41
41
  repository: GitRepository;
42
42
  candidate: SnapshotId;
43
43
  declarations: readonly VerificationDeclaration[];
44
- environment: NodeJS.ProcessEnv;
45
44
  materializeScratchCandidate: (repository: GitRepository, candidate: SnapshotId) => Promise<MaterializedScratchCandidate>;
46
45
  projectSettings: (root: string) => Promise<Settings>;
47
46
  signal?: AbortSignal;
@@ -1,5 +1,4 @@
1
- import { runHookCommands } from "../git/hooks.js";
2
- import { worktreeHooksFrom } from "../library/configuration.js";
1
+ import { runHookCommands, worktreeHooksFrom } from "../git/hooks.js";
3
2
  import { runProcess } from "../runtime/proc/run.js";
4
3
  const SUMMARY_BYTES = 32 * 1024;
5
4
  function argvFor(declaration) {
@@ -58,6 +57,7 @@ async function executeDeclarations(input) {
58
57
  }
59
58
  /** Execute one disposable Verification attempt over the exact integration snapshot. */
60
59
  export async function executeVerification(input) {
60
+ const environment = process.env;
61
61
  let scratch;
62
62
  try {
63
63
  scratch = await input.materializeScratchCandidate(input.repository, input.candidate);
@@ -75,7 +75,7 @@ export async function executeVerification(input) {
75
75
  try {
76
76
  const hooks = worktreeHooksFrom({ settings: await input.projectSettings(scratch.cwd) });
77
77
  destroy = hooks.destroy;
78
- const readiness = await runHookCommands(scratch.cwd, hooks.create, input.signal);
78
+ const readiness = await runHookCommands(scratch.cwd, hooks.create, input.signal, environment);
79
79
  outcome =
80
80
  readiness.kind === "cancelled"
81
81
  ? { kind: "cancelled" }
@@ -84,7 +84,7 @@ export async function executeVerification(input) {
84
84
  : await executeDeclarations({
85
85
  declarations: input.declarations,
86
86
  cwd: scratch.cwd,
87
- environment: input.environment,
87
+ environment,
88
88
  ...(input.signal === undefined ? {} : { signal: input.signal }),
89
89
  });
90
90
  }
@@ -94,7 +94,7 @@ export async function executeVerification(input) {
94
94
  }
95
95
  finally {
96
96
  if (destroy !== undefined) {
97
- const result = await runHookCommands(scratch.cwd, destroy);
97
+ const result = await runHookCommands(scratch.cwd, destroy, undefined, environment);
98
98
  if (result.kind === "cancelled")
99
99
  throw new Error("scratch destroy cancelled without a signal");
100
100
  if (result.kind === "failed")
@@ -11,8 +11,6 @@ export type WorldResolution = Readonly<{
11
11
  candidate: WorldRoot | null;
12
12
  establish: () => Promise<WorldRoot>;
13
13
  }>;
14
- export declare const KEIYAKU_SQUARE_FILE = "KEIYAKU.square";
15
- export declare function keiyakuSquarePath(worldRoot: string): string;
16
14
  export declare class WorldError extends Error {
17
15
  readonly kind: "invalid-world" | "home-world" | "root-world";
18
16
  constructor(kind: "invalid-world" | "home-world" | "root-world", message: string);
@@ -1,11 +1,7 @@
1
- import { mkdir, realpath, stat } from "node:fs/promises";
1
+ import { lstat, mkdir, realpath, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, parse, resolve } from "node:path";
4
4
  const WORLD_BRAND = Symbol("keiyaku.world");
5
- export const KEIYAKU_SQUARE_FILE = "KEIYAKU.square";
6
- export function keiyakuSquarePath(worldRoot) {
7
- return join(worldRoot, ".square", KEIYAKU_SQUARE_FILE);
8
- }
9
5
  export class WorldError extends Error {
10
6
  kind;
11
7
  constructor(kind, message) {
@@ -60,7 +56,7 @@ async function rejectReservedRoot(root) {
60
56
  async function ensureMarker(root) {
61
57
  const path = marker(root);
62
58
  try {
63
- if (!(await stat(path)).isDirectory())
59
+ if (!(await lstat(path)).isDirectory())
64
60
  throw new Error("not a directory");
65
61
  return;
66
62
  }
@@ -88,7 +84,7 @@ async function locateMarker(input) {
88
84
  const filesystemRoot = parse(candidate).root;
89
85
  if (candidate !== home && candidate !== filesystemRoot) {
90
86
  try {
91
- if (!(await stat(marker(candidate))).isDirectory()) {
87
+ if (!(await lstat(marker(candidate))).isDirectory()) {
92
88
  throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
93
89
  }
94
90
  return brand(candidate);
package/package.json CHANGED
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "4.5.11",
3
+ "version": "4.5.13",
4
+ "workspaces": [
5
+ "plugins/*"
6
+ ],
4
7
  "files": [
5
8
  "build"
6
9
  ],
@@ -22,6 +25,10 @@
22
25
  "./akuma": {
23
26
  "types": "./build/src/akuma/index.d.ts",
24
27
  "import": "./build/src/akuma/index.js"
28
+ },
29
+ "./plugin": {
30
+ "types": "./build/src/plugin/public.d.ts",
31
+ "import": "./build/src/plugin/public.js"
25
32
  }
26
33
  },
27
34
  "bin": {
@@ -32,15 +39,18 @@
32
39
  "node": ">=22.19.0"
33
40
  },
34
41
  "scripts": {
35
- "build": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('build', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/copy-integrations.js && node scripts/build-windows-launcher.js && node --input-type=module -e \"import { chmodSync } from 'node:fs'; chmodSync('build/src/cli/index.js', 0o755)\"",
42
+ "build": "node --input-type=module -e \"import { rmSync } from 'node:fs'; rmSync('build', { recursive: true, force: true })\" && tsc -p tsconfig.json && npm run build --workspace @astrosheep/keiyaku-plugin-square && node scripts/copy-integrations.js && node scripts/build-windows-launcher.js && node --input-type=module -e \"import { chmodSync } from 'node:fs'; chmodSync('build/src/cli/index.js', 0o755)\"",
36
43
  "prepack": "node scripts/verify-package.js",
37
44
  "postpack": "node scripts/verify-package.js",
38
45
  "harness:version": "node scripts/update-harness-version.js",
39
46
  "review:model-impact": "node --import tsx scripts/model-change-impact.ts",
40
47
  "format": "prettier --write --print-width 120 \"src/**/*.ts\" \"scripts/**/*.ts\"",
41
48
  "format:check": "prettier --check --print-width 120 \"src/**/*.ts\" \"scripts/**/*.ts\"",
49
+ "lint": "eslint src scripts tests",
42
50
  "test": "node scripts/test-entry.mjs",
43
- "test:all": "npm run format:check && npm run build && npm run test:architecture && npm run test:maintainability && npm run test:reachability && npm run test:parallel",
51
+ "test:all": "node scripts/test-entry.mjs --release",
52
+ "test:release": "node scripts/test-entry.mjs --release",
53
+ "test:dev": "node scripts/test-entry.mjs --dev",
44
54
  "test:architecture": "node --import tsx scripts/check-architecture.ts",
45
55
  "test:e2e": "npm run build && node scripts/run-tests.mjs tests/packaged-cli.e2e.ts",
46
56
  "test:local": "node scripts/run-tests.mjs --suite local",
@@ -50,7 +60,9 @@
50
60
  "test:focused": "node scripts/run-tests.mjs",
51
61
  "test:focused:serial": "node scripts/run-tests.mjs --test-concurrency=1",
52
62
  "test:reachability": "knip --include files,dependencies,unlisted,unresolved,duplicates",
53
- "test:typecheck": "tsc -p tsconfig.json --noEmit"
63
+ "test:typecheck": "tsc -p tsconfig.json --noEmit && npm run test:typecheck:scoped-tests && npm run test:typecheck:scripts",
64
+ "test:typecheck:scoped-tests": "tsc -p tsconfig.tests.json",
65
+ "test:typecheck:scripts": "tsc -p tsconfig.scripts.json"
54
66
  },
55
67
  "devDependencies": {
56
68
  "@types/cross-spawn": "^6.0.6",
@@ -65,7 +77,7 @@
65
77
  "dependencies": {
66
78
  "@agentclientprotocol/sdk": "^1.3.0",
67
79
  "@anthropic-ai/claude-agent-sdk": "^0.3.226",
68
- "@astrosheep/square": "0.3.32",
80
+ "@astrosheep/keiyaku-plugin-square": "0.1.1",
69
81
  "@earendil-works/pi-coding-agent": "0.80.10",
70
82
  "@opencode-ai/sdk": "1.18.3",
71
83
  "cross-spawn": "^7.0.6",
@@ -1,7 +0,0 @@
1
- import type { AllocatedAkuma } from "../akuma/identity.js";
2
- import { type WorldRoot } from "../world.js";
3
- export declare function recognizeAndListen(worldRoot: WorldRoot, environment: NodeJS.ProcessEnv, allocated: AllocatedAkuma): Promise<{
4
- committed: boolean;
5
- participantName: string;
6
- rollback(): Promise<void>;
7
- } | void>;