@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
@@ -2,16 +2,11 @@ import { contractSegment } from "../core/facts/types.js";
2
2
  import { observeContractsForAdmissionAt } from "../git/observe.js";
3
3
  import { repairNamespaceContext } from "../task/context.js";
4
4
  import { settleTask } from "../task/operations.js";
5
- import { observeTaskHolderProjection, readTaskHolderProjectionFromDecision, taskHolderObservationSelection, } from "./holder.js";
6
- import { withTaskSettlementFence } from "./fence.js";
5
+ import { publishTaskHolderRelease, readTaskHolderProjectionFromDecision, taskHolderObservationSelection, } from "./holder.js";
7
6
  import { World } from "../world.js";
8
7
  export function deferredTaskHolderSettlement(input) {
9
8
  return { actions: [], lags: [{ kind: "settlement-failed", surface: "task-holder", ...input }] };
10
9
  }
11
- function taskRuleCanApply(state) {
12
- const terminal = state?.terminal?.kind;
13
- return terminal === "claimed" || terminal === "abandoned";
14
- }
15
10
  function diagnostic(error) {
16
11
  return error instanceof Error ? error.message : String(error);
17
12
  }
@@ -21,80 +16,70 @@ function taskFailure(result) {
21
16
  : `Task settlement refused: ${JSON.stringify(result.refusal)}`;
22
17
  }
23
18
  async function settleTasks(input) {
24
- const { observation, repository, world, candidate, actions, lags } = input;
25
- const terminal = candidate.terminal?.kind;
26
- if (terminal !== "claimed" && terminal !== "abandoned")
19
+ const { repository, channel, world, candidate, actions, lags } = input;
20
+ let observation;
21
+ try {
22
+ observation = await observeContractsForAdmissionAt(repository, channel, [candidate.id], taskHolderObservationSelection());
23
+ }
24
+ catch (error) {
25
+ lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
27
26
  return;
28
- if (observation.kind === "failed") {
29
- lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: observation.diagnostic });
27
+ }
28
+ const state = observation.journals.get(candidate.id)?.state ?? null;
29
+ if (state === null || state.terminal?.kind !== "claimed")
30
30
  return;
31
+ let holder;
32
+ try {
33
+ holder = (await readTaskHolderProjectionFromDecision(channel, observation)).get(candidate.id) ?? null;
31
34
  }
32
- const holder = observation.holders.get(candidate.id) ?? null;
33
- const expectedDisposition = terminal === "claimed" ? "held" : "released";
34
- if (holder === null || holder.disposition !== expectedDisposition)
35
+ catch (error) {
36
+ lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, diagnostic: diagnostic(error) });
37
+ return;
38
+ }
39
+ if (holder === null || holder.disposition !== "held")
35
40
  return;
36
41
  const taskId = holder.taskId;
42
+ let result;
43
+ try {
44
+ result = await settleTask(world, taskId);
45
+ }
46
+ catch (error) {
47
+ lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
48
+ return;
49
+ }
50
+ if (result.kind === "changed")
51
+ actions.push({ kind: "task", taskId, action: "done" });
52
+ else if (result.kind !== "unchanged") {
53
+ lags.push({ kind: "settlement-failed", surface: "task", contractId: candidate.id, taskId, diagnostic: taskFailure(result) });
54
+ return;
55
+ }
37
56
  try {
38
- await withTaskSettlementFence({ repository, taskId }, async () => {
39
- let current;
40
- try {
41
- const observation = await observeContractsForAdmissionAt(repository, input.channel, [candidate.id], taskHolderObservationSelection());
42
- current = {
43
- state: observation.journals.get(candidate.id)?.state ?? null,
44
- holders: await readTaskHolderProjectionFromDecision(input.channel, observation),
45
- };
46
- }
47
- catch (error) {
48
- lags.push({
49
- kind: "settlement-failed",
50
- surface: "task-holder",
51
- contractId: candidate.id,
52
- taskId,
53
- diagnostic: diagnostic(error),
54
- });
55
- return;
56
- }
57
- const state = current.state;
58
- const currentTerminal = state?.terminal?.kind;
59
- if (state === null || (currentTerminal !== "claimed" && currentTerminal !== "abandoned"))
60
- return;
61
- const currentHolder = current.holders.get(state.id) ?? null;
62
- const disposition = currentTerminal === "claimed" ? "held" : "released";
63
- if (currentHolder === null || currentHolder.taskId !== taskId || currentHolder.disposition !== disposition)
64
- return;
65
- try {
66
- const result = await settleTask(world, taskId, currentTerminal === "claimed" ? "done" : "open-from-done");
67
- if (result.kind === "changed")
68
- actions.push({ kind: "task", taskId, action: result.action });
69
- else if (result.kind !== "unchanged")
70
- lags.push({
71
- kind: "settlement-failed",
72
- surface: "task",
73
- contractId: state.id,
74
- taskId,
75
- diagnostic: taskFailure(result),
76
- });
77
- }
78
- catch (error) {
79
- lags.push({ kind: "settlement-failed", surface: "task", contractId: state.id, taskId, diagnostic: diagnostic(error) });
80
- }
81
- });
57
+ const publication = await publishTaskHolderRelease(repository, channel, observation, candidate.id);
58
+ if (publication.kind === "non-published") {
59
+ lags.push({
60
+ kind: "settlement-failed",
61
+ surface: "task-holder",
62
+ contractId: candidate.id,
63
+ taskId,
64
+ diagnostic: `Task holder release requires retry: ${publication.diagnostic}`,
65
+ });
66
+ }
82
67
  }
83
68
  catch (error) {
84
69
  lags.push({ kind: "settlement-failed", surface: "task-holder", contractId: candidate.id, taskId, diagnostic: diagnostic(error) });
85
70
  }
86
71
  }
87
- function settleNamespace(state, effects, actions, lags) {
72
+ async function settleNamespace(state, effects, actions, lags) {
88
73
  if (state.terminal !== null || state.coordinates.workspace !== "worktree")
89
74
  return;
90
75
  const worktrees = effects.filter((effect) => effect.kind === "worktree" && effect.action !== "removed");
91
76
  for (const effect of worktrees) {
92
77
  try {
93
- const world = World.at(effect.path);
78
+ const world = await World.at(effect.path);
94
79
  actions.push({
95
80
  kind: "namespace-context",
96
81
  path: effect.path,
97
- action: repairNamespaceContext(world, [contractSegment(state.id)]),
82
+ action: await repairNamespaceContext(world, [contractSegment(state.id)]),
98
83
  });
99
84
  }
100
85
  catch (error) {
@@ -108,28 +93,16 @@ function settleNamespace(state, effects, actions, lags) {
108
93
  }
109
94
  }
110
95
  }
111
- async function observeSettlement(repository, channel) {
112
- try {
113
- return {
114
- kind: "present",
115
- holders: await observeTaskHolderProjection(repository, channel),
116
- };
117
- }
118
- catch (error) {
119
- return { kind: "failed", diagnostic: diagnostic(error) };
120
- }
121
- }
122
- async function settleObserved(input, observation) {
96
+ async function settleObserved(input) {
123
97
  if (input.state === null)
124
98
  return { actions: [], lags: [] };
125
99
  const actions = [], lags = [];
126
- if (observation !== null) {
100
+ if (input.state.terminal?.kind === "claimed") {
127
101
  try {
128
102
  await settleTasks({
129
- observation,
130
103
  repository: input.repository,
131
104
  channel: input.channel,
132
- world: World.at(input.repository.primaryWorktree),
105
+ world: await World.at(input.repository.primaryWorktree),
133
106
  candidate: input.state,
134
107
  actions,
135
108
  lags,
@@ -139,7 +112,7 @@ async function settleObserved(input, observation) {
139
112
  lags.push({ kind: "settlement-failed", surface: "task", contractId: input.state.id, diagnostic: diagnostic(error) });
140
113
  }
141
114
  }
142
- settleNamespace(input.state, input.effects, actions, lags);
115
+ await settleNamespace(input.state, input.effects, actions, lags);
143
116
  return { actions, lags };
144
117
  }
145
118
  function onPrimaryWorktree(repository) {
@@ -147,19 +120,9 @@ function onPrimaryWorktree(repository) {
147
120
  }
148
121
  export async function settle(input) {
149
122
  const repository = onPrimaryWorktree(input.repository);
150
- const observation = taskRuleCanApply(input.state)
151
- ? await observeSettlement(repository, input.channel)
152
- : null;
153
- return settleObserved({ ...input, repository }, observation);
123
+ return await settleObserved({ ...input, repository });
154
124
  }
155
125
  export async function settleAll(input) {
156
126
  const repository = onPrimaryWorktree(input.repository);
157
- const observation = input.contracts.some((contract) => taskRuleCanApply(contract.state))
158
- ? await observeSettlement(repository, input.channel)
159
- : null;
160
- const reports = [];
161
- for (const contract of input.contracts) {
162
- reports.push(await settleObserved({ repository, channel: input.channel, ...contract }, observation));
163
- }
164
- return reports;
127
+ return await Promise.all(input.contracts.map((contract) => settleObserved({ repository, channel: input.channel, ...contract })));
165
128
  }
@@ -34,8 +34,7 @@ export type TaskTreeNode = Readonly<{
34
34
  priority: TaskPriority | null;
35
35
  }>;
36
36
  cycle?: true;
37
- reference?: true;
38
- needs: readonly TaskTreeNode[];
37
+ children: readonly TaskTreeNode[];
39
38
  }>;
40
39
  export type TaskBoard = Readonly<{
41
40
  tasks: ReadonlyMap<TaskId, TaskDocument>;
@@ -55,6 +54,7 @@ export type TaskDoctorIssue = Readonly<{
55
54
  relation: "needs" | "parent" | "supersedes";
56
55
  tasks: readonly TaskId[];
57
56
  }>;
57
+ export declare function taskDisposition(board: TaskBoard, task: TaskDocument): TaskDisposition;
58
58
  export declare function projectDetailFacts(board: TaskBoard, id: TaskId): TaskDetailFacts | null;
59
59
  export declare function projectRows(board: TaskBoard, scope: readonly string[] | null, selection: "active" | "closed" | "all"): readonly TaskRow[];
60
60
  export declare function projectReady(board: TaskBoard, scope: readonly string[] | null): readonly TaskRow[];
@@ -79,5 +79,5 @@ export declare function projectStatusRows(board: TaskBoard, scope: readonly stri
79
79
  })[];
80
80
  export declare function relationProblem(board: TaskBoard, before: TaskDocument | null, after: TaskDocument): string | null;
81
81
  export declare function diagnoseBoard(board: TaskBoard): readonly TaskDoctorIssue[];
82
- export declare function buildTree(board: TaskBoard, root: TaskId, full: boolean): TaskTreeNode | null;
82
+ export declare function buildTree(board: TaskBoard, root: TaskId): TaskTreeNode | null;
83
83
  export {};
@@ -19,7 +19,7 @@ function related(board, task) {
19
19
  ids.add(candidate.id);
20
20
  return sorted([...ids].map((id) => taskRef(board, id)));
21
21
  }
22
- function disposition(board, task) {
22
+ export function taskDisposition(board, task) {
23
23
  if (task.state !== "open")
24
24
  return task.state;
25
25
  return task.needs.every((id) => { const target = board.tasks.get(id); return target !== undefined && terminal(target.state); }) ? "ready" : "blocked";
@@ -44,7 +44,7 @@ export function projectRows(board, scope, selection) {
44
44
  return [...board.tasks.values()].filter((task) => inScope(task, scope)).filter((task) => selection === "all"
45
45
  || (selection === "closed") === terminal(task.state)).map((task) => ({
46
46
  id: task.id, title: task.title, state: task.state, priority: task.priority,
47
- disposition: disposition(board, task),
47
+ disposition: taskDisposition(board, task),
48
48
  })).sort((a, b) => a.priority - b.priority || Buffer.compare(Buffer.from(a.id), Buffer.from(b.id)));
49
49
  }
50
50
  export function projectReady(board, scope) {
@@ -147,19 +147,16 @@ export function diagnoseBoard(board) {
147
147
  }
148
148
  return issues;
149
149
  }
150
- export function buildTree(board, root, full) {
150
+ export function buildTree(board, root) {
151
151
  if (!board.tasks.has(root))
152
152
  return null;
153
- const expanded = new Set();
154
153
  const walk = (id, path) => {
155
154
  const ref = taskRef(board, id);
156
155
  const task = board.tasks.get(id);
156
+ const node = { task: { ...ref, priority: task?.priority ?? null } };
157
157
  if (path.includes(id))
158
- return { task: { ...ref, priority: task?.priority ?? null }, cycle: true, needs: [] };
159
- if (!full && expanded.has(id))
160
- return { task: { ...ref, priority: task?.priority ?? null }, reference: true, needs: [] };
161
- expanded.add(id);
162
- return { task: { ...ref, priority: task?.priority ?? null }, needs: task === undefined ? [] : task.needs.map((need) => walk(need, [...path, id])) };
158
+ return { ...node, cycle: true, children: [] };
159
+ return { ...node, children: children(board, id).map((child) => walk(child.id, [...path, id])) };
163
160
  };
164
161
  return walk(root, []);
165
162
  }
@@ -21,5 +21,5 @@ export type TaskCompositionResult = Readonly<{
21
21
  }>;
22
22
  draft: string;
23
23
  }>;
24
- export declare function composeTasks(world: WorldRoot, markdown: string, signal?: AbortSignal): Promise<TaskCompositionResult>;
24
+ export declare function composeTasks(world: WorldRoot, markdown: string, signal?: AbortSignal, actor?: string): Promise<TaskCompositionResult>;
25
25
  export {};
@@ -1,3 +1,4 @@
1
+ import { resolve } from "node:path";
1
2
  import { documentDiff } from "../markdown/diff.js";
2
3
  import { readNamespaceContext } from "./context.js";
3
4
  import { relationProblem } from "./board.js";
@@ -119,7 +120,14 @@ function applyAssignments(document, assignments, body) {
119
120
  }
120
121
  function currentTimestamp() { return new Date().toISOString(); }
121
122
  function advancedTimestamp(previous, current) { return current > previous ? current : new Date(Date.parse(previous) + 1).toISOString(); }
122
- function plan(sketch, board, defaultNamespace, at) {
123
+ function createdTask(id, title, at, actor) {
124
+ return {
125
+ id, title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "",
126
+ ...(actor === undefined ? {} : { createdBy: actor }),
127
+ createdAt: at, updatedAt: at, body: "",
128
+ };
129
+ }
130
+ function plan(sketch, board, defaultNamespace, at, actor) {
123
131
  try {
124
132
  const namespace = sketch.namespace ?? defaultNamespace;
125
133
  const occupied = new Set([...board.tasks.values()].flatMap((task) => {
@@ -132,7 +140,7 @@ function plan(sketch, board, defaultNamespace, at) {
132
140
  const localId = allocateLocalId(deriveLocalStem(node.title), occupied);
133
141
  occupied.add(localId);
134
142
  const coordinate = { namespace, localId };
135
- allocations.set(node.index, { id: formatTaskId(coordinate), title: node.title, state: "open", priority: 2, needs: [], parent: null, supersedes: [], relates: [], note: "", createdAt: at, updatedAt: at, body: "" });
143
+ allocations.set(node.index, createdTask(formatTaskId(coordinate), node.title, at, actor));
136
144
  }
137
145
  const all = new Map(board.tasks);
138
146
  for (const allocated of allocations.values())
@@ -190,24 +198,24 @@ function draft(namespace, remaining) {
190
198
  }
191
199
  return `${lines.join("\n")}\n`;
192
200
  }
193
- export async function composeTasks(world, markdown, signal) {
201
+ export async function composeTasks(world, markdown, signal, actor) {
194
202
  const sketch = parseSketch(markdown);
195
203
  if ("kind" in sketch)
196
204
  return { kind: "refused", refusal: sketch };
197
205
  const at = currentTimestamp();
198
- const context = sketch.namespace ?? readNamespaceContext(world);
206
+ const context = sketch.namespace ?? await readNamespaceContext(world);
199
207
  if (context === "malformed")
200
- return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: `${world}/.keiyaku/namespace/current` } };
208
+ return { kind: "refused", refusal: { kind: "invalid-namespace-context", path: resolve(world, ".keiyaku", "namespace", "current") } };
201
209
  const namespace = context === "absent" ? [] : context;
202
- const initial = readBoard(world);
203
- const planned = plan(sketch, initial.board, namespace, at);
210
+ const initial = await readBoard(world);
211
+ const planned = plan(sketch, initial.board, namespace, at, actor);
204
212
  if ("kind" in planned)
205
213
  return { kind: "refused", refusal: planned };
206
214
  const ordered = [...planned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id)));
207
215
  const allocation = sketch.nodes.some((node) => node.kind === "new");
208
216
  const result = await withTaskLocks({ world, allocation, ids: ordered.map((item) => item.after.id), ...(signal === undefined ? {} : { signal }) }, async () => {
209
- const fresh = readBoard(world);
210
- const replanned = plan(sketch, fresh.board, namespace, at);
217
+ const fresh = await readBoard(world);
218
+ const replanned = plan(sketch, fresh.board, namespace, at, actor);
211
219
  if ("kind" in replanned)
212
220
  return { kind: "refused", refusal: replanned };
213
221
  const queue = [...replanned].sort((a, b) => Buffer.compare(Buffer.from(a.after.id), Buffer.from(b.after.id))), changes = [];
@@ -219,7 +227,7 @@ export async function composeTasks(world, markdown, signal) {
219
227
  const before = beforeBytes === null ? "" : Buffer.from(beforeBytes).toString("utf8"), after = Buffer.from(afterBytes).toString("utf8");
220
228
  if (before === after)
221
229
  continue;
222
- if (replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
230
+ if (await replaceAuthority({ path, expected: beforeBytes, next: afterBytes }) !== "replaced")
223
231
  return {
224
232
  kind: "incomplete", documentChanges: changes, stopped: { kind: "retry", reason: "concurrent-modification" }, draft: draft(namespace, queue.slice(index)),
225
233
  };
@@ -1,4 +1,4 @@
1
1
  export type NamespaceContextRead = readonly string[] | "absent" | "malformed";
2
- export declare function readNamespaceContext(root: string): NamespaceContextRead;
3
- export declare function installNamespaceContext(root: string, segments: readonly string[]): void;
4
- export declare function repairNamespaceContext(root: string, segments: readonly string[]): "kept" | "installed";
2
+ export declare function readNamespaceContext(root: string): Promise<NamespaceContextRead>;
3
+ export declare function installNamespaceContext(root: string, segments: readonly string[]): Promise<void>;
4
+ export declare function repairNamespaceContext(root: string, segments: readonly string[]): Promise<"kept" | "installed">;
@@ -1,4 +1,4 @@
1
- import { lstatSync, mkdirSync, readFileSync, } from "node:fs";
1
+ import { lstat, mkdir, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { repairDerivedFile, replaceFileDurably } from "../coordination/durable-file.js";
4
4
  import { normalizeIdentityStem } from "../identity/normalize.js";
@@ -10,13 +10,13 @@ function validNamespaceSegments(value) {
10
10
  && segment.length > 0 && normalizeIdentityStem({ source: segment }) === segment
11
11
  && !segment.includes("/") && segment !== "." && segment !== "..");
12
12
  }
13
- export function readNamespaceContext(root) {
13
+ export async function readNamespaceContext(root) {
14
14
  let bytes;
15
15
  try {
16
- const path = currentPath(root), stat = lstatSync(path);
16
+ const path = currentPath(root), stat = await lstat(path);
17
17
  if (!stat.isFile() || stat.isSymbolicLink())
18
18
  return "malformed";
19
- bytes = readFileSync(path, "utf8");
19
+ bytes = await readFile(path, "utf8");
20
20
  }
21
21
  catch (error) {
22
22
  if (error.code === "ENOENT")
@@ -29,23 +29,23 @@ export function readNamespaceContext(root) {
29
29
  const segments = content === "" ? [] : content.split("/");
30
30
  return validNamespaceSegments(segments) ? segments : "malformed";
31
31
  }
32
- export function installNamespaceContext(root, segments) {
32
+ export async function installNamespaceContext(root, segments) {
33
33
  if (!validNamespaceSegments(segments))
34
34
  throw new TypeError("namespace must contain normalized segments");
35
35
  const parent = directory(root);
36
- mkdirSync(parent, { recursive: true });
37
- installIgnore(root);
38
- replaceFileDurably(currentPath(root), `${segments.join("/")}\n`);
36
+ await mkdir(parent, { recursive: true });
37
+ await installIgnore(root);
38
+ await replaceFileDurably(currentPath(root), `${segments.join("/")}\n`);
39
39
  }
40
- function installIgnore(root) {
41
- repairDerivedFile(ignorePath(root), "*\n");
40
+ async function installIgnore(root) {
41
+ await repairDerivedFile(ignorePath(root), "*\n");
42
42
  }
43
- export function repairNamespaceContext(root, segments) {
44
- const current = readNamespaceContext(root);
43
+ export async function repairNamespaceContext(root, segments) {
44
+ const current = await readNamespaceContext(root);
45
45
  if (Array.isArray(current)) {
46
- installIgnore(root);
46
+ await installIgnore(root);
47
47
  return "kept";
48
48
  }
49
- installNamespaceContext(root, segments);
49
+ await installNamespaceContext(root, segments);
50
50
  return "installed";
51
51
  }
@@ -11,11 +11,12 @@ export type TaskDocument = Readonly<{
11
11
  supersedes: readonly TaskId[];
12
12
  relates: readonly TaskId[];
13
13
  note: string;
14
+ createdBy?: string;
14
15
  createdAt: string;
15
16
  updatedAt: string;
16
17
  body: string;
17
18
  }>;
18
- export type TaskCreationDocument = Omit<TaskDocument, "id" | "createdAt" | "updatedAt">;
19
+ export type TaskCreationDocument = Omit<TaskDocument, "id" | "createdBy" | "createdAt" | "updatedAt">;
19
20
  export declare class TaskAuthorityCorruptionError extends Error {
20
21
  constructor(message: string, options?: ErrorOptions);
21
22
  }
@@ -4,7 +4,8 @@ export class TaskAuthorityCorruptionError extends Error {
4
4
  constructor(message, options) { super(message, options); this.name = "TaskAuthorityCorruptionError"; }
5
5
  }
6
6
  const STATES = new Set(["open", "in_progress", "on_hold", "done", "drop"]);
7
- const STORED_KEYS = ["id", "title", "state", "priority", "needs", "parent", "supersedes", "relates", "note", "createdAt", "updatedAt"];
7
+ const REQUIRED_STORED_KEYS = ["id", "title", "state", "priority", "needs", "parent", "supersedes", "relates", "note", "createdAt", "updatedAt"];
8
+ const STORED_KEYS = [...REQUIRED_STORED_KEYS, "createdBy"];
8
9
  const CREATION_KEYS = ["title", "state", "priority", "needs", "parent", "supersedes", "relates", "note"];
9
10
  function frontMatter(markdown, fail) {
10
11
  if (!markdown.startsWith("---\n"))
@@ -73,6 +74,11 @@ function taskIds(value, field, fail) {
73
74
  function nullableTaskId(value, field, fail) {
74
75
  return value === null ? null : taskId(value, field, fail);
75
76
  }
77
+ function createdBy(value, fail) {
78
+ if (typeof value !== "string" || value.trim().length === 0)
79
+ fail("createdBy must be a nonblank string");
80
+ return value;
81
+ }
76
82
  function fields(value, body, fail) {
77
83
  return {
78
84
  title: title(value.title, fail),
@@ -88,14 +94,17 @@ function fields(value, body, fail) {
88
94
  export function parseTaskDocument(bytes, expected) {
89
95
  const fail = (message, cause) => { throw new TaskAuthorityCorruptionError(message, cause === undefined ? {} : { cause }); };
90
96
  const { value, body } = frontMatter(Buffer.from(bytes).toString("utf8"), fail);
91
- closed(value, STORED_KEYS, STORED_KEYS, fail);
97
+ closed(value, STORED_KEYS, REQUIRED_STORED_KEYS, fail);
92
98
  const id = taskId(value.id, "id", fail);
93
99
  if (id !== formatTaskId(expected))
94
100
  fail(`task document ID ${id} does not match its authority path`);
95
101
  const createdAt = timestamp(value.createdAt, "createdAt", fail), updatedAt = timestamp(value.updatedAt, "updatedAt", fail);
96
102
  if (Date.parse(updatedAt) < Date.parse(createdAt))
97
103
  fail("updatedAt must not precede createdAt");
98
- return { id, state: state(value.state, fail), createdAt, updatedAt, ...fields(value, body, fail) };
104
+ return {
105
+ id, state: state(value.state, fail), createdAt, updatedAt, ...fields(value, body, fail),
106
+ ...(Object.hasOwn(value, "createdBy") ? { createdBy: createdBy(value.createdBy, fail) } : {}),
107
+ };
99
108
  }
100
109
  export function parseTaskCreationDocument(markdown) {
101
110
  const fail = (message) => { throw new TypeError(message); };
@@ -111,8 +120,9 @@ export function serializeTaskDocument(document) {
111
120
  const value = {
112
121
  id: document.id, title: document.title, state: document.state, priority: document.priority,
113
122
  needs: [...document.needs], parent: document.parent, supersedes: [...document.supersedes],
114
- relates: [...document.relates], note: document.note, createdAt: document.createdAt,
115
- updatedAt: document.updatedAt,
123
+ relates: [...document.relates], note: document.note,
124
+ ...(document.createdBy === undefined ? {} : { createdBy: document.createdBy }),
125
+ createdAt: document.createdAt, updatedAt: document.updatedAt,
116
126
  };
117
127
  return Buffer.from(`---\n${stringify(value, { lineWidth: 0 })}---\n${document.body}`);
118
128
  }
@@ -5,26 +5,26 @@ import { type TaskCompositionResult } from "./compose.js";
5
5
  import { TaskAuthorityCorruptionError, type TaskPriority, type TaskState } from "./document.js";
6
6
  import { type TaskId } from "./identity.js";
7
7
  import { type AddTaskDocumentInput, type AddTaskInput, type TaskBatchResult, type TaskMutationResult, type TaskOutcome, type TaskRefusal, type TaskRetry, type TaskUpdateResult, type TaskView, type UpdateTaskInput } from "./operations.js";
8
+ import { type TaskPage, type TaskQueryExpression, type TaskQueryPredicate, type TaskQueryRow, type TaskQuerySort } from "./query.js";
8
9
  export type TaskDetail = Omit<TaskDetailFacts, "task"> & Readonly<{
9
10
  task: TaskView;
10
11
  }>;
11
- export type TaskList = TaskOutcome<readonly TaskRow[]>;
12
- export type BlockedTaskList = TaskOutcome<readonly BlockedTaskRow[]>;
12
+ export type TaskList = TaskOutcome<TaskPage<TaskRow>>;
13
+ export type BlockedTaskList = TaskOutcome<TaskPage<BlockedTaskRow>>;
14
+ export type TaskQueryResult = TaskOutcome<TaskPage<TaskQueryRow>>;
13
15
  export type TaskNamespaceResult = TaskOutcome<readonly string[]>;
14
16
  export type TaskDoctorReport = Readonly<{
15
17
  issues: readonly TaskDoctorIssue[];
16
18
  }>;
17
- export type TaskDependencyTree = TaskOutcome<TaskTreeNode>;
18
- export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskUpdateResult, TaskView, UpdateTaskInput };
19
+ export type TaskDecompositionTree = TaskOutcome<TaskTreeNode>;
20
+ export type { AddTaskDocumentInput, AddTaskInput, BlockedTaskRow, TaskBatchResult, TaskCompositionResult, TaskDoctorIssue, TaskId, TaskMutationResult, TaskOutcome, TaskPriority, TaskRef, TaskRefusal, TaskRetry, TaskRow, TaskState, TaskTreeNode, TaskUpdateResult, TaskView, UpdateTaskInput, TaskPage, TaskQueryExpression, TaskQueryPredicate, TaskQueryRow, TaskQuerySort };
19
21
  export { TaskAuthorityCorruptionError };
20
22
  declare class TaskHandle {
21
23
  readonly id: TaskId;
22
24
  private readonly world;
23
25
  constructor(id: TaskId, world: WorldRoot);
24
26
  read(): Promise<TaskDetail | null>;
25
- tree(input?: Readonly<{
26
- full?: boolean;
27
- }>): Promise<TaskDependencyTree>;
27
+ tree(): Promise<TaskDecompositionTree>;
28
28
  update(input: UpdateTaskInput): Promise<TaskUpdateResult>;
29
29
  private lifecycle;
30
30
  start(input?: {
@@ -65,13 +65,24 @@ declare class TasksHandle {
65
65
  list(input?: Readonly<{
66
66
  selection?: "active" | "closed" | "all";
67
67
  scope?: "namespace" | "world";
68
+ limit?: number;
68
69
  }>): Promise<TaskList>;
69
70
  ready(input?: Readonly<{
70
71
  scope?: "namespace" | "world";
72
+ parent?: string;
73
+ limit?: number;
71
74
  }>): Promise<TaskList>;
72
75
  blocked(input?: Readonly<{
73
76
  scope?: "namespace" | "world";
77
+ parent?: string;
78
+ limit?: number;
74
79
  }>): Promise<BlockedTaskList>;
80
+ query(input?: Readonly<{
81
+ where?: TaskQueryExpression;
82
+ scope?: "namespace" | "world";
83
+ sort?: TaskQuerySort;
84
+ limit?: number;
85
+ }>): Promise<TaskQueryResult>;
75
86
  doctor(): Promise<TaskDoctorReport>;
76
87
  batch(input: Readonly<{
77
88
  verb: "done" | "drop" | "hold";
@@ -81,6 +92,7 @@ declare class TasksHandle {
81
92
  }>): Promise<TaskBatchResult>;
82
93
  compose(input: Readonly<{
83
94
  markdown: string;
95
+ actor?: string;
84
96
  signal?: AbortSignal;
85
97
  }>): Promise<TaskCompositionResult>;
86
98
  }