@astrosheep/keiyaku 4.0.1 → 4.0.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 (245) hide show
  1. package/README.md +137 -5
  2. package/build/integrations/marketplace/plugins/keiyaku/.claude-plugin/plugin.json +1 -1
  3. package/build/integrations/marketplace/plugins/keiyaku/.codex-plugin/plugin.json +1 -1
  4. package/build/integrations/marketplace/plugins/keiyaku/package.json +1 -1
  5. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku/SKILL.md +3 -3
  6. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-akuma/SKILL.md +91 -35
  7. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-bind/SKILL.md +98 -58
  8. package/build/integrations/marketplace/plugins/keiyaku/skills/keiyaku-workflow/SKILL.md +152 -53
  9. package/build/src/akuma/abort.d.ts +2 -0
  10. package/build/src/akuma/abort.js +50 -0
  11. package/build/src/akuma/akuma.d.ts +23 -14
  12. package/build/src/akuma/akuma.js +120 -104
  13. package/build/src/akuma/archetype.d.ts +7 -6
  14. package/build/src/akuma/archetype.js +68 -41
  15. package/build/src/akuma/body.d.ts +3 -5
  16. package/build/src/akuma/body.js +408 -213
  17. package/build/src/akuma/heart/facts.d.ts +25 -34
  18. package/build/src/akuma/heart/facts.js +18 -7
  19. package/build/src/akuma/heart/index.d.ts +39 -42
  20. package/build/src/akuma/heart/index.js +134 -93
  21. package/build/src/akuma/heart/rows.d.ts +9 -31
  22. package/build/src/akuma/heart/rows.js +26 -44
  23. package/build/src/akuma/heart/schema.d.ts +1 -1
  24. package/build/src/akuma/heart/schema.js +7 -15
  25. package/build/src/akuma/heart/soul.d.ts +8 -0
  26. package/build/src/akuma/heart/soul.js +136 -0
  27. package/build/src/akuma/heart/storage.d.ts +28 -14
  28. package/build/src/akuma/heart/storage.js +86 -26
  29. package/build/src/akuma/identity.d.ts +2 -2
  30. package/build/src/akuma/identity.js +9 -9
  31. package/build/src/akuma/index.d.ts +6 -1
  32. package/build/src/akuma/index.js +6 -0
  33. package/build/src/akuma/projection.d.ts +117 -38
  34. package/build/src/akuma/projection.js +208 -97
  35. package/build/src/akuma/provider-recipe.d.ts +24 -0
  36. package/build/src/akuma/provider-recipe.js +100 -0
  37. package/build/src/akuma/provider.d.ts +12 -4
  38. package/build/src/akuma/provider.js +117 -70
  39. package/build/src/akuma/providers/acp/core.d.ts +19 -0
  40. package/build/src/akuma/providers/acp/core.js +159 -0
  41. package/build/src/akuma/providers/acp/events.d.ts +20 -0
  42. package/build/src/akuma/providers/acp/events.js +73 -0
  43. package/build/src/akuma/providers/acp/index.d.ts +12 -0
  44. package/build/src/akuma/providers/acp/index.js +94 -0
  45. package/build/src/akuma/providers/claude/events.js +66 -13
  46. package/build/src/akuma/providers/claude/index.d.ts +2 -1
  47. package/build/src/akuma/providers/claude/index.js +35 -21
  48. package/build/src/akuma/providers/codex-app-server/events.js +4 -1
  49. package/build/src/akuma/providers/codex-app-server/index.d.ts +3 -3
  50. package/build/src/akuma/providers/codex-app-server/index.js +24 -31
  51. package/build/src/akuma/providers/grok-build/index.d.ts +4 -0
  52. package/build/src/akuma/providers/grok-build/index.js +70 -0
  53. package/build/src/akuma/providers/index.d.ts +5 -3
  54. package/build/src/akuma/providers/index.js +24 -59
  55. package/build/src/akuma/providers/opencode-sdk/events.d.ts +40 -34
  56. package/build/src/akuma/providers/opencode-sdk/events.js +220 -101
  57. package/build/src/akuma/providers/opencode-sdk/index.d.ts +3 -2
  58. package/build/src/akuma/providers/opencode-sdk/index.js +233 -128
  59. package/build/src/akuma/providers/opencode-sdk/session.d.ts +11 -26
  60. package/build/src/akuma/providers/opencode-sdk/session.js +10 -9
  61. package/build/src/akuma/providers/pi/events.js +49 -11
  62. package/build/src/akuma/providers/pi/index.d.ts +1 -1
  63. package/build/src/akuma/providers/pi/index.js +50 -21
  64. package/build/src/akuma/publication.d.ts +3 -3
  65. package/build/src/akuma/publication.js +28 -40
  66. package/build/src/akuma/requests.d.ts +9 -7
  67. package/build/src/akuma/requests.js +92 -65
  68. package/build/src/alias/index.d.ts +2 -2
  69. package/build/src/alias/index.js +10 -10
  70. package/build/src/body/decode.js +7 -1
  71. package/build/src/body/region.d.ts +2 -0
  72. package/build/src/body/region.js +77 -7
  73. package/build/src/cli/accepted.d.ts +17 -13
  74. package/build/src/cli/accepted.js +72 -34
  75. package/build/src/cli/actor.js +3 -3
  76. package/build/src/cli/commands/akuma-invoke.d.ts +4 -3
  77. package/build/src/cli/commands/akuma-invoke.js +14 -9
  78. package/build/src/cli/commands/akuma.d.ts +13 -3
  79. package/build/src/cli/commands/akuma.js +67 -34
  80. package/build/src/cli/commands/contract.d.ts +18 -5
  81. package/build/src/cli/commands/contract.js +9 -3
  82. package/build/src/cli/commands/task-invoke.d.ts +17 -4
  83. package/build/src/cli/commands/task-invoke.js +43 -20
  84. package/build/src/cli/commands/task-query.d.ts +5 -0
  85. package/build/src/cli/commands/task-query.js +247 -0
  86. package/build/src/cli/commands/task.d.ts +3 -1
  87. package/build/src/cli/commands/task.js +51 -13
  88. package/build/src/cli/coordinates.d.ts +18 -0
  89. package/build/src/cli/coordinates.js +120 -0
  90. package/build/src/cli/draft.d.ts +8 -0
  91. package/build/src/cli/draft.js +96 -0
  92. package/build/src/cli/invoke.d.ts +4 -3
  93. package/build/src/cli/invoke.js +160 -179
  94. package/build/src/cli/main.js +34 -6
  95. package/build/src/cli/parse.d.ts +9 -2
  96. package/build/src/cli/parse.js +29 -7
  97. package/build/src/cli/render/akuma-tool-command.d.ts +6 -0
  98. package/build/src/cli/render/akuma-tool-command.js +144 -0
  99. package/build/src/cli/render/akuma-tool.d.ts +2 -2
  100. package/build/src/cli/render/akuma-tool.js +30 -4
  101. package/build/src/cli/render/akuma.d.ts +1 -0
  102. package/build/src/cli/render/akuma.js +211 -116
  103. package/build/src/cli/render/audit.d.ts +3 -0
  104. package/build/src/cli/render/audit.js +104 -0
  105. package/build/src/cli/render/contract.d.ts +3 -3
  106. package/build/src/cli/render/contract.js +192 -79
  107. package/build/src/cli/render/kanshi.js +263 -168
  108. package/build/src/cli/render/receipt.d.ts +19 -0
  109. package/build/src/cli/render/receipt.js +106 -0
  110. package/build/src/cli/render/refusal.d.ts +16 -2
  111. package/build/src/cli/render/refusal.js +88 -20
  112. package/build/src/cli/render/region.d.ts +2 -0
  113. package/build/src/cli/render/region.js +17 -0
  114. package/build/src/cli/render/task.d.ts +2 -1
  115. package/build/src/cli/render/task.js +223 -60
  116. package/build/src/cli/render/terminal.d.ts +7 -0
  117. package/build/src/cli/render/terminal.js +46 -0
  118. package/build/src/cli/render/text.js +6 -3
  119. package/build/src/cli/result.d.ts +115 -20
  120. package/build/src/cli/usage.d.ts +1 -0
  121. package/build/src/cli/usage.js +3 -0
  122. package/build/src/contract-worktree.d.ts +6 -5
  123. package/build/src/contract-worktree.js +95 -57
  124. package/build/src/coordination/durable-file.d.ts +4 -3
  125. package/build/src/coordination/durable-file.js +29 -25
  126. package/build/src/coordination/sqlite-transaction-lock.d.ts +4 -0
  127. package/build/src/coordination/sqlite-transaction-lock.js +22 -5
  128. package/build/src/core/facts/fold.js +1 -6
  129. package/build/src/core/facts/gate.d.ts +1 -0
  130. package/build/src/core/facts/gate.js +1 -0
  131. package/build/src/core/verbs/amend.d.ts +1 -1
  132. package/build/src/core/verbs/amend.js +0 -11
  133. package/build/src/core/verbs/deliver.d.ts +1 -1
  134. package/build/src/core/verbs/deliver.js +1 -7
  135. package/build/src/core/verbs/placement.d.ts +1 -1
  136. package/build/src/core/verbs/placement.js +4 -1
  137. package/build/src/dispatch/index.d.ts +2 -2
  138. package/build/src/dispatch/index.js +17 -17
  139. package/build/src/git/admission.d.ts +1 -1
  140. package/build/src/git/admission.js +11 -11
  141. package/build/src/git/hooks.js +13 -13
  142. package/build/src/git/integration.d.ts +14 -15
  143. package/build/src/git/integration.js +80 -44
  144. package/build/src/git/observe.d.ts +5 -3
  145. package/build/src/git/observe.js +18 -12
  146. package/build/src/git/read-observation.js +9 -9
  147. package/build/src/git/reconcile.d.ts +2 -0
  148. package/build/src/git/reconcile.js +138 -95
  149. package/build/src/git/repository.d.ts +27 -19
  150. package/build/src/git/repository.js +155 -83
  151. package/build/src/git/scratch.d.ts +7 -3
  152. package/build/src/git/scratch.js +44 -37
  153. package/build/src/git/target-placement.d.ts +40 -4
  154. package/build/src/git/target-placement.js +221 -101
  155. package/build/src/git/tender.d.ts +6 -5
  156. package/build/src/git/tender.js +24 -43
  157. package/build/src/git/terminal-seal.d.ts +1 -1
  158. package/build/src/git/terminal-seal.js +7 -5
  159. package/build/src/git/workspace.d.ts +36 -4
  160. package/build/src/git/workspace.js +53 -18
  161. package/build/src/identity/selector.js +1 -1
  162. package/build/src/index.d.ts +3 -3
  163. package/build/src/index.js +1 -1
  164. package/build/src/kanshi/index.d.ts +2 -2
  165. package/build/src/kanshi/index.js +1 -1
  166. package/build/src/kanshi/read.d.ts +2 -1
  167. package/build/src/kanshi/read.js +118 -27
  168. package/build/src/kanshi/report.d.ts +50 -0
  169. package/build/src/kanshi/select.d.ts +5 -0
  170. package/build/src/kanshi/select.js +32 -0
  171. package/build/src/library/address.d.ts +12 -3
  172. package/build/src/library/address.js +77 -46
  173. package/build/src/library/akuma-creation.d.ts +6 -1
  174. package/build/src/library/akuma-creation.js +86 -13
  175. package/build/src/library/audit.d.ts +18 -0
  176. package/build/src/library/audit.js +44 -0
  177. package/build/src/library/bind.js +4 -4
  178. package/build/src/library/catalog.d.ts +1 -1
  179. package/build/src/library/catalog.js +5 -8
  180. package/build/src/library/contract.d.ts +5 -6
  181. package/build/src/library/contract.js +11 -35
  182. package/build/src/library/delivery.d.ts +2 -1
  183. package/build/src/library/fleet.d.ts +2 -2
  184. package/build/src/library/fleet.js +35 -71
  185. package/build/src/library/keiyaku.d.ts +3 -2
  186. package/build/src/library/keiyaku.js +1 -0
  187. package/build/src/library/mutation.d.ts +7 -6
  188. package/build/src/library/mutation.js +23 -25
  189. package/build/src/library/reconcile.d.ts +29 -0
  190. package/build/src/library/reconcile.js +179 -0
  191. package/build/src/library/region.d.ts +12 -0
  192. package/build/src/library/region.js +15 -1
  193. package/build/src/library/repo.d.ts +4 -16
  194. package/build/src/library/repo.js +13 -38
  195. package/build/src/protocol/attempt.js +6 -6
  196. package/build/src/protocol/bind.js +4 -4
  197. package/build/src/protocol/intent.d.ts +15 -2
  198. package/build/src/protocol/intent.js +21 -6
  199. package/build/src/protocol/operations.d.ts +73 -22
  200. package/build/src/protocol/operations.js +292 -88
  201. package/build/src/protocol/outcome.d.ts +9 -3
  202. package/build/src/protocol/outcome.js +3 -1
  203. package/build/src/protocol/placement.d.ts +15 -4
  204. package/build/src/protocol/placement.js +58 -25
  205. package/build/src/protocol/read/status.d.ts +8 -1
  206. package/build/src/protocol/read/status.js +61 -14
  207. package/build/src/runtime/proc/line-rpc.d.ts +3 -8
  208. package/build/src/runtime/proc/line-rpc.js +16 -43
  209. package/build/src/runtime/proc/run.d.ts +15 -29
  210. package/build/src/runtime/proc/run.js +113 -105
  211. package/build/src/runtime/proc/stdio.d.ts +18 -0
  212. package/build/src/runtime/proc/stdio.js +58 -0
  213. package/build/src/settings.d.ts +2 -2
  214. package/build/src/settings.js +8 -8
  215. package/build/src/settlement/fence.d.ts +0 -4
  216. package/build/src/settlement/fence.js +0 -9
  217. package/build/src/settlement/holder.d.ts +11 -1
  218. package/build/src/settlement/holder.js +25 -0
  219. package/build/src/settlement/settle.d.ts +1 -1
  220. package/build/src/settlement/settle.js +52 -89
  221. package/build/src/task/board.d.ts +3 -3
  222. package/build/src/task/board.js +6 -9
  223. package/build/src/task/compose.d.ts +1 -1
  224. package/build/src/task/compose.js +18 -10
  225. package/build/src/task/context.d.ts +3 -3
  226. package/build/src/task/context.js +14 -14
  227. package/build/src/task/document.d.ts +2 -1
  228. package/build/src/task/document.js +15 -5
  229. package/build/src/task/index.d.ts +19 -7
  230. package/build/src/task/index.js +42 -23
  231. package/build/src/task/operations.d.ts +15 -9
  232. package/build/src/task/operations.js +78 -48
  233. package/build/src/task/query.d.ts +67 -0
  234. package/build/src/task/query.js +279 -0
  235. package/build/src/task/store.d.ts +2 -2
  236. package/build/src/task/store.js +40 -25
  237. package/build/src/verification/execution.d.ts +4 -2
  238. package/build/src/verification/execution.js +9 -5
  239. package/build/src/workspace-place.d.ts +41 -0
  240. package/build/src/workspace-place.js +386 -0
  241. package/build/src/world.d.ts +13 -2
  242. package/build/src/world.js +81 -48
  243. package/package.json +3 -2
  244. package/build/src/protocol/read/audit.d.ts +0 -31
  245. package/build/src/protocol/read/audit.js +0 -55
@@ -0,0 +1,386 @@
1
+ import { mkdir, readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { replaceFileDurably } from "./coordination/durable-file.js";
4
+ import { acquireSqliteTransactionLock } from "./coordination/sqlite-transaction-lock.js";
5
+ import { AuthorityCorruptionError } from "./core/facts/errors.js";
6
+ import { contractId } from "./core/facts/types.js";
7
+ import { worktreePath } from "./git/workspace.js";
8
+ export const CONTRACT_PLACES = Object.freeze([
9
+ "atlantis",
10
+ "hogwarts",
11
+ "narnia",
12
+ "neverland",
13
+ "mordor",
14
+ "shire",
15
+ "olympus",
16
+ "valhalla",
17
+ "asgard",
18
+ "hades",
19
+ "camelot",
20
+ "avalon",
21
+ "utopia",
22
+ "azkaban",
23
+ "konoha",
24
+ "namek",
25
+ "laputa",
26
+ "tatooine",
27
+ "stomach",
28
+ "eyeball",
29
+ "braincell",
30
+ "appendix",
31
+ "nostril",
32
+ "armpit",
33
+ "chineseroom",
34
+ "catbox",
35
+ "babel",
36
+ "teapot",
37
+ "blackhole",
38
+ "wormhole",
39
+ "devnull",
40
+ "localhost",
41
+ "recyclebin",
42
+ "bikeshed",
43
+ "serverroom",
44
+ "spawnpoint",
45
+ "bossroom",
46
+ "hiddenlevel",
47
+ "warpzone",
48
+ "styx",
49
+ "limbo",
50
+ "purgatory",
51
+ "penglai",
52
+ "longgong",
53
+ "nantianmen",
54
+ "guimenguan",
55
+ "naiheqiao",
56
+ "backrooms",
57
+ "bermuda",
58
+ "upsidedown",
59
+ "mindpalace",
60
+ "pandorabox",
61
+ "friendzone",
62
+ "comfortzone",
63
+ "echochamber",
64
+ "rabbithole",
65
+ "ivorytower",
66
+ "doghouse",
67
+ "panicroom",
68
+ "shoebox",
69
+ "snowglobe",
70
+ "dollhouse",
71
+ "donuthole",
72
+ "dumpster",
73
+ "blanketfort",
74
+ "kotatsu",
75
+ "fishbowl",
76
+ "pokeball",
77
+ "pineapple",
78
+ "titanic",
79
+ "disneyland",
80
+ "ikea",
81
+ "costco",
82
+ "warppipe",
83
+ "tetris",
84
+ "minecraft",
85
+ "ufo",
86
+ "catbus",
87
+ "timemachine",
88
+ "moonbox",
89
+ "huaguoshan",
90
+ "pansidong",
91
+ "bellybutton",
92
+ "sock",
93
+ "spam",
94
+ "downloads",
95
+ "toilet",
96
+ "blender",
97
+ "toaster",
98
+ "litterbox",
99
+ "kiddiepool",
100
+ "beanbag",
101
+ "vacuum",
102
+ "elevator",
103
+ "glovebox",
104
+ "attic",
105
+ "cloud",
106
+ "wall",
107
+ "can",
108
+ "cup",
109
+ "wc",
110
+ "bean",
111
+ "bus",
112
+ "helicopter",
113
+ "commandroom",
114
+ "box",
115
+ "jar",
116
+ "pot",
117
+ "wok",
118
+ "bowl",
119
+ "bucket",
120
+ "drawer",
121
+ "sofa",
122
+ "fridge",
123
+ "microwave",
124
+ "well",
125
+ "ditch",
126
+ "puddle",
127
+ "tent",
128
+ "cave",
129
+ "shed",
130
+ "garage",
131
+ "basement",
132
+ "balcony",
133
+ "corner",
134
+ "pigpen",
135
+ "henhouse",
136
+ "excavator",
137
+ "tank",
138
+ "submarine",
139
+ "treehouse",
140
+ "igloo",
141
+ "phonebooth",
142
+ "mailbox",
143
+ "doormat",
144
+ "bathtub",
145
+ "wardrobe",
146
+ "quicksand",
147
+ "tarpit",
148
+ "haystack",
149
+ "manhole",
150
+ "chimney",
151
+ "dryer",
152
+ "fittingroom",
153
+ "lazysusan",
154
+ "revolvingdoor",
155
+ "minibar",
156
+ "casino",
157
+ "karaoke",
158
+ "pinball",
159
+ "funhouse",
160
+ "burrito",
161
+ "dumpling",
162
+ "hotpot",
163
+ "bento",
164
+ "teabag",
165
+ "fortunecookie",
166
+ "airlock",
167
+ "escapepod",
168
+ "hammock",
169
+ "legroom",
170
+ "sidequest",
171
+ "lootbox",
172
+ "gutter",
173
+ "pawnshop",
174
+ "hermitcrab",
175
+ "couchgap",
176
+ "dumbwaiter",
177
+ "petridish",
178
+ "baggageclaim",
179
+ "morgue",
180
+ "ballpit",
181
+ "clawmachine",
182
+ ]);
183
+ const PLACE_INDEX = new Map(CONTRACT_PLACES.map((base, index) => [base, index]));
184
+ const PLACE_VERSION = 1;
185
+ const GENERATION_SUFFIX = /^(?:[2-9]|[1-9][0-9]+)$/u;
186
+ function placePaths(repository) {
187
+ return {
188
+ authority: join(repository.commonDirectory, "keiyaku", "places.json"),
189
+ lock: join(repository.commonDirectory, "keiyaku", "locks", "places.sqlite"),
190
+ };
191
+ }
192
+ export function placeRegisterPath(repository) {
193
+ return placePaths(repository).authority;
194
+ }
195
+ function indexedRegister(appointments) {
196
+ return {
197
+ appointments,
198
+ byPlace: new Map(appointments.map((appointment) => [appointment.place, appointment])),
199
+ byContract: new Map(appointments.map((appointment) => [appointment.contract, appointment])),
200
+ };
201
+ }
202
+ function parsedPlace(value) {
203
+ if (PLACE_INDEX.has(value))
204
+ return { base: value, generation: 1n };
205
+ for (let end = value.length - 1; end > 0; end -= 1) {
206
+ const base = value.slice(0, end);
207
+ const suffix = value.slice(end);
208
+ if (PLACE_INDEX.has(base) && GENERATION_SUFFIX.test(suffix)) {
209
+ return { base, generation: BigInt(suffix) };
210
+ }
211
+ }
212
+ return undefined;
213
+ }
214
+ function composePlace(base, generation) {
215
+ return (generation === 1n ? base : `${base}${generation.toString()}`);
216
+ }
217
+ export function place(value) {
218
+ if (parsedPlace(value) === undefined)
219
+ throw new TypeError(`Place is not canonical: ${value}`);
220
+ return value;
221
+ }
222
+ export function nextPlace(current) {
223
+ if (current === undefined)
224
+ return place(CONTRACT_PLACES[0]);
225
+ const parsed = parsedPlace(current);
226
+ if (parsed === undefined)
227
+ throw new TypeError(`Place is not canonical: ${current}`);
228
+ const index = PLACE_INDEX.get(parsed.base);
229
+ return index + 1 < CONTRACT_PLACES.length
230
+ ? composePlace(CONTRACT_PLACES[index + 1], parsed.generation)
231
+ : composePlace(CONTRACT_PLACES[0], parsed.generation + 1n);
232
+ }
233
+ export function firstFreePlace(register) {
234
+ for (let candidate = nextPlace();; candidate = nextPlace(candidate)) {
235
+ if (!register.byPlace.has(candidate))
236
+ return candidate;
237
+ }
238
+ }
239
+ export function canonicalPlaceRegister(register) {
240
+ const appointments = Object.fromEntries(register.appointments
241
+ .slice()
242
+ .sort((left, right) => Buffer.compare(Buffer.from(left.place), Buffer.from(right.place)))
243
+ .map((appointment) => [appointment.place, appointment.contract]));
244
+ return `${JSON.stringify({ version: PLACE_VERSION, appointments })}\n`;
245
+ }
246
+ function placeCorruption(path, message, cause) {
247
+ throw new AuthorityCorruptionError(`${message}: ${path}`, cause === undefined ? {} : { cause });
248
+ }
249
+ export function decodePlaceRegister(path, bytes) {
250
+ let value;
251
+ try {
252
+ if (!bytes.endsWith("\n") || bytes.slice(0, -1).includes("\n")) {
253
+ placeCorruption(path, "Place file is not one canonical JSON line");
254
+ }
255
+ value = JSON.parse(bytes);
256
+ }
257
+ catch (error) {
258
+ if (error instanceof AuthorityCorruptionError)
259
+ throw error;
260
+ return placeCorruption(path, "invalid Place JSON", error);
261
+ }
262
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
263
+ placeCorruption(path, "Place file must be an object");
264
+ }
265
+ const record = value;
266
+ if (Object.keys(record).length !== 2 || !Object.hasOwn(record, "version") || !Object.hasOwn(record, "appointments")) {
267
+ placeCorruption(path, "Place file has invalid fields");
268
+ }
269
+ if (record.version !== PLACE_VERSION)
270
+ placeCorruption(path, `Place file version must be ${PLACE_VERSION}`);
271
+ if (typeof record.appointments !== "object" || record.appointments === null || Array.isArray(record.appointments)) {
272
+ placeCorruption(path, "Place appointments must be an object");
273
+ }
274
+ const appointments = [];
275
+ const contracts = new Set();
276
+ for (const [rawPlace, rawContract] of Object.entries(record.appointments)) {
277
+ if (typeof rawContract !== "string")
278
+ placeCorruption(path, "Place appointment must be a ContractId");
279
+ try {
280
+ const appointed = { place: place(rawPlace), contract: contractId(rawContract) };
281
+ if (contracts.has(appointed.contract))
282
+ placeCorruption(path, "Place file has duplicate Contract appointment");
283
+ contracts.add(appointed.contract);
284
+ appointments.push(appointed);
285
+ }
286
+ catch (error) {
287
+ return placeCorruption(path, "Place appointment is invalid", error);
288
+ }
289
+ }
290
+ appointments.sort((left, right) => Buffer.compare(Buffer.from(left.place), Buffer.from(right.place)));
291
+ const register = indexedRegister(appointments);
292
+ if (canonicalPlaceRegister(register) !== bytes)
293
+ placeCorruption(path, "Place bytes are not canonical");
294
+ return register;
295
+ }
296
+ export function emptyPlaceRegister() {
297
+ return indexedRegister([]);
298
+ }
299
+ export async function readPlaceRegister(repository) {
300
+ const path = placePaths(repository).authority;
301
+ try {
302
+ return decodePlaceRegister(path, await readFile(path, "utf8"));
303
+ }
304
+ catch (error) {
305
+ if (error.code === "ENOENT")
306
+ return emptyPlaceRegister();
307
+ throw error;
308
+ }
309
+ }
310
+ export function appointmentFor(register, contract) {
311
+ return register.byContract.get(contract);
312
+ }
313
+ async function mutatePlaceRegister(repository, mutate) {
314
+ const location = placePaths(repository);
315
+ const held = await acquireSqliteTransactionLock({ path: location.lock, mode: "immediate" });
316
+ try {
317
+ const current = await readPlaceRegister(repository);
318
+ const next = mutate(current);
319
+ const bytes = canonicalPlaceRegister(next);
320
+ try {
321
+ if (await readFile(location.authority, "utf8") === bytes)
322
+ return next;
323
+ }
324
+ catch (error) {
325
+ if (error.code !== "ENOENT")
326
+ throw error;
327
+ }
328
+ await mkdir(dirname(location.authority), { recursive: true });
329
+ await replaceFileDurably(location.authority, bytes);
330
+ return next;
331
+ }
332
+ finally {
333
+ held.close();
334
+ }
335
+ }
336
+ function withAppointments(register, contracts) {
337
+ const additions = [];
338
+ const occupied = new Set(register.byPlace.keys());
339
+ const known = new Set(register.byContract.keys());
340
+ let cursor;
341
+ for (const contract of contracts) {
342
+ if (known.has(contract))
343
+ continue;
344
+ let candidate = cursor === undefined ? nextPlace() : nextPlace(cursor);
345
+ while (occupied.has(candidate))
346
+ candidate = nextPlace(candidate);
347
+ additions.push({ place: candidate, contract });
348
+ occupied.add(candidate);
349
+ known.add(contract);
350
+ cursor = candidate;
351
+ }
352
+ return additions.length === 0 ? register : indexedRegister([...register.appointments, ...additions]);
353
+ }
354
+ export async function appointManagedWorktrees(repository, contracts) {
355
+ return await mutatePlaceRegister(repository, (current) => withAppointments(current, contracts));
356
+ }
357
+ export async function appointManagedWorktree(repository, contract) {
358
+ const appointed = appointmentFor(await appointManagedWorktrees(repository, [contract]), contract);
359
+ if (appointed === undefined)
360
+ throw new Error(`Place appointment was not recorded: ${contract}`);
361
+ return appointed;
362
+ }
363
+ export async function releaseManagedWorktrees(repository, contracts) {
364
+ if (contracts.length === 0)
365
+ return;
366
+ const drop = new Set(contracts);
367
+ await mutatePlaceRegister(repository, (current) => {
368
+ const kept = current.appointments.filter((appointment) => !drop.has(appointment.contract));
369
+ return kept.length === current.appointments.length ? current : indexedRegister(kept);
370
+ });
371
+ }
372
+ export async function releaseManagedWorktree(repository, contract) {
373
+ await releaseManagedWorktrees(repository, [contract]);
374
+ }
375
+ export async function readManagedWorktreeAppointment(repository, contract, register) {
376
+ try {
377
+ const snapshot = register ?? await readPlaceRegister(repository);
378
+ const appointed = appointmentFor(snapshot, contract);
379
+ return appointed === undefined
380
+ ? { kind: "unappointed" }
381
+ : { kind: "appointed", place: appointed.place, path: worktreePath(repository, appointed.place) };
382
+ }
383
+ catch (error) {
384
+ return { kind: "failed", diagnostic: error instanceof Error ? error.message : String(error) };
385
+ }
386
+ }
@@ -2,12 +2,23 @@ declare const WORLD_BRAND: unique symbol;
2
2
  export type WorldRoot = string & {
3
3
  readonly [WORLD_BRAND]: true;
4
4
  };
5
+ export type WorldResolutionInput = Readonly<{
6
+ cwd: string;
7
+ repositoryRoot?: string;
8
+ }>;
9
+ export type WorldResolution = Readonly<{
10
+ root: WorldRoot | null;
11
+ candidate: WorldRoot | null;
12
+ establish: () => Promise<WorldRoot>;
13
+ }>;
5
14
  export declare class WorldError extends Error {
6
15
  readonly kind: "invalid-world" | "home-world" | "root-world";
7
16
  constructor(kind: "invalid-world" | "home-world" | "root-world", message: string);
8
17
  }
18
+ type WorldInput = string | WorldResolutionInput;
9
19
  export declare const World: Readonly<{
10
- locate(input: string): WorldRoot | null;
11
- at(input: string): WorldRoot;
20
+ resolve(input: WorldInput): Promise<WorldResolution>;
21
+ locate(input: WorldInput): Promise<WorldRoot | null>;
22
+ at(input: string): Promise<WorldRoot>;
12
23
  }>;
13
24
  export {};
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
1
+ import { 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");
@@ -10,28 +10,28 @@ export class WorldError extends Error {
10
10
  this.kind = kind;
11
11
  }
12
12
  }
13
- function homeRoot() {
13
+ async function homeRoot() {
14
14
  try {
15
- return realpathSync(resolve(homedir()));
15
+ return await realpath(resolve(homedir()));
16
16
  }
17
17
  catch {
18
18
  return resolve(homedir());
19
19
  }
20
20
  }
21
- function directory(input, label) {
21
+ async function directory(input, label) {
22
22
  if (typeof input !== "string" || input.trim().length === 0) {
23
23
  throw new TypeError(`${label} must be a nonblank path`);
24
24
  }
25
25
  const path = resolve(input);
26
26
  let real;
27
27
  try {
28
- real = realpathSync(path);
28
+ real = await realpath(path);
29
29
  }
30
30
  catch (error) {
31
31
  throw new WorldError("invalid-world", `world path is not an existing directory: ${path}`);
32
32
  }
33
33
  try {
34
- if (!statSync(real).isDirectory())
34
+ if (!(await stat(real)).isDirectory())
35
35
  throw new Error("not a directory");
36
36
  }
37
37
  catch {
@@ -41,63 +41,96 @@ function directory(input, label) {
41
41
  }
42
42
  function brand(path) { return path; }
43
43
  function marker(root) { return join(root, ".keiyaku"); }
44
- function rejectReservedRoot(root) {
45
- if (root === homeRoot()) {
44
+ async function rejectReservedRoot(root) {
45
+ if (root === await homeRoot()) {
46
46
  throw new WorldError("home-world", "the user home directory cannot be a Keiyaku world");
47
47
  }
48
48
  if (root === parse(root).root) {
49
49
  throw new WorldError("root-world", "the filesystem root cannot be a Keiyaku world");
50
50
  }
51
51
  }
52
- function ensureMarker(root) {
52
+ async function ensureMarker(root) {
53
53
  const path = marker(root);
54
- if (existsSync(path)) {
55
- try {
56
- if (!statSync(path).isDirectory())
57
- throw new Error("not a directory");
58
- }
59
- catch {
54
+ try {
55
+ if (!(await stat(path)).isDirectory())
56
+ throw new Error("not a directory");
57
+ return;
58
+ }
59
+ catch (error) {
60
+ if (error.code !== "ENOENT") {
60
61
  throw new WorldError("invalid-world", `world marker is not a directory: ${path}`);
61
62
  }
62
- return;
63
63
  }
64
- mkdirSync(path, { recursive: true });
65
- }
66
- function exact(input) {
67
- const root = directory(input, "world");
68
- rejectReservedRoot(root);
69
- ensureMarker(root);
70
- return brand(root);
64
+ await mkdir(path, { recursive: true });
71
65
  }
72
- function locateInput(input) {
73
- return directory(input, "world location");
66
+ async function inputValues(input, label) {
67
+ if (typeof input === "string")
68
+ return { cwd: await directory(input, label) };
69
+ if (input === null || typeof input !== "object")
70
+ throw new TypeError(`${label} must be a path or resolution input`);
71
+ const cwd = await directory(input.cwd, `${label} cwd`);
72
+ if (input.repositoryRoot === undefined)
73
+ return { cwd };
74
+ return { cwd, repositoryRoot: await directory(input.repositoryRoot, `${label} repository root`) };
74
75
  }
75
- export const World = Object.freeze({
76
- locate(input) {
77
- let candidate = locateInput(input);
78
- const home = homeRoot();
79
- for (;;) {
80
- const filesystemRoot = parse(candidate).root;
81
- if (candidate !== home && candidate !== filesystemRoot && existsSync(marker(candidate))) {
82
- try {
83
- if (!statSync(marker(candidate)).isDirectory()) {
84
- throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
85
- }
86
- }
87
- catch (error) {
88
- if (error instanceof WorldError)
89
- throw error;
76
+ async function locateMarker(input) {
77
+ let candidate = input;
78
+ const home = await homeRoot();
79
+ for (;;) {
80
+ const filesystemRoot = parse(candidate).root;
81
+ if (candidate !== home && candidate !== filesystemRoot) {
82
+ try {
83
+ if (!(await stat(marker(candidate))).isDirectory()) {
90
84
  throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
91
85
  }
92
86
  return brand(candidate);
93
87
  }
94
- const parent = dirname(candidate);
95
- if (parent === candidate || candidate === filesystemRoot)
96
- return null;
97
- candidate = parent;
88
+ catch (error) {
89
+ if (error instanceof WorldError)
90
+ throw error;
91
+ if (error.code !== "ENOENT") {
92
+ throw new WorldError("invalid-world", `world marker is not a directory: ${marker(candidate)}`);
93
+ }
94
+ }
98
95
  }
99
- },
100
- at(input) {
101
- return exact(input);
102
- },
96
+ const parent = dirname(candidate);
97
+ if (parent === candidate || candidate === filesystemRoot)
98
+ return null;
99
+ candidate = parent;
100
+ }
101
+ }
102
+ async function exact(input) {
103
+ const root = await directory(input, "world");
104
+ await rejectReservedRoot(root);
105
+ await ensureMarker(root);
106
+ return brand(root);
107
+ }
108
+ async function resolved(input) {
109
+ const values = await inputValues(input, "world location");
110
+ const root = values.repositoryRoot === undefined ? await locateMarker(values.cwd) : brand(values.repositoryRoot);
111
+ const selected = root ?? brand(values.cwd);
112
+ let candidate = selected;
113
+ try {
114
+ await rejectReservedRoot(selected);
115
+ }
116
+ catch (error) {
117
+ if (root !== null || !(error instanceof WorldError))
118
+ throw error;
119
+ candidate = null;
120
+ }
121
+ return Object.freeze({
122
+ root,
123
+ candidate,
124
+ async establish() {
125
+ const established = candidate ?? brand(values.cwd);
126
+ await rejectReservedRoot(established);
127
+ await ensureMarker(established);
128
+ return established;
129
+ },
130
+ });
131
+ }
132
+ export const World = Object.freeze({
133
+ resolve(input) { return resolved(input); },
134
+ async locate(input) { return (await resolved(input)).root; },
135
+ at(input) { return exact(input); },
103
136
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrosheep/keiyaku",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "files": [
5
5
  "build"
6
6
  ],
@@ -34,7 +34,7 @@
34
34
  "scripts": {
35
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 --input-type=module -e \"import { chmodSync } from 'node:fs'; chmodSync('build/src/cli/index.js', 0o755)\"",
36
36
  "review:model-impact": "node --import tsx scripts/model-change-impact.ts",
37
- "test": "npm run test:architecture && npm run test:maintainability && npm run test:reachability && node --import tsx scripts/run-tests.mjs",
37
+ "test": "npm run build && npm run test:architecture && npm run test:maintainability && npm run test:reachability && node --import tsx scripts/run-tests.mjs",
38
38
  "test:architecture": "node --import tsx scripts/check-architecture.ts",
39
39
  "test:e2e": "npm run build && node --import tsx --test tests/packaged-cli.e2e.ts",
40
40
  "test:maintainability": "node scripts/check-maintainability.js",
@@ -51,6 +51,7 @@
51
51
  "typescript": "^5.9.0"
52
52
  },
53
53
  "dependencies": {
54
+ "@agentclientprotocol/sdk": "^1.3.0",
54
55
  "@anthropic-ai/claude-agent-sdk": "^0.3.226",
55
56
  "@earendil-works/pi-coding-agent": "0.80.10",
56
57
  "@opencode-ai/sdk": "1.18.3",
@@ -1,31 +0,0 @@
1
- import { type GitDecodeChannel } from "../../git/read-observation.js";
2
- import type { GitRepository } from "../../git/repository.js";
3
- import type { ContractId, ContractState, DeliverData, FactKind, Gate, JournalEntry, SnapshotId } from "../../core/facts/types.js";
4
- export type { FactKind } from "../../core/facts/types.js";
5
- export type TimelineEntry = Readonly<{
6
- kind: FactKind;
7
- at: string;
8
- sincePrior: number | null;
9
- attestation?: Readonly<{
10
- gate: Gate;
11
- verdict: "satisfied" | "unsatisfied";
12
- summary?: string;
13
- }>;
14
- }>;
15
- export type AuditReport = Readonly<{
16
- reworks: number;
17
- reviews: number;
18
- timeline: readonly TimelineEntry[];
19
- delivery?: DeliverData;
20
- targetObservation?: Readonly<{
21
- head: SnapshotId | null;
22
- drift: boolean;
23
- }>;
24
- }>;
25
- type AuditRead = Readonly<{
26
- state: ContractState | null;
27
- entries: readonly JournalEntry[];
28
- report: AuditReport;
29
- }>;
30
- export declare function auditReport(entries: readonly JournalEntry[], reviewed: Gate, state?: ContractState, targetObservation?: AuditReport["targetObservation"]): AuditReport;
31
- export declare function readAuditAt(repository: GitRepository, channel: GitDecodeChannel, contract: ContractId, reviewed: Gate): Promise<AuditRead>;